From 6ea80c4ecd61371b0b50c844b368dce66b177942 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 5 Nov 2015 16:21:19 -0800 Subject: [PATCH 01/47] beginnings of a parser --- src/object-store/parser/parser.cpp | 65 ++++++++++++++++++++++++++++++ src/object-store/parser/query.abnf | 23 +++++++++++ 2 files changed, 88 insertions(+) create mode 100644 src/object-store/parser/parser.cpp create mode 100644 src/object-store/parser/query.abnf diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp new file mode 100644 index 00000000..5d89918e --- /dev/null +++ b/src/object-store/parser/parser.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include + +using namespace pegtl; + +namespace query +{ + // strings + struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; + struct escaped_char : one< '"', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; + struct escaped : sor< escaped_char, unicode > {}; + struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; + struct char_ : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; + + struct string_content : until< at< one< '"' > >, must< char_ > > {}; + struct string : seq< one< '"' >, must< string_content >, any > + { + using content = string_content; + }; + + // numbers + struct minus : opt< one< '-' > > {}; + struct dot : one< '.' > {}; + + struct float_num : sor< + seq< plus< digit >, dot, star< digit > >, + seq< star< digit >, dot, plus< digit > > + > {}; + struct hex_num : seq< one< '0' >, one< 'x', 'X' >, plus< xdigit > > {}; + struct int_num : plus< digit > {}; + + struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; + + // key paths + struct key_path : list< must< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; + + // expressions and operators + struct expr : sor< string, key_path, number > {}; + struct oper : sor< one< '=' >, istring< '=', '=' >, istring< '!', '=' >, one< '<' >, istring< '<', '=' >, one< '>' >, istring< '>', '=' > > {}; + + // predicates + struct pred : seq< expr, plus< blank >, oper, plus< blank >, expr > {}; + + // rules + template< typename Rule > + struct action : nothing< Rule > {}; + template<> struct action< expr > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << in.string() << std::endl; + } + }; +} + +int main( int argc, char ** argv ) +{ + if ( argc > 1 ) { + std::string intstring; + parse< must< query::expr, eof>, query::action >( 1, argv, intstring); + } +} + diff --git a/src/object-store/parser/query.abnf b/src/object-store/parser/query.abnf new file mode 100644 index 00000000..d70416d3 --- /dev/null +++ b/src/object-store/parser/query.abnf @@ -0,0 +1,23 @@ + + +pred = expr 1*WSP oper 1*WSP expr +;pred =/ "(" *WSP pred *WSP ")" +;pred =/ ("NOT" / "!") 1*WSP pred +;pred =/ pred 1*WSP ("OR" / "||") !*WSP pred +;pred =/ pred 1*WSP ("AND" / "&&") 1*WSP pred + +oper = "=" / "==" / "!=" / "<" / "<=" / ">" / ">=" + +expr = string / num / key-path + +key-path = key *("." key) +key = 1*(ALPHA / "_") + +string = dq-string / sq-string +sq-string = "'" *(%x20-ffffffff) "'" +dq-string = DQUOTE *("\\" / %x20-21 / %x23-ffffffff) DQUOTE + +num = float-num / int-num / hex-num +float-num = ["-"] (*DIGIT "." 1*DIGIT / "." 1*DIGIT) +int-num = ["-"] 1*DIGIT +hex-num = ["-"] ["0"] "x" 1*HEXDIG From ed4e59c8d09ea73592e1841b663181d35e016aa1 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 6 Nov 2015 08:23:23 -0800 Subject: [PATCH 02/47] compound predicates --- src/object-store/parser/parser.cpp | 57 +++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 5d89918e..ce211a21 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include using namespace pegtl; @@ -34,32 +36,77 @@ namespace query struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; // key paths - struct key_path : list< must< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; + struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; // expressions and operators struct expr : sor< string, key_path, number > {}; - struct oper : sor< one< '=' >, istring< '=', '=' >, istring< '!', '=' >, one< '<' >, istring< '<', '=' >, one< '>' >, istring< '>', '=' > > {}; + struct oper : sor< + two< '=' >, + one< '=' >, + pegtl::string< '!', '=' >, + pegtl::string< '<', '=' >, + one< '<' >, + pegtl::string< '>', '=' >, + one< '>' > + > {}; // predicates - struct pred : seq< expr, plus< blank >, oper, plus< blank >, expr > {}; + struct comparison_pred : seq< expr, pad< oper, blank >, expr > {}; + + struct pred; + struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; + + struct single_pred : pad< sor< group_pred, comparison_pred >, blank > {}; + struct not_pre : pegtl::string< 'N', 'O', 'T' > {}; + struct atom_pred : seq< opt< not_pre >, single_pred > {}; + + struct or_ext : if_must< two< '|' >, atom_pred > {}; + struct and_ext : if_must< two< '&' >, atom_pred > {}; + + struct pred : seq< atom_pred, star< sor< or_ext, and_ext > > > {}; // rules template< typename Rule > struct action : nothing< Rule > {}; - template<> struct action< expr > + template<> struct action< and_ext > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << "" << in.string() << std::endl; + } + }; + + template<> struct action< or_ext > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << "" << in.string() << std::endl; + } + }; + + template<> struct action< comparison_pred > { static void apply( const input & in, std::string & string_value ) { std::cout << in.string() << std::endl; } }; + + template<> struct action< group_pred > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << "" << std::endl; + } + }; } int main( int argc, char ** argv ) { if ( argc > 1 ) { std::string intstring; - parse< must< query::expr, eof>, query::action >( 1, argv, intstring); + analyze< query::pred >(); + parse< must< seq< query::pred, eof > >, query::action >( 1, argv, intstring); } } From 2f287d046dac0ec042b0adb6cb8a9cae613590e4 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 08:27:27 -0800 Subject: [PATCH 03/47] full grammar --- src/object-store/parser/parser.cpp | 32 ++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index ce211a21..d3d1f92d 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -57,13 +57,17 @@ namespace query struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; struct single_pred : pad< sor< group_pred, comparison_pred >, blank > {}; - struct not_pre : pegtl::string< 'N', 'O', 'T' > {}; + struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< istring< 'N', 'O', 'T' >, plus< blank > > > {}; struct atom_pred : seq< opt< not_pre >, single_pred > {}; - struct or_ext : if_must< two< '|' >, atom_pred > {}; - struct and_ext : if_must< two< '&' >, atom_pred > {}; + struct and_op : sor< two< '&' >, istring< 'A', 'N', 'D' > > {}; + struct or_op : sor< two< '|' >, istring< 'O', 'R' > > {}; - struct pred : seq< atom_pred, star< sor< or_ext, and_ext > > > {}; + struct or_ext : seq< pad< or_op, blank >, pred > {}; + struct and_ext : seq< pad< and_op, blank >, pred > {}; + struct and_pred : seq< atom_pred, star< and_ext > > {}; + + struct pred : seq< and_pred, star< or_ext > > {}; // rules template< typename Rule > @@ -92,11 +96,27 @@ namespace query } }; + template<> struct action< one< '(' > > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << "" << std::endl; + } + }; + template<> struct action< group_pred > { static void apply( const input & in, std::string & string_value ) { - std::cout << "" << std::endl; + std::cout << "" << std::endl; + } + }; + + template<> struct action< not_pre > + { + static void apply( const input & in, std::string & string_value ) + { + std::cout << "" << std::endl; } }; } @@ -106,7 +126,7 @@ int main( int argc, char ** argv ) if ( argc > 1 ) { std::string intstring; analyze< query::pred >(); - parse< must< seq< query::pred, eof > >, query::action >( 1, argv, intstring); + parse< must< query::pred, eof >, query::action >( 1, argv, intstring); } } From 5bdc6eba9300fba84085e5a33deee21fa0eeef87 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 11:29:57 -0800 Subject: [PATCH 04/47] parse tree construction --- .gitmodules | 6 + RealmJS.xcodeproj/project.pbxproj | 14 +- src/object-store/parser/main.cpp | 7 + src/object-store/parser/parser.cpp | 322 +++++++++++++++++++---------- src/object-store/parser/parser.hpp | 78 +++++++ vendor/PEGTL | 1 + 6 files changed, 319 insertions(+), 109 deletions(-) create mode 100644 src/object-store/parser/main.cpp create mode 100644 src/object-store/parser/parser.hpp create mode 160000 vendor/PEGTL diff --git a/.gitmodules b/.gitmodules index 9433b3de..f92241b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "vendor/GCDWebServer"] path = vendor/GCDWebServer url = https://github.com/swisspol/GCDWebServer.git +[submodule "PEGTL"] + path = PEGTL + url = https://github.com/ColinH/PEGTL.git +[submodule "vendor/PEGTL"] + path = vendor/PEGTL + url = https://github.com/ColinH/PEGTL.git diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index 6474ffb0..c8c7d041 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -46,6 +46,8 @@ 0270BC861B7D020100010E03 /* schemas.js in Resources */ = {isa = PBXBuildFile; fileRef = 0270BC7F1B7D020100010E03 /* schemas.js */; }; 0270BC871B7D023200010E03 /* RealmJS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; }; 0270BCD11B7D067300010E03 /* RealmReact.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0270BCD01B7D067300010E03 /* RealmReact.mm */; }; + 02786E331BF1065100937061 /* parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02786E311BF1065000937061 /* parser.cpp */; }; + 02786E341BF1065100937061 /* parser.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 02786E321BF1065000937061 /* parser.hpp */; }; 0291DBD21BD994F700E3852C /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; 029445931BDEDF40006D1617 /* transact_log_handler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029445911BDEDF40006D1617 /* transact_log_handler.cpp */; }; 029445941BDEDF40006D1617 /* transact_log_handler.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029445921BDEDF40006D1617 /* transact_log_handler.hpp */; }; @@ -200,6 +202,8 @@ 0270BC7F1B7D020100010E03 /* schemas.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = schemas.js; path = tests/schemas.js; sourceTree = SOURCE_ROOT; }; 0270BCCF1B7D067300010E03 /* RealmReact.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RealmReact.h; path = ReactNative/RealmReact.h; sourceTree = ""; }; 0270BCD01B7D067300010E03 /* RealmReact.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RealmReact.mm; path = ReactNative/RealmReact.mm; sourceTree = ""; }; + 02786E311BF1065000937061 /* parser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = parser.cpp; path = "src/object-store/parser/parser.cpp"; sourceTree = ""; }; + 02786E321BF1065000937061 /* parser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = parser.hpp; path = "src/object-store/parser/parser.hpp"; sourceTree = ""; }; 029445911BDEDF40006D1617 /* transact_log_handler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = transact_log_handler.cpp; path = "src/object-store/impl/transact_log_handler.cpp"; sourceTree = ""; }; 029445921BDEDF40006D1617 /* transact_log_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = transact_log_handler.hpp; path = "src/object-store/impl/transact_log_handler.hpp"; sourceTree = ""; }; 029445951BDEDF46006D1617 /* external_commit_helper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = external_commit_helper.cpp; path = "src/object-store/impl/apple/external_commit_helper.cpp"; sourceTree = ""; }; @@ -279,6 +283,8 @@ 029445921BDEDF40006D1617 /* transact_log_handler.hpp */, 029445951BDEDF46006D1617 /* external_commit_helper.cpp */, 029445961BDEDF46006D1617 /* external_commit_helper.hpp */, + 02786E311BF1065000937061 /* parser.cpp */, + 02786E321BF1065000937061 /* parser.hpp */, 0270BC3E1B7CFC0D00010E03 /* RealmJS.h */, 0270BC3F1B7CFC0D00010E03 /* RealmJS.mm */, 02258FB11BC6E2D00075F13A /* RealmRPC.hpp */, @@ -414,6 +420,7 @@ 0270BC501B7CFC0D00010E03 /* RJSObject.hpp in Headers */, 02601F0E1BA0F3A7007C91FF /* schema.hpp in Headers */, 0270BC6C1B7CFC1C00010E03 /* object_store.hpp in Headers */, + 02786E341BF1065100937061 /* parser.hpp in Headers */, 0270BC681B7CFC1C00010E03 /* object_accessor.hpp in Headers */, F6BB7DF21BF681BC00D0A69E /* base64.hpp in Headers */, 029445941BDEDF40006D1617 /* transact_log_handler.hpp in Headers */, @@ -656,6 +663,7 @@ 0270BC691B7CFC1C00010E03 /* object_schema.cpp in Sources */, 02601F0D1BA0F3A7007C91FF /* schema.cpp in Sources */, 0270BC6E1B7CFC1C00010E03 /* results.cpp in Sources */, + 02786E331BF1065100937061 /* parser.cpp in Sources */, 0270BC511B7CFC0D00010E03 /* RJSObject.mm in Sources */, 0270BC4D1B7CFC0D00010E03 /* RealmJS.mm in Sources */, 02601F081BA0F0CD007C91FF /* index_set.cpp in Sources */, @@ -936,7 +944,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/vendor", + "$(SRCROOT)/vendor/PEGTL", ); INFOPLIST_FILE = src/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -970,7 +978,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/vendor", + "$(SRCROOT)/vendor/PEGTL", ); INFOPLIST_FILE = src/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1106,7 +1114,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/vendor", + "$(SRCROOT)/vendor/PEGTL", ); INFOPLIST_FILE = src/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/src/object-store/parser/main.cpp b/src/object-store/parser/main.cpp new file mode 100644 index 00000000..def876c3 --- /dev/null +++ b/src/object-store/parser/main.cpp @@ -0,0 +1,7 @@ +#include "parser.hpp" + +int main( int argc, char ** argv ) +{ + realm::parser::parse(argv[1]); +} + diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index d3d1f92d..384d748a 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -1,4 +1,23 @@ -#include +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "parser.hpp" + #include #include @@ -7,126 +26,217 @@ using namespace pegtl; -namespace query +namespace realm { +namespace parser { + +// strings +struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; +struct escaped_char : one< '"', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; +struct escaped : sor< escaped_char, unicode > {}; +struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; +struct char_ : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; + +struct string_content : until< at< one< '"' > >, must< char_ > > {}; +struct string : seq< one< '"' >, must< string_content >, any > { - // strings - struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; - struct escaped_char : one< '"', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; - struct escaped : sor< escaped_char, unicode > {}; - struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; - struct char_ : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; + using content = string_content; +}; - struct string_content : until< at< one< '"' > >, must< char_ > > {}; - struct string : seq< one< '"' >, must< string_content >, any > - { - using content = string_content; - }; +// numbers +struct minus : opt< one< '-' > > {}; +struct dot : one< '.' > {}; - // numbers - struct minus : opt< one< '-' > > {}; - struct dot : one< '.' > {}; +struct float_num : sor< + seq< plus< digit >, dot, star< digit > >, + seq< star< digit >, dot, plus< digit > > +> {}; +struct hex_num : seq< one< '0' >, one< 'x', 'X' >, plus< xdigit > > {}; +struct int_num : plus< digit > {}; - struct float_num : sor< - seq< plus< digit >, dot, star< digit > >, - seq< star< digit >, dot, plus< digit > > - > {}; - struct hex_num : seq< one< '0' >, one< 'x', 'X' >, plus< xdigit > > {}; - struct int_num : plus< digit > {}; +struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; - struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; +// key paths +struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; - // key paths - struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; +// expressions and operators +struct expr : sor< string, key_path, number > {}; - // expressions and operators - struct expr : sor< string, key_path, number > {}; - struct oper : sor< - two< '=' >, - one< '=' >, - pegtl::string< '!', '=' >, - pegtl::string< '<', '=' >, - one< '<' >, - pegtl::string< '>', '=' >, - one< '>' > - > {}; +struct eq : sor< two< '=' >, one< '=' > > {}; +struct noteq : pegtl::string< '!', '=' > {}; +struct lteq : pegtl::string< '<', '=' > {}; +struct lt : one< '<' > {}; +struct gteq : pegtl::string< '>', '=' > {}; +struct gt : one< '<' > {}; +struct begins : istring< 'b', 'e', 'g', 'i', 'n', 's', 'w', 'i', 't', 'h' > {}; +struct ends : istring< 'e', 'n', 'd', 's', 'w', 'i', 't', 'h' > {}; +struct contains : istring< 'c', 'o', 'n', 't', 'a', 'i', 'n', 's' > {}; +struct oper : sor< eq, noteq, lteq, lt, gteq, gt, begins, ends, contains > {}; - // predicates - struct comparison_pred : seq< expr, pad< oper, blank >, expr > {}; +// predicates +struct comparison_pred : seq< expr, pad< oper, blank >, expr > {}; - struct pred; - struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; +struct pred; +struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; - struct single_pred : pad< sor< group_pred, comparison_pred >, blank > {}; - struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< istring< 'N', 'O', 'T' >, plus< blank > > > {}; - struct atom_pred : seq< opt< not_pre >, single_pred > {}; +struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< istring< 'N', 'O', 'T' >, plus< blank > > > {}; +struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, comparison_pred >, blank > > {}; - struct and_op : sor< two< '&' >, istring< 'A', 'N', 'D' > > {}; - struct or_op : sor< two< '|' >, istring< 'O', 'R' > > {}; +struct and_op : sor< two< '&' >, istring< 'A', 'N', 'D' > > {}; +struct or_op : sor< two< '|' >, istring< 'O', 'R' > > {}; - struct or_ext : seq< pad< or_op, blank >, pred > {}; - struct and_ext : seq< pad< and_op, blank >, pred > {}; - struct and_pred : seq< atom_pred, star< and_ext > > {}; +struct or_ext : seq< pad< or_op, blank >, pred > {}; +struct and_ext : seq< pad< and_op, blank >, pred > {}; +struct and_pred : seq< atom_pred, star< and_ext > > {}; - struct pred : seq< and_pred, star< or_ext > > {}; +struct pred : seq< and_pred, star< or_ext > > {}; - // rules - template< typename Rule > - struct action : nothing< Rule > {}; - template<> struct action< and_ext > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << "" << in.string() << std::endl; - } - }; - - template<> struct action< or_ext > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << "" << in.string() << std::endl; - } - }; - - template<> struct action< comparison_pred > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << in.string() << std::endl; - } - }; - - template<> struct action< one< '(' > > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << "" << std::endl; - } - }; - - template<> struct action< group_pred > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << "" << std::endl; - } - }; - - template<> struct action< not_pre > - { - static void apply( const input & in, std::string & string_value ) - { - std::cout << "" << std::endl; - } - }; -} - -int main( int argc, char ** argv ) +// state +struct ParserState { - if ( argc > 1 ) { - std::string intstring; - analyze< query::pred >(); - parse< must< query::pred, eof >, query::action >( 1, argv, intstring); + std::vector predicate_stack; + Predicate *current() { return predicate_stack.back(); } + + void addExpression(Expression exp) + { + if (current()->type == Predicate::Type::Comparison) { + current()->sub_expressions.emplace_back(std::move(exp)); + predicate_stack.pop_back(); + } + else { + Predicate p; + p.type = Predicate::Type::Comparison; + p.sub_expressions.emplace_back(std::move(exp)); + current()->sub_predicates.emplace_back(std::move(p)); + predicate_stack.push_back(¤t()->sub_predicates.back()); + } } +}; + +// rules +template< typename Rule > +struct action : nothing< Rule > {}; + +template<> struct action< and_ext > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << "" << in.string() << std::endl; + } +}; + +template<> struct action< or_ext > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << "" << in.string() << std::endl; + } +}; + +template<> struct action< string > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + + Expression exp; + exp.type = Expression::Type::String; + exp.s = in.string(); + + state.addExpression(exp); + } +}; + +template<> struct action< key_path > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + + Expression exp; + exp.type = Expression::Type::KeyPath; + exp.s = in.string(); + + state.addExpression(std::move(exp)); + } +}; + +template<> struct action< number > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + + Expression exp; + exp.type = Expression::Type::Number; + exp.s = in.string(); + + state.addExpression(std::move(exp)); + } +}; + +#define OPERATOR_ACTION(rule, oper) \ +template<> struct action< rule > { \ + static void apply( const input & in, ParserState & state ) { \ + std::cout << in.string() << std::endl; \ + state.current()->op = oper; }}; + +OPERATOR_ACTION(eq, Predicate::Operator::Equal) +OPERATOR_ACTION(noteq, Predicate::Operator::NotEqual) +OPERATOR_ACTION(gteq, Predicate::Operator::GreaterThanOrEqual) +OPERATOR_ACTION(gt, Predicate::Operator::GreaterThan) +OPERATOR_ACTION(lteq, Predicate::Operator::LessThanOrEqual) +OPERATOR_ACTION(lt, Predicate::Operator::LessThan) +OPERATOR_ACTION(begins, Predicate::Operator::BeginsWith) +OPERATOR_ACTION(ends, Predicate::Operator::EndsWith) +OPERATOR_ACTION(contains, Predicate::Operator::Contains) + + +template<> struct action< one< '(' > > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << "" << std::endl; + + Predicate group; + group.type = Predicate::Type::And; + state.current()->sub_predicates.emplace_back(std::move(group)); + state.predicate_stack.push_back(&state.current()->sub_predicates.back()); + } +}; + +template<> struct action< group_pred > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << "" << std::endl; + + state.predicate_stack.pop_back(); + } +}; + +template<> struct action< not_pre > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << "" << std::endl; + } +}; + +Predicate parse(const std::string &query) +{ + analyze< pred >(); + const std::string source = "user query"; + + Predicate out_predicate; + out_predicate.type = Predicate::Type::And; + + ParserState state; + state.predicate_stack.push_back(&out_predicate); + + pegtl::parse< must< pred, eof >, action >(query, source, state); + return out_predicate; } +}} + + diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp new file mode 100644 index 00000000..416faa2e --- /dev/null +++ b/src/object-store/parser/parser.hpp @@ -0,0 +1,78 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_EXTERNAL_COMMIT_HELPER_HPP +#define REALM_EXTERNAL_COMMIT_HELPER_HPP + +#include +#include + +namespace realm { + namespace parser { + struct Expression + { + enum class Type + { + Number, + String, + KeyPath, + }; + Type type; + + std::string s; + }; + + struct Predicate + { + enum class Type + { + None, + Comparison, + Or, + And, + True, + False, + }; + Type type = Type::None; + + // for comparison + enum class Operator + { + None, + Equal, + NotEqual, + LessThan, + LessThanOrEqual, + GreaterThan, + GreaterThanOrEqual, + BeginsWith, + EndsWith, + Contains, + }; + Operator op = Operator::None; + std::vector sub_expressions; + + // for compounds + std::vector sub_predicates; + }; + + Predicate parse(const std::string &query); + } +} + +#endif // REALM_EXTERNAL_COMMIT_HELPER_HPP diff --git a/vendor/PEGTL b/vendor/PEGTL new file mode 160000 index 00000000..498cb70f --- /dev/null +++ b/vendor/PEGTL @@ -0,0 +1 @@ +Subproject commit 498cb70f5b60b9d87d7f4864104155339daf561b From 4b3417736f0cbf20c9c84a4cbb0c548211bc4445 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 12:13:01 -0800 Subject: [PATCH 05/47] support OR with proper precedence --- src/object-store/parser/parser.cpp | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 384d748a..ab2b8726 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -121,6 +121,28 @@ template<> struct action< and_ext > static void apply( const input & in, ParserState & state ) { std::cout << "" << in.string() << std::endl; + + // if we were put into an OR group we need to rearrange + auto current = state.current(); + if (current->type == Predicate::Type::Or) { + auto &sub_preds = state.current()->sub_predicates; + auto second_last = sub_preds[sub_preds.size()-2]; + if (second_last.type == Predicate::Type::And) { + // if we are in an OR group and second to last predicate group is + // an AND group then move the last predicate inside + second_last.sub_predicates.push_back(sub_preds.back()); + sub_preds.pop_back(); + } + else { + // otherwise combine last two into a new AND group + Predicate pred; + pred.type = Predicate::Type::And; + pred.sub_predicates = { second_last, sub_preds.back() }; + sub_preds.pop_back(); + sub_preds.pop_back(); + sub_preds.push_back(pred); + } + } } }; @@ -129,6 +151,29 @@ template<> struct action< or_ext > static void apply( const input & in, ParserState & state ) { std::cout << "" << in.string() << std::endl; + + // if already an OR group do nothing + auto current = state.current(); + if (current->type == Predicate::Type::Or) { + return; + } + + // if only two predicates in the group, then convert to OR + auto &sub_preds = state.current()->sub_predicates; + if (sub_preds.size()) { + current->type = Predicate::Type::Or; + return; + } + + // split the current group into to groups which are ORed together + Predicate pred1, pred2; + pred1.type = Predicate::Type::And; + pred2.type = Predicate::Type::And; + pred1.sub_predicates.insert(sub_preds.begin(), sub_preds.back()); + pred2.sub_predicates.push_back(sub_preds.back()); + + current->type = Predicate::Type::Or; + sub_preds = { pred1, pred2 }; } }; From 11b27dbdbf43c958fe3c01f6aa5d726a7e55c92c Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 12:20:59 -0800 Subject: [PATCH 06/47] store negated predicates in parse tree --- src/object-store/parser/parser.cpp | 12 ++++++++++++ src/object-store/parser/parser.hpp | 2 ++ 2 files changed, 14 insertions(+) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index ab2b8726..3b504ee9 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -95,6 +95,7 @@ struct ParserState { std::vector predicate_stack; Predicate *current() { return predicate_stack.back(); } + bool negate_next; void addExpression(Expression exp) { @@ -106,6 +107,10 @@ struct ParserState Predicate p; p.type = Predicate::Type::Comparison; p.sub_expressions.emplace_back(std::move(exp)); + if (negate_next) { + p.negate = true; + negate_next = false; + } current()->sub_predicates.emplace_back(std::move(p)); predicate_stack.push_back(¤t()->sub_predicates.back()); } @@ -244,6 +249,11 @@ template<> struct action< one< '(' > > Predicate group; group.type = Predicate::Type::And; + if (state.negate_next) { + group.negate = true; + state.negate_next = false; + } + state.current()->sub_predicates.emplace_back(std::move(group)); state.predicate_stack.push_back(&state.current()->sub_predicates.back()); } @@ -264,6 +274,8 @@ template<> struct action< not_pre > static void apply( const input & in, ParserState & state ) { std::cout << "" << std::endl; + + state.negate_next = true; } }; diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp index 416faa2e..149f12ff 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -69,6 +69,8 @@ namespace realm { // for compounds std::vector sub_predicates; + + bool negate; }; Predicate parse(const std::string &query); From c7c0698ad0067b10daf8d5a2003aca9b8fa59904 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 21:12:18 -0800 Subject: [PATCH 07/47] hook it up --- src/RJSResults.mm | 12 +- src/RJSUtil.mm | 539 ----------------------------- src/object-store/parser/parser.cpp | 351 +++++++++++++++++++ src/object-store/parser/parser.hpp | 5 + src/object-store/parser/query.abnf | 8 +- 5 files changed, 364 insertions(+), 551 deletions(-) diff --git a/src/RJSResults.mm b/src/RJSResults.mm index a00d0e84..9f19249e 100644 --- a/src/RJSResults.mm +++ b/src/RJSResults.mm @@ -6,6 +6,7 @@ #import "RJSObject.hpp" #import "object_accessor.hpp" #import "results.hpp" +#import "parser.hpp" using namespace realm; @@ -103,8 +104,6 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl } -void RLMUpdateQueryWithPredicate(realm::Query *query, NSPredicate *predicate, realm::Schema &schema, realm::ObjectSchema &objectSchema); - JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string className, std::string queryString) { TableRef table = ObjectStore::table_for_object_type(realm->read_group(), className); Query query = table->where(); @@ -113,12 +112,9 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl if (object_schema == realm->config().schema->end()) { throw std::runtime_error("Object type '" + className + "' not present in Realm."); } - @try { - RLMUpdateQueryWithPredicate(&query, [NSPredicate predicateWithFormat:@(queryString.c_str())], schema, *object_schema); - } - @catch(NSException *ex) { - throw std::runtime_error(ex.description.UTF8String); - } + parser::Predicate predicate = parser::parse(queryString); + parser::apply_predicate(query, predicate, schema, object_schema->name); + return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query))); } diff --git a/src/RJSUtil.mm b/src/RJSUtil.mm index 20993469..d1ab0193 100644 --- a/src/RJSUtil.mm +++ b/src/RJSUtil.mm @@ -98,542 +98,3 @@ bool RJSIsValueDate(JSContextRef ctx, JSValueRef value) { return RJSIsValueObjectOfType(ctx, value, dateString); } - -#include -#include "object_store.hpp" - -// check a precondition and throw an exception if it is not met -// this should be used iff the condition being false indicates a bug in the caller -// of the function checking its preconditions -static void RLMPrecondition(bool condition, NSString *name, NSString *format, ...) { - if (__builtin_expect(condition, 1)) { - return; - } - - va_list args; - va_start(args, format); - NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; - va_end(args); - - throw std::runtime_error(reason.UTF8String); -} - -// FIXME: TrueExpression and FalseExpression should be supported by core in some way -struct TrueExpression : realm::Expression { - size_t find_first(size_t start, size_t end) const override - { - if (start != end) - return start; - - return realm::not_found; - } - void set_table() override {} - const realm::Table* get_table() const override { return nullptr; } -}; - -struct FalseExpression : realm::Expression { - size_t find_first(size_t, size_t) const override { return realm::not_found; } - void set_table() override {} - const realm::Table* get_table() const override { return nullptr; } -}; - -NSString *operatorName(NSPredicateOperatorType operatorType) -{ - switch (operatorType) { - case NSLessThanPredicateOperatorType: - return @"<"; - case NSLessThanOrEqualToPredicateOperatorType: - return @"<="; - case NSGreaterThanPredicateOperatorType: - return @">"; - case NSGreaterThanOrEqualToPredicateOperatorType: - return @">="; - case NSEqualToPredicateOperatorType: - return @"=="; - case NSNotEqualToPredicateOperatorType: - return @"!="; - case NSMatchesPredicateOperatorType: - return @"MATCHES"; - case NSLikePredicateOperatorType: - return @"LIKE"; - case NSBeginsWithPredicateOperatorType: - return @"BEGINSWITH"; - case NSEndsWithPredicateOperatorType: - return @"ENDSWITH"; - case NSInPredicateOperatorType: - return @"IN"; - case NSContainsPredicateOperatorType: - return @"CONTAINS"; - case NSBetweenPredicateOperatorType: - return @"BETWEENS"; - case NSCustomSelectorPredicateOperatorType: - return @"custom selector"; - } - - return [NSString stringWithFormat:@"unknown operator %lu", (unsigned long)operatorType]; -} - -// add a clause for numeric constraints based on operator type -template -void add_numeric_constraint_to_query(realm::Query& query, - realm::PropertyType datatype, - NSPredicateOperatorType operatorType, - A lhs, - B rhs) -{ - switch (operatorType) { - case NSLessThanPredicateOperatorType: - query.and_query(lhs < rhs); - break; - case NSLessThanOrEqualToPredicateOperatorType: - query.and_query(lhs <= rhs); - break; - case NSGreaterThanPredicateOperatorType: - query.and_query(lhs > rhs); - break; - case NSGreaterThanOrEqualToPredicateOperatorType: - query.and_query(lhs >= rhs); - break; - case NSEqualToPredicateOperatorType: - query.and_query(lhs == rhs); - break; - case NSNotEqualToPredicateOperatorType: - query.and_query(lhs != rhs); - break; - default: { - NSString *error = [NSString stringWithFormat:@"Operator '%@' not supported for type %s", operatorName(operatorType), realm::string_for_property_type(datatype)]; - throw std::runtime_error(error.UTF8String); - } - } -} - -template -void add_bool_constraint_to_query(realm::Query &query, NSPredicateOperatorType operatorType, A lhs, B rhs) { - switch (operatorType) { - case NSEqualToPredicateOperatorType: - query.and_query(lhs == rhs); - break; - case NSNotEqualToPredicateOperatorType: - query.and_query(lhs != rhs); - break; - default: { - NSString *error = [NSString stringWithFormat:@"Operator '%@' not supported for bool type", operatorName(operatorType)]; - throw std::runtime_error(error.UTF8String); - } - } -} - -void add_string_constraint_to_query(realm::Query &query, - NSPredicateOperatorType operatorType, - NSComparisonPredicateOptions predicateOptions, - realm::Columns &&column, - NSString *value) { - bool caseSensitive = !(predicateOptions & NSCaseInsensitivePredicateOption); - bool diacriticInsensitive = (predicateOptions & NSDiacriticInsensitivePredicateOption); - RLMPrecondition(!diacriticInsensitive, @"Invalid predicate option", - @"NSDiacriticInsensitivePredicateOption not supported for string type"); - - realm::StringData sd = value.UTF8String; - switch (operatorType) { - case NSBeginsWithPredicateOperatorType: - query.and_query(column.begins_with(sd, caseSensitive)); - break; - case NSEndsWithPredicateOperatorType: - query.and_query(column.ends_with(sd, caseSensitive)); - break; - case NSContainsPredicateOperatorType: - query.and_query(column.contains(sd, caseSensitive)); - break; - case NSEqualToPredicateOperatorType: - query.and_query(column.equal(sd, caseSensitive)); - break; - case NSNotEqualToPredicateOperatorType: - query.and_query(column.not_equal(sd, caseSensitive)); - break; - default: { - NSString *error = [NSString stringWithFormat:@"Operator '%@' not supported for string type", operatorName(operatorType)]; - throw std::runtime_error(error.UTF8String); - } - } -} - -void add_string_constraint_to_query(realm::Query& query, - NSPredicateOperatorType operatorType, - NSComparisonPredicateOptions predicateOptions, - NSString *value, - realm::Columns&& column) { - switch (operatorType) { - case NSEqualToPredicateOperatorType: - case NSNotEqualToPredicateOperatorType: - add_string_constraint_to_query(query, operatorType, predicateOptions, std::move(column), value); - break; - default: { - NSString *error = [NSString stringWithFormat:@"Operator '%@' is not supported for string type with key path on right side of operator", - operatorName(operatorType)]; - throw std::runtime_error(error.UTF8String); - } - } -} - -template -static inline T *RLMDynamicCast(__unsafe_unretained id obj) { - if ([obj isKindOfClass:[T class]]) { - return obj; - } - return nil; -} - -id value_from_constant_expression_or_value(id value) { - if (NSExpression *exp = RLMDynamicCast(value)) { - RLMPrecondition(exp.expressionType == NSConstantValueExpressionType, - @"Invalid value", - @"Expressions within predicate aggregates must be constant values"); - return exp.constantValue; - } - return value; -} - -// iterate over an array of subpredicates, using @func to build a query from each -// one and ORing them together -template -void process_or_group(realm::Query &query, id array, Func&& func) { - RLMPrecondition([array conformsToProtocol:@protocol(NSFastEnumeration)], - @"Invalid value", @"IN clause requires an array of items"); - - query.group(); - - bool first = true; - for (id item in array) { - if (!first) { - query.Or(); - } - first = false; - - func(item); - } - - if (first) { - // Queries can't be empty, so if there's zero things in the OR group - // validation will fail. Work around this by adding an expression which - // will never find any rows in a table. - query.and_query(new FalseExpression); - } - - query.end_group(); -} - -template -struct ColumnOfTypeHelper { - static realm::Columns convert(TableGetter&& table, NSUInteger idx) - { - return table()->template column(idx); - } -}; - -template -struct ColumnOfTypeHelper { - static realm::Columns convert(TableGetter&& table, NSUInteger idx) - { - return table()->template column(idx); - } -}; - -template -struct ValueOfTypeHelper; - -template -struct ValueOfTypeHelper { - static realm::Int convert(TableGetter&&, id value) - { - return [value timeIntervalSince1970]; - } -}; - -template -struct ValueOfTypeHelper { - static bool convert(TableGetter&&, id value) - { - return [value boolValue]; - } -}; - -template -struct ValueOfTypeHelper { - static realm::Double convert(TableGetter&&, id value) - { - return [value doubleValue]; - } -}; - -template -struct ValueOfTypeHelper { - static realm::Float convert(TableGetter&&, id value) - { - return [value floatValue]; - } -}; - -template -struct ValueOfTypeHelper { - static realm::Int convert(TableGetter&&, id value) - { - return [value longLongValue]; - } -}; - -template -struct ValueOfTypeHelper { - static id convert(TableGetter&&, id value) - { - return value; - } -}; - -template -auto value_of_type_for_query(TableGetter&& tables, Value&& value) -{ - const bool isColumnIndex = std::is_same::type>::value; - using helper = std::conditional_t, - ValueOfTypeHelper>; - return helper::convert(std::forward(tables), std::forward(value)); -} - -template -void add_constraint_to_query(realm::Query &query, realm::PropertyType type, - NSPredicateOperatorType operatorType, - NSComparisonPredicateOptions predicateOptions, - std::vector linkColumns, T... values) -{ - static_assert(sizeof...(T) == 2, "add_constraint_to_query accepts only two values as arguments"); - - auto table = [&] { - realm::TableRef& tbl = query.get_table(); - for (NSUInteger col : linkColumns) { - tbl->link(col); // mutates m_link_chain on table - } - return tbl.get(); - }; - - switch (type) { - case realm::PropertyTypeBool: - add_bool_constraint_to_query(query, operatorType, value_of_type_for_query(table, values)...); - break; - case realm::PropertyTypeDate: - add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query(table, values)...); - break; - case realm::PropertyTypeDouble: - add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query(table, values)...); - break; - case realm::PropertyTypeFloat: - add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query(table, values)...); - break; - case realm::PropertyTypeInt: - add_numeric_constraint_to_query(query, type, operatorType, value_of_type_for_query(table, values)...); - break; - case realm::PropertyTypeString: - case realm::PropertyTypeData: - add_string_constraint_to_query(query, operatorType, predicateOptions, value_of_type_for_query(table, values)...); - break; - default: { - NSString *error = [NSString stringWithFormat:@"Object type %s not supported", realm::string_for_property_type(type)]; - throw std::runtime_error(error.UTF8String); - } - } -} - - -realm::Property *get_property_from_key_path(realm::Schema &schema, realm::ObjectSchema &desc, - NSString *keyPath, std::vector &indexes, bool isAny) -{ - realm::Property *prop = nullptr; - - NSString *prevPath = nil; - NSUInteger start = 0, length = keyPath.length, end = NSNotFound; - do { - end = [keyPath rangeOfString:@"." options:0 range:{start, length - start}].location; - NSString *path = [keyPath substringWithRange:{start, end == NSNotFound ? length - start : end - start}]; - if (prop) { - RLMPrecondition(prop->type == realm::PropertyTypeObject || prop->type == realm::PropertyTypeArray, - @"Invalid value", @"Property '%@' is not a link in object of type '%s'", prevPath, desc.name.c_str()); - indexes.push_back(prop->table_column); - prop = desc.property_for_name(path.UTF8String); - RLMPrecondition(prop, @"Invalid property name", - @"Property '%@' not found in object of type '%s'", path, desc.name.c_str()); - } - else { - prop = desc.property_for_name(path.UTF8String); - RLMPrecondition(prop, @"Invalid property name", - @"Property '%@' not found in object of type '%s'", path, desc.name.c_str()); - - if (isAny) { - RLMPrecondition(prop->type == realm::PropertyTypeArray, - @"Invalid predicate", - @"ANY modifier can only be used for RLMArray properties"); - } - else { - RLMPrecondition(prop->type != realm::PropertyTypeArray, - @"Invalid predicate", - @"RLMArray predicates must contain the ANY modifier"); - } - } - - if (prop->object_type.length()) { - desc = *schema.find(prop->object_type); - } - prevPath = path; - start = end + 1; - } while (end != NSNotFound); - - return prop; -} - -void update_query_with_value_expression(realm::Schema &schema, - realm::ObjectSchema &desc, - realm::Query &query, - NSString *keyPath, - id value, - NSComparisonPredicate *pred) -{ - bool isAny = pred.comparisonPredicateModifier == NSAnyPredicateModifier; - std::vector indexes; - realm::Property *prop = get_property_from_key_path(schema, desc, keyPath, indexes, isAny); - - NSUInteger index = prop->table_column; - - // check to see if this is a between query - if (pred.predicateOperatorType == NSBetweenPredicateOperatorType) { - throw std::runtime_error("BETWEEN queries not supported"); - } - - // turn IN into ored together == - if (pred.predicateOperatorType == NSInPredicateOperatorType) { - process_or_group(query, value, [&](id item) { - id normalized = value_from_constant_expression_or_value(item); - add_constraint_to_query(query, prop->type, NSEqualToPredicateOperatorType, - pred.options, indexes, index, normalized); - }); - return; - } - - if (pred.leftExpression.expressionType == NSKeyPathExpressionType) { - add_constraint_to_query(query, prop->type, pred.predicateOperatorType, - pred.options, indexes, index, value); - } else { - add_constraint_to_query(query, prop->type, pred.predicateOperatorType, - pred.options, indexes, value, index); - } -} - - -void update_query_with_predicate(NSPredicate *predicate, realm::Schema &schema, realm::ObjectSchema &objectSchema, realm::Query &query) -{ - // Compound predicates. - if ([predicate isMemberOfClass:[NSCompoundPredicate class]]) { - NSCompoundPredicate *comp = (NSCompoundPredicate *)predicate; - - switch ([comp compoundPredicateType]) { - case NSAndPredicateType: - if (comp.subpredicates.count) { - // Add all of the subpredicates. - query.group(); - for (NSPredicate *subp in comp.subpredicates) { - update_query_with_predicate(subp, schema, objectSchema, query); - } - query.end_group(); - } else { - // NSCompoundPredicate's documentation states that an AND predicate with no subpredicates evaluates to TRUE. - query.and_query(new TrueExpression); - } - break; - - case NSOrPredicateType: { - // Add all of the subpredicates with ors inbetween. - process_or_group(query, comp.subpredicates, [&](__unsafe_unretained NSPredicate *const subp) { - update_query_with_predicate(subp, schema, objectSchema, query); - }); - break; - } - - case NSNotPredicateType: - // Add the negated subpredicate - query.Not(); - update_query_with_predicate(comp.subpredicates.firstObject, schema, objectSchema, query); - break; - - default: - throw std::runtime_error("Invalid compound predicate type - Only support AND, OR and NOT predicate types"); - } - } - else if ([predicate isMemberOfClass:[NSComparisonPredicate class]]) { - NSComparisonPredicate *compp = (NSComparisonPredicate *)predicate; - - // check modifier - RLMPrecondition(compp.comparisonPredicateModifier != NSAllPredicateModifier, - @"Invalid predicate", @"ALL modifier not supported"); - - NSExpressionType exp1Type = compp.leftExpression.expressionType; - NSExpressionType exp2Type = compp.rightExpression.expressionType; - - if (compp.comparisonPredicateModifier == NSAnyPredicateModifier) { - // for ANY queries - RLMPrecondition(exp1Type == NSKeyPathExpressionType && exp2Type == NSConstantValueExpressionType, - @"Invalid predicate", - @"Predicate with ANY modifier must compare a KeyPath with RLMArray with a value"); - } - - if (compp.predicateOperatorType == NSBetweenPredicateOperatorType || compp.predicateOperatorType == NSInPredicateOperatorType) { - // Inserting an array via %@ gives NSConstantValueExpressionType, but - // including it directly gives NSAggregateExpressionType - if (exp1Type != NSKeyPathExpressionType || (exp2Type != NSAggregateExpressionType && exp2Type != NSConstantValueExpressionType)) { - NSString * error = [NSString stringWithFormat:@"Predicate with %s operator must compare a KeyPath with an aggregate with two values", compp.predicateOperatorType == NSBetweenPredicateOperatorType ? "BETWEEN" : "IN"]; - throw std::runtime_error(error.UTF8String); - } - exp2Type = NSConstantValueExpressionType; - } - - if (exp1Type == NSKeyPathExpressionType && exp2Type == NSConstantValueExpressionType) { - // comparing keypath to value - update_query_with_value_expression(schema, objectSchema, query, compp.leftExpression.keyPath, - compp.rightExpression.constantValue, compp); - } - else if (exp1Type == NSConstantValueExpressionType && exp2Type == NSKeyPathExpressionType) { - // comparing value to keypath - update_query_with_value_expression(schema, objectSchema, query, compp.rightExpression.keyPath, - compp.leftExpression.constantValue, compp); - } - //if (exp1Type == NSKeyPathExpressionType && exp2Type == NSKeyPathExpressionType) { - // both expression are KeyPaths - // update_query_with_column_expression(objectSchema, query, compp.leftExpression.keyPath, - // compp.rightExpression.keyPath, compp); - //} - else { - throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); - } - } - else if ([predicate isEqual:[NSPredicate predicateWithValue:YES]]) { - query.and_query(new TrueExpression); - } else if ([predicate isEqual:[NSPredicate predicateWithValue:NO]]) { - query.and_query(new FalseExpression); - } - else { - // invalid predicate type - throw std::runtime_error("Only support compound, comparison, and constant predicates"); - } -} - -void RLMUpdateQueryWithPredicate(realm::Query *query, NSPredicate *predicate, realm::Schema &schema, realm::ObjectSchema &objectSchema) -{ - // passing a nil predicate is a no-op - if (!predicate) { - return; - } - - RLMPrecondition([predicate isKindOfClass:NSPredicate.class], @"Invalid argument", @"predicate must be an NSPredicate object"); - - update_query_with_predicate(predicate, schema, objectSchema, *query); - - // Test the constructed query in core - std::string validateMessage = query->validate(); - RLMPrecondition(validateMessage.empty(), @"Invalid query", @"%.*s", (int)validateMessage.size(), validateMessage.c_str()); -} diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 3b504ee9..723332b1 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -24,6 +24,10 @@ #include #include +#include +#include "object_store.hpp" +#include "schema.hpp" + using namespace pegtl; namespace realm { @@ -291,9 +295,356 @@ Predicate parse(const std::string &query) state.predicate_stack.push_back(&out_predicate); pegtl::parse< must< pred, eof >, action >(query, source, state); + if (out_predicate.type == Predicate::Type::And && out_predicate.sub_predicates.size() == 1) { + return out_predicate.sub_predicates.back(); + } return out_predicate; } + + +// check a precondition and throw an exception if it is not met +// this should be used iff the condition being false indicates a bug in the caller +// of the function checking its preconditions +static void precondition(bool condition, const std::string message) { + if (__builtin_expect(condition, 1)) { + return; + } + throw std::runtime_error(message); +} + +// FIXME: TrueExpression and FalseExpression should be supported by core in some way +struct TrueExpression : realm::Expression { + size_t find_first(size_t start, size_t end) const override + { + if (start != end) + return start; + + return not_found; + } + void set_table() override {} + const Table* get_table() const override { return nullptr; } +}; + +struct FalseExpression : realm::Expression { + size_t find_first(size_t, size_t) const override { return not_found; } + void set_table() override {} + const Table* get_table() const override { return nullptr; } +}; + + +// add a clause for numeric constraints based on operator type +template +void add_numeric_constraint_to_query(Query& query, + PropertyType datatype, + Predicate::Operator operatorType, + A lhs, + B rhs) +{ + switch (operatorType) { + case Predicate::Operator::LessThan: + query.and_query(lhs < rhs); + break; + case Predicate::Operator::LessThanOrEqual: + query.and_query(lhs <= rhs); + break; + case Predicate::Operator::GreaterThan: + query.and_query(lhs > rhs); + break; + case Predicate::Operator::GreaterThanOrEqual: + query.and_query(lhs >= rhs); + break; + case Predicate::Operator::Equal: + query.and_query(lhs == rhs); + break; + case Predicate::Operator::NotEqual: + query.and_query(lhs != rhs); + break; + default: + throw std::runtime_error("Unsupported operator for numeric queries."); + } +} + +template +void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType, A lhs, B rhs) { + switch (operatorType) { + case Predicate::Operator::Equal: + query.and_query(lhs == rhs); + break; + case Predicate::Operator::NotEqual: + query.and_query(lhs != rhs); + break; + default: + throw std::runtime_error("Unsupported operator for numeric queries."); + } +} + +void add_string_constraint_to_query(Query &query, + Predicate::Operator op, + Columns &&column, + StringData value) { + bool case_sensitive = true; + StringData sd = value; + switch (op) { + case Predicate::Operator::BeginsWith: + query.and_query(column.begins_with(sd, case_sensitive)); + break; + case Predicate::Operator::EndsWith: + query.and_query(column.ends_with(sd, case_sensitive)); + break; + case Predicate::Operator::Contains: + query.and_query(column.contains(sd, case_sensitive)); + break; + case Predicate::Operator::Equal: + query.and_query(column.equal(sd, case_sensitive)); + break; + case Predicate::Operator::NotEqual: + query.and_query(column.not_equal(sd, case_sensitive)); + break; + default: + throw std::runtime_error("Unsupported operator for string queries."); + } +} + +void add_string_constraint_to_query(realm::Query& query, + Predicate::Operator op, + StringData value, + Columns &&column) { + bool case_sensitive = true; + StringData sd = value; + switch (op) { + case Predicate::Operator::Equal: + query.and_query(column.equal(sd, case_sensitive)); + break; + case Predicate::Operator::NotEqual: + query.and_query(column.not_equal(sd, case_sensitive)); + break; + default: + throw std::runtime_error("Substring comparison not supported for keypath substrings."); + } +} + +template +struct ColumnOfTypeHelper { + static Columns convert(TableGetter&& table, unsigned int idx) + { + return table()->template column(idx); + } +}; + +template +struct ColumnOfTypeHelper { + static Columns convert(TableGetter&& table, unsigned int idx) + { + return table()->template column(idx); + } +}; + +template +struct ValueOfTypeHelper; + +template +struct ValueOfTypeHelper { + static Int convert(TableGetter&&, const std::string & value) + { + assert(0); + } +}; + +template +struct ValueOfTypeHelper { + static bool convert(TableGetter&&, const std::string & value) + { + assert(0); + } +}; + +template +struct ValueOfTypeHelper { + static Double convert(TableGetter&&, const std::string & value) + { + return std::stod(value); + } +}; + +template +struct ValueOfTypeHelper { + static Float convert(TableGetter&&, const std::string & value) + { + return std::stof(value); + } +}; + +template +struct ValueOfTypeHelper { + static Int convert(TableGetter&&, const std::string & value) + { + return std::stoll(value); + } +}; + +template +struct ValueOfTypeHelper { + static std::string convert(TableGetter&&, const std::string & value) + { + return value; + } +}; + +template +auto value_of_type_for_query(TableGetter&& tables, Value&& value) +{ + const bool isColumnIndex = std::is_same::type>::value; + using helper = std::conditional_t, + ValueOfTypeHelper>; + return helper::convert(std::forward(tables), std::forward(value)); +} + +std::vector &split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; +} + +std::vector split(const std::string &s, char delim) { + std::vector elems; + split(s, delim, elems); + return elems; +} + +Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector &indexes) +{ + Property *prop = nullptr; + + auto paths = split(key_path, '.'); + for (size_t index = 0; index < paths.size(); index++) { + prop = desc.property_for_name(paths[index]); + precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); + precondition(index == paths.size() - 1 || prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); + + indexes.push_back(prop->table_column); + desc = *schema.find(prop->object_type); + } + return prop; +} + +template + void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop, + Predicate::Operator op, const std::vector& indexes, T... values) +{ + auto table = [&] { + TableRef& tbl = query.get_table(); + for (size_t col : indexes) { + tbl->link(col); // mutates m_link_chain on table + } + return tbl.get(); + }; + + auto type = prop->type; + switch (type) { + case PropertyTypeBool: + add_bool_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeDate: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeDouble: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeFloat: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeInt: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeString: + case PropertyTypeData: + add_string_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + break; + default: { + throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); + } + } +} + +void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +{ + std::vector indexes; + auto t0 = pred.sub_expressions[0].type, t1 = pred.sub_expressions[1].type; + if (t0 == Expression::Type::KeyPath && t1 != Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, pred.sub_expressions[0].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, pred.op, indexes, prop->table_column, pred.sub_expressions[1].s); + } + else if (t0 != Expression::Type::KeyPath && t1 == Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, pred.sub_expressions[1].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, pred.op, indexes, pred.sub_expressions[0].s, prop->table_column); + } + else { + throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); + } +} + +void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +{ + if (pred.negate) { + query.Not(); + } + + switch (pred.type) { + case Predicate::Type::And: + query.group(); + for (auto &sub : pred.sub_predicates) { + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.sub_predicates.size()) { + query.and_query(new TrueExpression); + } + query.end_group(); + break; + + case Predicate::Type::Or: + query.group(); + for (auto &sub : pred.sub_predicates) { + query.Or(); + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.sub_predicates.size()) { + query.and_query(new FalseExpression); + } + query.end_group(); + break; + + case Predicate::Type::Comparison: { + add_comparison_to_query(query, pred, schema, object_schema); + break; + } + case Predicate::Type::True: + query.and_query(new TrueExpression); + break; + + case Predicate::Type::False: + query.and_query(new FalseExpression); + break; + + default: + throw std::runtime_error("Invalid predicate type"); + break; + } +} + +void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType) { + update_query_with_predicate(query, predicate, schema, *schema.find(objectType)); + + // Test the constructed query in core + std::string validateMessage = query.validate(); + precondition(validateMessage.empty(), validateMessage.c_str()); +} + }} diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp index 149f12ff..99dda99d 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -23,6 +23,9 @@ #include namespace realm { + class Query; + class Schema; + namespace parser { struct Expression { @@ -74,6 +77,8 @@ namespace realm { }; Predicate parse(const std::string &query); + + void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType); } } diff --git a/src/object-store/parser/query.abnf b/src/object-store/parser/query.abnf index d70416d3..d315fd35 100644 --- a/src/object-store/parser/query.abnf +++ b/src/object-store/parser/query.abnf @@ -17,7 +17,7 @@ string = dq-string / sq-string sq-string = "'" *(%x20-ffffffff) "'" dq-string = DQUOTE *("\\" / %x20-21 / %x23-ffffffff) DQUOTE -num = float-num / int-num / hex-num -float-num = ["-"] (*DIGIT "." 1*DIGIT / "." 1*DIGIT) -int-num = ["-"] 1*DIGIT -hex-num = ["-"] ["0"] "x" 1*HEXDIG +num = ["-"] (float-num / int-num / hex-num) +float-num = (*DIGIT "." 1*DIGIT / "." 1*DIGIT) +int-num = 1*DIGIT +hex-num = ("0x" / "0X") 1*HEXDIG From d8c89c70985cad51da60dfc9e02b8aa2e0dc02ca Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 9 Nov 2015 22:08:06 -0800 Subject: [PATCH 08/47] support for truepredicate/falsepredicate, single quote strings - all tests now pass --- RealmJS.xcodeproj/project.pbxproj | 3 ++ src/object-store/parser/parser.cpp | 77 +++++++++++++++++++++++------- tests/RealmTests.js | 1 + 3 files changed, 64 insertions(+), 17 deletions(-) diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index c8c7d041..4b35b20a 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -945,6 +945,7 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/vendor/PEGTL", + "$(SRCROOT)/vendor", ); INFOPLIST_FILE = src/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -979,6 +980,7 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/vendor/PEGTL", + "$(SRCROOT)/vendor", ); INFOPLIST_FILE = src/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -1115,6 +1117,7 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/vendor/PEGTL", + "$(SRCROOT)/vendor", ); INFOPLIST_FILE = src/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 723332b1..770cab77 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -35,16 +35,16 @@ namespace parser { // strings struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; -struct escaped_char : one< '"', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; +struct escaped_char : one< '"', '\'', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; struct escaped : sor< escaped_char, unicode > {}; struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; struct char_ : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; -struct string_content : until< at< one< '"' > >, must< char_ > > {}; -struct string : seq< one< '"' >, must< string_content >, any > -{ - using content = string_content; -}; +struct dq_string_content : until< at< one< '"' > >, must< char_ > > {}; +struct dq_string : seq< one< '"' >, must< dq_string_content >, any > {}; + +struct sq_string_content : until< at< one< '\'' > >, must< char_ > > {}; +struct sq_string : seq< one< '\'' >, must< sq_string_content >, any > {}; // numbers struct minus : opt< one< '-' > > {}; @@ -63,17 +63,17 @@ struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; // expressions and operators -struct expr : sor< string, key_path, number > {}; +struct expr : sor< dq_string, sq_string, key_path, number > {}; struct eq : sor< two< '=' >, one< '=' > > {}; struct noteq : pegtl::string< '!', '=' > {}; struct lteq : pegtl::string< '<', '=' > {}; struct lt : one< '<' > {}; struct gteq : pegtl::string< '>', '=' > {}; -struct gt : one< '<' > {}; -struct begins : istring< 'b', 'e', 'g', 'i', 'n', 's', 'w', 'i', 't', 'h' > {}; -struct ends : istring< 'e', 'n', 'd', 's', 'w', 'i', 't', 'h' > {}; -struct contains : istring< 'c', 'o', 'n', 't', 'a', 'i', 'n', 's' > {}; +struct gt : one< '>' > {}; +struct begins : istring< 'b','e','g','i','n','s','w','i','t','h' > {}; +struct ends : istring< 'e','n','d','s','w','i','t','h' > {}; +struct contains : istring< 'c','o','n','t','a','i','n','s' > {}; struct oper : sor< eq, noteq, lteq, lt, gteq, gt, begins, ends, contains > {}; // predicates @@ -81,9 +81,11 @@ struct comparison_pred : seq< expr, pad< oper, blank >, expr > {}; struct pred; struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; +struct true_pred : sor< istring<'t','r','u','e','p','r','e','d','i','c','a','t','e'>, istring<'t','r','u','e'> > {}; +struct false_pred : sor< istring<'f','a','l','s','e','p','r','e','d','i','c','a','t','e'>, istring<'f','a','l','s','e'> > {}; struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< istring< 'N', 'O', 'T' >, plus< blank > > > {}; -struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, comparison_pred >, blank > > {}; +struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; struct and_op : sor< two< '&' >, istring< 'A', 'N', 'D' > > {}; struct or_op : sor< two< '|' >, istring< 'O', 'R' > > {}; @@ -186,7 +188,21 @@ template<> struct action< or_ext > } }; -template<> struct action< string > +template<> struct action< dq_string_content > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + + Expression exp; + exp.type = Expression::Type::String; + exp.s = in.string(); + + state.addExpression(exp); + } +}; + +template<> struct action< sq_string_content > { static void apply( const input & in, ParserState & state ) { @@ -228,6 +244,28 @@ template<> struct action< number > } }; +template<> struct action< true_pred > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + Predicate pred; + pred.type = Predicate::Type::True; + state.current()->sub_predicates.push_back(std::move(pred)); + } +}; + +template<> struct action< false_pred > +{ + static void apply( const input & in, ParserState & state ) + { + std::cout << in.string() << std::endl; + Predicate pred; + pred.type = Predicate::Type::False; + state.current()->sub_predicates.push_back(std::move(pred)); + } +}; + #define OPERATOR_ACTION(rule, oper) \ template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ @@ -522,13 +560,18 @@ Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const s auto paths = split(key_path, '.'); for (size_t index = 0; index < paths.size(); index++) { + if (prop) { + precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); + indexes.push_back(prop->table_column); + + } prop = desc.property_for_name(paths[index]); precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); - precondition(index == paths.size() - 1 || prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, - (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); - indexes.push_back(prop->table_column); - desc = *schema.find(prop->object_type); + if (prop->object_type.size()) { + desc = *schema.find(prop->object_type); + } } return prop; } diff --git a/tests/RealmTests.js b/tests/RealmTests.js index 5d93c97c..747f3969 100644 --- a/tests/RealmTests.js +++ b/tests/RealmTests.js @@ -268,6 +268,7 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(realm.objects('PersonObject', 'age < 12').length, 2); TestCase.assertEqual(realm.objects('PersonObject', 'age > 10 && age < 13').length, 3); TestCase.assertEqual(realm.objects('PersonObject', 'age >= 11 && age < 13').length, 3); + TestCase.assertEqual(realm.objects('PersonObject', 'name = "Tim"').length, 1); TestCase.assertEqual(realm.objects('PersonObject', 'name = \'Tim\'').length, 1); }, From c776290df874a054a44afa936dc84d9d4bcb3596 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 09:09:20 -0800 Subject: [PATCH 09/47] code cleanup, bugfixes --- src/object-store/parser/parser.cpp | 105 ++++++++++++++--------------- src/object-store/parser/parser.hpp | 44 ++++++------ 2 files changed, 72 insertions(+), 77 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 770cab77..ba4fd68f 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -100,25 +100,26 @@ struct pred : seq< and_pred, star< or_ext > > {}; struct ParserState { std::vector predicate_stack; - Predicate *current() { return predicate_stack.back(); } + Predicate ¤t() { + return *predicate_stack.back(); + } bool negate_next; void addExpression(Expression exp) { - if (current()->type == Predicate::Type::Comparison) { - current()->sub_expressions.emplace_back(std::move(exp)); + if (current().type == Predicate::Type::Comparison) { + current().cmpr.expr[1] = std::move(exp); predicate_stack.pop_back(); } else { - Predicate p; - p.type = Predicate::Type::Comparison; - p.sub_expressions.emplace_back(std::move(exp)); + Predicate p(Predicate::Type::Comparison); + p.cmpr.expr[0] = std::move(exp); if (negate_next) { p.negate = true; negate_next = false; } - current()->sub_predicates.emplace_back(std::move(p)); - predicate_stack.push_back(¤t()->sub_predicates.back()); + current().cpnd.sub_predicates.emplace_back(std::move(p)); + predicate_stack.push_back(¤t().cpnd.sub_predicates.back()); } } }; @@ -134,24 +135,24 @@ template<> struct action< and_ext > std::cout << "" << in.string() << std::endl; // if we were put into an OR group we need to rearrange - auto current = state.current(); - if (current->type == Predicate::Type::Or) { - auto &sub_preds = state.current()->sub_predicates; - auto second_last = sub_preds[sub_preds.size()-2]; + auto ¤t = state.current(); + if (current.type == Predicate::Type::Or) { + auto &sub_preds = state.current().cpnd.sub_predicates; + auto &second_last = sub_preds[sub_preds.size()-2]; if (second_last.type == Predicate::Type::And) { // if we are in an OR group and second to last predicate group is // an AND group then move the last predicate inside - second_last.sub_predicates.push_back(sub_preds.back()); + second_last.cpnd.sub_predicates.push_back(std::move(sub_preds.back())); sub_preds.pop_back(); } else { // otherwise combine last two into a new AND group - Predicate pred; - pred.type = Predicate::Type::And; - pred.sub_predicates = { second_last, sub_preds.back() }; + Predicate pred(Predicate::Type::And); + pred.cpnd.sub_predicates.emplace_back(std::move(second_last)); + pred.cpnd.sub_predicates.emplace_back(std::move(sub_preds.back())); sub_preds.pop_back(); sub_preds.pop_back(); - sub_preds.push_back(pred); + sub_preds.push_back(std::move(pred)); } } } @@ -164,27 +165,27 @@ template<> struct action< or_ext > std::cout << "" << in.string() << std::endl; // if already an OR group do nothing - auto current = state.current(); - if (current->type == Predicate::Type::Or) { + auto ¤t = state.current(); + if (current.type == Predicate::Type::Or) { return; } // if only two predicates in the group, then convert to OR - auto &sub_preds = state.current()->sub_predicates; + auto &sub_preds = state.current().cpnd.sub_predicates; if (sub_preds.size()) { - current->type = Predicate::Type::Or; + current.type = Predicate::Type::Or; return; } // split the current group into to groups which are ORed together - Predicate pred1, pred2; - pred1.type = Predicate::Type::And; - pred2.type = Predicate::Type::And; - pred1.sub_predicates.insert(sub_preds.begin(), sub_preds.back()); - pred2.sub_predicates.push_back(sub_preds.back()); + Predicate pred1(Predicate::Type::And), pred2(Predicate::Type::And); + pred1.cpnd.sub_predicates.insert(sub_preds.begin(), sub_preds.back()); + pred2.cpnd.sub_predicates.push_back(std::move(sub_preds.back())); - current->type = Predicate::Type::Or; - sub_preds = { pred1, pred2 }; + current.type = Predicate::Type::Or; + sub_preds.clear(); + sub_preds.emplace_back(std::move(pred1)); + sub_preds.emplace_back(std::move(pred2)); } }; @@ -249,9 +250,8 @@ template<> struct action< true_pred > static void apply( const input & in, ParserState & state ) { std::cout << in.string() << std::endl; - Predicate pred; - pred.type = Predicate::Type::True; - state.current()->sub_predicates.push_back(std::move(pred)); + Predicate pred(Predicate::Type::True); + state.current().cpnd.sub_predicates.push_back(std::move(pred)); } }; @@ -260,9 +260,8 @@ template<> struct action< false_pred > static void apply( const input & in, ParserState & state ) { std::cout << in.string() << std::endl; - Predicate pred; - pred.type = Predicate::Type::False; - state.current()->sub_predicates.push_back(std::move(pred)); + Predicate pred(Predicate::Type::False); + state.current().cpnd.sub_predicates.push_back(std::move(pred)); } }; @@ -270,7 +269,7 @@ template<> struct action< false_pred > template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ std::cout << in.string() << std::endl; \ - state.current()->op = oper; }}; + state.current().cmpr.op = oper; }}; OPERATOR_ACTION(eq, Predicate::Operator::Equal) OPERATOR_ACTION(noteq, Predicate::Operator::NotEqual) @@ -289,15 +288,14 @@ template<> struct action< one< '(' > > { std::cout << "" << std::endl; - Predicate group; - group.type = Predicate::Type::And; + Predicate group(Predicate::Type::And); if (state.negate_next) { group.negate = true; state.negate_next = false; } - state.current()->sub_predicates.emplace_back(std::move(group)); - state.predicate_stack.push_back(&state.current()->sub_predicates.back()); + state.current().cpnd.sub_predicates.emplace_back(std::move(group)); + state.predicate_stack.push_back(&state.current().cpnd.sub_predicates.back()); } }; @@ -326,21 +324,19 @@ Predicate parse(const std::string &query) analyze< pred >(); const std::string source = "user query"; - Predicate out_predicate; - out_predicate.type = Predicate::Type::And; + Predicate out_predicate(Predicate::Type::And); ParserState state; state.predicate_stack.push_back(&out_predicate); pegtl::parse< must< pred, eof >, action >(query, source, state); - if (out_predicate.type == Predicate::Type::And && out_predicate.sub_predicates.size() == 1) { - return out_predicate.sub_predicates.back(); + if (out_predicate.type == Predicate::Type::And && out_predicate.cpnd.sub_predicates.size() == 1) { + return std::move(out_predicate.cpnd.sub_predicates.back()); } - return out_predicate; + return std::move(out_predicate); } - // check a precondition and throw an exception if it is not met // this should be used iff the condition being false indicates a bug in the caller // of the function checking its preconditions @@ -618,14 +614,15 @@ template void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) { std::vector indexes; - auto t0 = pred.sub_expressions[0].type, t1 = pred.sub_expressions[1].type; + Predicate::Comparison &cmpr = pred.cmpr; + auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; if (t0 == Expression::Type::KeyPath && t1 != Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, pred.sub_expressions[0].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, pred.op, indexes, prop->table_column, pred.sub_expressions[1].s); + Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1].s); } else if (t0 != Expression::Type::KeyPath && t1 == Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, pred.sub_expressions[1].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, pred.op, indexes, pred.sub_expressions[0].s, prop->table_column); + Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0].s, prop->table_column); } else { throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); @@ -641,10 +638,10 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, switch (pred.type) { case Predicate::Type::And: query.group(); - for (auto &sub : pred.sub_predicates) { + for (auto &sub : pred.cpnd.sub_predicates) { update_query_with_predicate(query, sub, schema, object_schema); } - if (!pred.sub_predicates.size()) { + if (!pred.cpnd.sub_predicates.size()) { query.and_query(new TrueExpression); } query.end_group(); @@ -652,11 +649,11 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, case Predicate::Type::Or: query.group(); - for (auto &sub : pred.sub_predicates) { + for (auto &sub : pred.cpnd.sub_predicates) { query.Or(); update_query_with_predicate(query, sub, schema, object_schema); } - if (!pred.sub_predicates.size()) { + if (!pred.cpnd.sub_predicates.size()) { query.and_query(new FalseExpression); } query.end_group(); diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp index 99dda99d..c53887f5 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -29,33 +29,21 @@ namespace realm { namespace parser { struct Expression { - enum class Type - { - Number, - String, - KeyPath, - }; - Type type; - + enum class Type { Number, String, KeyPath } type; std::string s; }; struct Predicate { - enum class Type - { - None, + enum class Type { Comparison, Or, And, True, - False, - }; - Type type = Type::None; + False + } type = Type::And; - // for comparison - enum class Operator - { + enum class Operator { None, Equal, NotEqual, @@ -65,15 +53,25 @@ namespace realm { GreaterThanOrEqual, BeginsWith, EndsWith, - Contains, + Contains }; - Operator op = Operator::None; - std::vector sub_expressions; - // for compounds - std::vector sub_predicates; + struct Comparison { + Operator op = Operator::None; + Expression expr[2]; + ~Comparison() {} + }; - bool negate; + struct Compound { + std::vector sub_predicates; + }; + + Comparison cmpr; + Compound cpnd; + + bool negate = false; + + Predicate(Type t) : type(t) {} }; Predicate parse(const std::string &query); From 2f1c26ad734667b20f4511ea0f77104dcbe3a4f7 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 11:26:27 -0800 Subject: [PATCH 10/47] remove duplicate code, add argument expression type --- src/object-store/parser/parser.cpp | 82 ++++++++---------------------- src/object-store/parser/parser.hpp | 4 +- 2 files changed, 24 insertions(+), 62 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index ba4fd68f..be632cac 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -62,8 +62,12 @@ struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; // key paths struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; +// argument +struct argument_index : until< at< one< '}' > >, must< digit > > {}; +struct argument : seq< one< '{' >, must< argument_index >, any > {}; + // expressions and operators -struct expr : sor< dq_string, sq_string, key_path, number > {}; +struct expr : sor< dq_string, sq_string, key_path, number, argument > {}; struct eq : sor< two< '=' >, one< '=' > > {}; struct noteq : pegtl::string< '!', '=' > {}; @@ -103,9 +107,10 @@ struct ParserState Predicate ¤t() { return *predicate_stack.back(); } - bool negate_next; - void addExpression(Expression exp) + bool negate_next = false; + + void addExpression(Expression && exp) { if (current().type == Predicate::Type::Comparison) { current().cmpr.expr[1] = std::move(exp); @@ -189,69 +194,26 @@ template<> struct action< or_ext > } }; -template<> struct action< dq_string_content > -{ - static void apply( const input & in, ParserState & state ) - { - std::cout << in.string() << std::endl; - Expression exp; - exp.type = Expression::Type::String; - exp.s = in.string(); +#define EXPRESSION_ACTION(rule, type) \ +template<> struct action< rule > { \ + static void apply( const input & in, ParserState & state ) { \ + std::cout << in.string() << std::endl; \ + state.addExpression(Expression(type, in.string())); }}; - state.addExpression(exp); - } -}; +EXPRESSION_ACTION(dq_string_content, Expression::Type::String) +EXPRESSION_ACTION(sq_string_content, Expression::Type::String) +EXPRESSION_ACTION(key_path, Expression::Type::KeyPath) +EXPRESSION_ACTION(number, Expression::Type::Number) +EXPRESSION_ACTION(argument_index, Expression::Type::Argument) -template<> struct action< sq_string_content > -{ - static void apply( const input & in, ParserState & state ) - { - std::cout << in.string() << std::endl; - - Expression exp; - exp.type = Expression::Type::String; - exp.s = in.string(); - - state.addExpression(exp); - } -}; - -template<> struct action< key_path > -{ - static void apply( const input & in, ParserState & state ) - { - std::cout << in.string() << std::endl; - - Expression exp; - exp.type = Expression::Type::KeyPath; - exp.s = in.string(); - - state.addExpression(std::move(exp)); - } -}; - -template<> struct action< number > -{ - static void apply( const input & in, ParserState & state ) - { - std::cout << in.string() << std::endl; - - Expression exp; - exp.type = Expression::Type::Number; - exp.s = in.string(); - - state.addExpression(std::move(exp)); - } -}; template<> struct action< true_pred > { static void apply( const input & in, ParserState & state ) { std::cout << in.string() << std::endl; - Predicate pred(Predicate::Type::True); - state.current().cpnd.sub_predicates.push_back(std::move(pred)); + state.current().cpnd.sub_predicates.emplace_back(Predicate::Type::True); } }; @@ -260,11 +222,11 @@ template<> struct action< false_pred > static void apply( const input & in, ParserState & state ) { std::cout << in.string() << std::endl; - Predicate pred(Predicate::Type::False); - state.current().cpnd.sub_predicates.push_back(std::move(pred)); + state.current().cpnd.sub_predicates.emplace_back(Predicate::Type::False); } }; + #define OPERATOR_ACTION(rule, oper) \ template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ @@ -304,7 +266,6 @@ template<> struct action< group_pred > static void apply( const input & in, ParserState & state ) { std::cout << "" << std::endl; - state.predicate_stack.pop_back(); } }; @@ -314,7 +275,6 @@ template<> struct action< not_pre > static void apply( const input & in, ParserState & state ) { std::cout << "" << std::endl; - state.negate_next = true; } }; diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp index c53887f5..117ca833 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -29,8 +29,10 @@ namespace realm { namespace parser { struct Expression { - enum class Type { Number, String, KeyPath } type; + enum class Type { Number, String, KeyPath, Argument } type; std::string s; + Expression() {} + Expression(Type t, std::string s) : type(t), s(s) {} }; struct Predicate From e7e4b6715e4510a9c8e609bddac7a26e52dbe3a9 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 12:51:21 -0800 Subject: [PATCH 11/47] move query building to a separate file --- RealmJS.xcodeproj/project.pbxproj | 10 +- src/RJSResults.mm | 3 +- src/object-store/parser/parser.cpp | 353 -------------------- src/object-store/parser/parser.hpp | 9 +- src/object-store/parser/query_builder.cpp | 381 ++++++++++++++++++++++ src/object-store/parser/query_builder.hpp | 34 ++ 6 files changed, 429 insertions(+), 361 deletions(-) create mode 100644 src/object-store/parser/query_builder.cpp create mode 100644 src/object-store/parser/query_builder.hpp diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index 4b35b20a..1cb71a9c 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -53,6 +53,8 @@ 029445941BDEDF40006D1617 /* transact_log_handler.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029445921BDEDF40006D1617 /* transact_log_handler.hpp */; }; 029445971BDEDF46006D1617 /* external_commit_helper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 029445951BDEDF46006D1617 /* external_commit_helper.cpp */; }; 029445981BDEDF46006D1617 /* external_commit_helper.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029445961BDEDF46006D1617 /* external_commit_helper.hpp */; }; + 0294465E1BF27DE6006D1617 /* query_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0294465C1BF27DE6006D1617 /* query_builder.cpp */; }; + 0294465F1BF27DE6006D1617 /* query_builder.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0294465D1BF27DE6006D1617 /* query_builder.hpp */; }; 02B29A311B7CF86D008A7E6B /* RealmJS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; }; 02B58CCE1AE99D4D009B348C /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; 02C0864E1BCDB27000942F9C /* list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02C0864C1BCDB27000942F9C /* list.cpp */; }; @@ -208,7 +210,9 @@ 029445921BDEDF40006D1617 /* transact_log_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = transact_log_handler.hpp; path = "src/object-store/impl/transact_log_handler.hpp"; sourceTree = ""; }; 029445951BDEDF46006D1617 /* external_commit_helper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = external_commit_helper.cpp; path = "src/object-store/impl/apple/external_commit_helper.cpp"; sourceTree = ""; }; 029445961BDEDF46006D1617 /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = external_commit_helper.hpp; path = "src/object-store/impl/apple/external_commit_helper.hpp"; sourceTree = ""; }; - 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GCDWebServer.xcodeproj; path = GCDWebServer/GCDWebServer.xcodeproj; sourceTree = ""; }; + 0294465C1BF27DE6006D1617 /* query_builder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = query_builder.cpp; path = "src/object-store/parser/query_builder.cpp"; sourceTree = ""; }; + 0294465D1BF27DE6006D1617 /* query_builder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = query_builder.hpp; path = "src/object-store/parser/query_builder.hpp"; sourceTree = ""; }; + 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GCDWebServer.xcodeproj; path = vendor/GCDWebServer/GCDWebServer.xcodeproj; sourceTree = ""; }; 02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 02B29A161B7CF7C9008A7E6B /* RealmReact.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmReact.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CB11AE99CEC009B348C /* RealmJS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmJS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -285,6 +289,8 @@ 029445961BDEDF46006D1617 /* external_commit_helper.hpp */, 02786E311BF1065000937061 /* parser.cpp */, 02786E321BF1065000937061 /* parser.hpp */, + 0294465C1BF27DE6006D1617 /* query_builder.cpp */, + 0294465D1BF27DE6006D1617 /* query_builder.hpp */, 0270BC3E1B7CFC0D00010E03 /* RealmJS.h */, 0270BC3F1B7CFC0D00010E03 /* RealmJS.mm */, 02258FB11BC6E2D00075F13A /* RealmRPC.hpp */, @@ -412,6 +418,7 @@ 0270BC4C1B7CFC0D00010E03 /* RealmJS.h in Headers */, 02258FB31BC6E2D00075F13A /* RealmRPC.hpp in Headers */, 0270BC4F1B7CFC0D00010E03 /* RJSList.hpp in Headers */, + 0294465F1BF27DE6006D1617 /* query_builder.hpp in Headers */, 0270BC541B7CFC0D00010E03 /* RJSResults.hpp in Headers */, 02D0F23B1BF6C95200B4FC45 /* binding_context.hpp in Headers */, 0270BC581B7CFC0D00010E03 /* RJSUtil.hpp in Headers */, @@ -673,6 +680,7 @@ 0270BC551B7CFC0D00010E03 /* RJSResults.mm in Sources */, 02C0864E1BCDB27000942F9C /* list.cpp in Sources */, 0270BC6B1B7CFC1C00010E03 /* object_store.cpp in Sources */, + 0294465E1BF27DE6006D1617 /* query_builder.cpp in Sources */, 0270BC701B7CFC1C00010E03 /* shared_realm.cpp in Sources */, F6BB7DF11BF681BC00D0A69E /* base64.cpp in Sources */, 0270BC4E1B7CFC0D00010E03 /* RJSList.cpp in Sources */, diff --git a/src/RJSResults.mm b/src/RJSResults.mm index 9f19249e..b1c287d9 100644 --- a/src/RJSResults.mm +++ b/src/RJSResults.mm @@ -7,6 +7,7 @@ #import "object_accessor.hpp" #import "results.hpp" #import "parser.hpp" +#import "query_builder.hpp" using namespace realm; @@ -113,7 +114,7 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl throw std::runtime_error("Object type '" + className + "' not present in Realm."); } parser::Predicate predicate = parser::parse(queryString); - parser::apply_predicate(query, predicate, schema, object_schema->name); + query_builder::apply_predicate(query, predicate, schema, object_schema->name); return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query))); } diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index be632cac..03dbd39d 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -24,10 +24,6 @@ #include #include -#include -#include "object_store.hpp" -#include "schema.hpp" - using namespace pegtl; namespace realm { @@ -296,355 +292,6 @@ Predicate parse(const std::string &query) return std::move(out_predicate); } - -// check a precondition and throw an exception if it is not met -// this should be used iff the condition being false indicates a bug in the caller -// of the function checking its preconditions -static void precondition(bool condition, const std::string message) { - if (__builtin_expect(condition, 1)) { - return; - } - throw std::runtime_error(message); -} - -// FIXME: TrueExpression and FalseExpression should be supported by core in some way -struct TrueExpression : realm::Expression { - size_t find_first(size_t start, size_t end) const override - { - if (start != end) - return start; - - return not_found; - } - void set_table() override {} - const Table* get_table() const override { return nullptr; } -}; - -struct FalseExpression : realm::Expression { - size_t find_first(size_t, size_t) const override { return not_found; } - void set_table() override {} - const Table* get_table() const override { return nullptr; } -}; - - -// add a clause for numeric constraints based on operator type -template -void add_numeric_constraint_to_query(Query& query, - PropertyType datatype, - Predicate::Operator operatorType, - A lhs, - B rhs) -{ - switch (operatorType) { - case Predicate::Operator::LessThan: - query.and_query(lhs < rhs); - break; - case Predicate::Operator::LessThanOrEqual: - query.and_query(lhs <= rhs); - break; - case Predicate::Operator::GreaterThan: - query.and_query(lhs > rhs); - break; - case Predicate::Operator::GreaterThanOrEqual: - query.and_query(lhs >= rhs); - break; - case Predicate::Operator::Equal: - query.and_query(lhs == rhs); - break; - case Predicate::Operator::NotEqual: - query.and_query(lhs != rhs); - break; - default: - throw std::runtime_error("Unsupported operator for numeric queries."); - } -} - -template -void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType, A lhs, B rhs) { - switch (operatorType) { - case Predicate::Operator::Equal: - query.and_query(lhs == rhs); - break; - case Predicate::Operator::NotEqual: - query.and_query(lhs != rhs); - break; - default: - throw std::runtime_error("Unsupported operator for numeric queries."); - } -} - -void add_string_constraint_to_query(Query &query, - Predicate::Operator op, - Columns &&column, - StringData value) { - bool case_sensitive = true; - StringData sd = value; - switch (op) { - case Predicate::Operator::BeginsWith: - query.and_query(column.begins_with(sd, case_sensitive)); - break; - case Predicate::Operator::EndsWith: - query.and_query(column.ends_with(sd, case_sensitive)); - break; - case Predicate::Operator::Contains: - query.and_query(column.contains(sd, case_sensitive)); - break; - case Predicate::Operator::Equal: - query.and_query(column.equal(sd, case_sensitive)); - break; - case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(sd, case_sensitive)); - break; - default: - throw std::runtime_error("Unsupported operator for string queries."); - } -} - -void add_string_constraint_to_query(realm::Query& query, - Predicate::Operator op, - StringData value, - Columns &&column) { - bool case_sensitive = true; - StringData sd = value; - switch (op) { - case Predicate::Operator::Equal: - query.and_query(column.equal(sd, case_sensitive)); - break; - case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(sd, case_sensitive)); - break; - default: - throw std::runtime_error("Substring comparison not supported for keypath substrings."); - } -} - -template -struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, unsigned int idx) - { - return table()->template column(idx); - } -}; - -template -struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, unsigned int idx) - { - return table()->template column(idx); - } -}; - -template -struct ValueOfTypeHelper; - -template -struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const std::string & value) - { - assert(0); - } -}; - -template -struct ValueOfTypeHelper { - static bool convert(TableGetter&&, const std::string & value) - { - assert(0); - } -}; - -template -struct ValueOfTypeHelper { - static Double convert(TableGetter&&, const std::string & value) - { - return std::stod(value); - } -}; - -template -struct ValueOfTypeHelper { - static Float convert(TableGetter&&, const std::string & value) - { - return std::stof(value); - } -}; - -template -struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const std::string & value) - { - return std::stoll(value); - } -}; - -template -struct ValueOfTypeHelper { - static std::string convert(TableGetter&&, const std::string & value) - { - return value; - } -}; - -template -auto value_of_type_for_query(TableGetter&& tables, Value&& value) -{ - const bool isColumnIndex = std::is_same::type>::value; - using helper = std::conditional_t, - ValueOfTypeHelper>; - return helper::convert(std::forward(tables), std::forward(value)); -} - -std::vector &split(const std::string &s, char delim, std::vector &elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; -} - -std::vector split(const std::string &s, char delim) { - std::vector elems; - split(s, delim, elems); - return elems; -} - -Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector &indexes) -{ - Property *prop = nullptr; - - auto paths = split(key_path, '.'); - for (size_t index = 0; index < paths.size(); index++) { - if (prop) { - precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, - (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); - indexes.push_back(prop->table_column); - - } - prop = desc.property_for_name(paths[index]); - precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); - - if (prop->object_type.size()) { - desc = *schema.find(prop->object_type); - } - } - return prop; -} - -template - void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop, - Predicate::Operator op, const std::vector& indexes, T... values) -{ - auto table = [&] { - TableRef& tbl = query.get_table(); - for (size_t col : indexes) { - tbl->link(col); // mutates m_link_chain on table - } - return tbl.get(); - }; - - auto type = prop->type; - switch (type) { - case PropertyTypeBool: - add_bool_constraint_to_query(query, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeDate: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeDouble: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeFloat: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeInt: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); - break; - case PropertyTypeString: - case PropertyTypeData: - add_string_constraint_to_query(query, op, value_of_type_for_query(table, values)...); - break; - default: { - throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); - } - } -} - -void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) -{ - std::vector indexes; - Predicate::Comparison &cmpr = pred.cmpr; - auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; - if (t0 == Expression::Type::KeyPath && t1 != Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1].s); - } - else if (t0 != Expression::Type::KeyPath && t1 == Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0].s, prop->table_column); - } - else { - throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); - } -} - -void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) -{ - if (pred.negate) { - query.Not(); - } - - switch (pred.type) { - case Predicate::Type::And: - query.group(); - for (auto &sub : pred.cpnd.sub_predicates) { - update_query_with_predicate(query, sub, schema, object_schema); - } - if (!pred.cpnd.sub_predicates.size()) { - query.and_query(new TrueExpression); - } - query.end_group(); - break; - - case Predicate::Type::Or: - query.group(); - for (auto &sub : pred.cpnd.sub_predicates) { - query.Or(); - update_query_with_predicate(query, sub, schema, object_schema); - } - if (!pred.cpnd.sub_predicates.size()) { - query.and_query(new FalseExpression); - } - query.end_group(); - break; - - case Predicate::Type::Comparison: { - add_comparison_to_query(query, pred, schema, object_schema); - break; - } - case Predicate::Type::True: - query.and_query(new TrueExpression); - break; - - case Predicate::Type::False: - query.and_query(new FalseExpression); - break; - - default: - throw std::runtime_error("Invalid predicate type"); - break; - } -} - -void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType) { - update_query_with_predicate(query, predicate, schema, *schema.find(objectType)); - - // Test the constructed query in core - std::string validateMessage = query.validate(); - precondition(validateMessage.empty(), validateMessage.c_str()); -} - }} diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp index 117ca833..568c446b 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -16,14 +16,13 @@ // //////////////////////////////////////////////////////////////////////////// -#ifndef REALM_EXTERNAL_COMMIT_HELPER_HPP -#define REALM_EXTERNAL_COMMIT_HELPER_HPP +#ifndef REALM_PARSER_HPP +#define REALM_PARSER_HPP #include #include namespace realm { - class Query; class Schema; namespace parser { @@ -77,9 +76,7 @@ namespace realm { }; Predicate parse(const std::string &query); - - void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType); } } -#endif // REALM_EXTERNAL_COMMIT_HELPER_HPP +#endif // REALM_PARSER_HPP diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp new file mode 100644 index 00000000..004b821e --- /dev/null +++ b/src/object-store/parser/query_builder.cpp @@ -0,0 +1,381 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#include "query_builder.hpp" +#include "parser.hpp" + +#include +#include "object_store.hpp" +#include "schema.hpp" + +#include + +namespace realm { +namespace query_builder { +using namespace parser; + +// check a precondition and throw an exception if it is not met +// this should be used iff the condition being false indicates a bug in the caller +// of the function checking its preconditions +static void precondition(bool condition, const std::string message) { + if (__builtin_expect(condition, 1)) { + return; + } + throw std::runtime_error(message); +} + +// FIXME: TrueExpression and FalseExpression should be supported by core in some way +struct TrueExpression : realm::Expression { + size_t find_first(size_t start, size_t end) const override + { + if (start != end) + return start; + + return not_found; + } + void set_table() override {} + const Table* get_table() const override { return nullptr; } +}; + +struct FalseExpression : realm::Expression { + size_t find_first(size_t, size_t) const override { return not_found; } + void set_table() override {} + const Table* get_table() const override { return nullptr; } +}; + + +// add a clause for numeric constraints based on operator type +template +void add_numeric_constraint_to_query(Query& query, + PropertyType datatype, + Predicate::Operator operatorType, + A lhs, + B rhs) +{ + switch (operatorType) { + case Predicate::Operator::LessThan: + query.and_query(lhs < rhs); + break; + case Predicate::Operator::LessThanOrEqual: + query.and_query(lhs <= rhs); + break; + case Predicate::Operator::GreaterThan: + query.and_query(lhs > rhs); + break; + case Predicate::Operator::GreaterThanOrEqual: + query.and_query(lhs >= rhs); + break; + case Predicate::Operator::Equal: + query.and_query(lhs == rhs); + break; + case Predicate::Operator::NotEqual: + query.and_query(lhs != rhs); + break; + default: + throw std::runtime_error("Unsupported operator for numeric queries."); + } +} + +template +void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType, A lhs, B rhs) { + switch (operatorType) { + case Predicate::Operator::Equal: + query.and_query(lhs == rhs); + break; + case Predicate::Operator::NotEqual: + query.and_query(lhs != rhs); + break; + default: + throw std::runtime_error("Unsupported operator for numeric queries."); + } +} + +void add_string_constraint_to_query(Query &query, + Predicate::Operator op, + Columns &&column, + StringData value) { + bool case_sensitive = true; + StringData sd = value; + switch (op) { + case Predicate::Operator::BeginsWith: + query.and_query(column.begins_with(sd, case_sensitive)); + break; + case Predicate::Operator::EndsWith: + query.and_query(column.ends_with(sd, case_sensitive)); + break; + case Predicate::Operator::Contains: + query.and_query(column.contains(sd, case_sensitive)); + break; + case Predicate::Operator::Equal: + query.and_query(column.equal(sd, case_sensitive)); + break; + case Predicate::Operator::NotEqual: + query.and_query(column.not_equal(sd, case_sensitive)); + break; + default: + throw std::runtime_error("Unsupported operator for string queries."); + } +} + +void add_string_constraint_to_query(realm::Query& query, + Predicate::Operator op, + StringData value, + Columns &&column) { + bool case_sensitive = true; + StringData sd = value; + switch (op) { + case Predicate::Operator::Equal: + query.and_query(column.equal(sd, case_sensitive)); + break; + case Predicate::Operator::NotEqual: + query.and_query(column.not_equal(sd, case_sensitive)); + break; + default: + throw std::runtime_error("Substring comparison not supported for keypath substrings."); + } +} + +template +struct ColumnOfTypeHelper { + static Columns convert(TableGetter&& table, unsigned int idx) + { + return table()->template column(idx); + } +}; + +template +struct ColumnOfTypeHelper { + static Columns convert(TableGetter&& table, unsigned int idx) + { + return table()->template column(idx); + } +}; + +template +struct ValueOfTypeHelper; + +template +struct ValueOfTypeHelper { + static Int convert(TableGetter&&, const std::string & value) + { + assert(0); + } +}; + +template +struct ValueOfTypeHelper { + static bool convert(TableGetter&&, const std::string & value) + { + assert(0); + } +}; + +template +struct ValueOfTypeHelper { + static Double convert(TableGetter&&, const std::string & value) + { + return std::stod(value); + } +}; + +template +struct ValueOfTypeHelper { + static Float convert(TableGetter&&, const std::string & value) + { + return std::stof(value); + } +}; + +template +struct ValueOfTypeHelper { + static Int convert(TableGetter&&, const std::string & value) + { + return std::stoll(value); + } +}; + +template +struct ValueOfTypeHelper { + static std::string convert(TableGetter&&, const std::string & value) + { + return value; + } +}; + +template +auto value_of_type_for_query(TableGetter&& tables, Value&& value) +{ + const bool isColumnIndex = std::is_same::type>::value; + using helper = std::conditional_t, + ValueOfTypeHelper>; + return helper::convert(std::forward(tables), std::forward(value)); +} + +std::vector &split(const std::string &s, char delim, std::vector &elems) { + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; +} + +std::vector split(const std::string &s, char delim) { + std::vector elems; + split(s, delim, elems); + return elems; +} + +Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector &indexes) +{ + Property *prop = nullptr; + + auto paths = split(key_path, '.'); + for (size_t index = 0; index < paths.size(); index++) { + if (prop) { + precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); + indexes.push_back(prop->table_column); + + } + prop = desc.property_for_name(paths[index]); + precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); + + if (prop->object_type.size()) { + desc = *schema.find(prop->object_type); + } + } + return prop; +} + +template +void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop, + Predicate::Operator op, const std::vector& indexes, T... values) +{ + auto table = [&] { + TableRef& tbl = query.get_table(); + for (size_t col : indexes) { + tbl->link(col); // mutates m_link_chain on table + } + return tbl.get(); + }; + + auto type = prop->type; + switch (type) { + case PropertyTypeBool: + add_bool_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeDate: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeDouble: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeFloat: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeInt: + add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + break; + case PropertyTypeString: + case PropertyTypeData: + add_string_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + break; + default: { + throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); + } + } +} + +void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +{ + std::vector indexes; + Predicate::Comparison &cmpr = pred.cmpr; + auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; + if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1].s); + } + else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) { + Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0].s, prop->table_column); + } + else { + throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); + } +} + +void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +{ + if (pred.negate) { + query.Not(); + } + + switch (pred.type) { + case Predicate::Type::And: + query.group(); + for (auto &sub : pred.cpnd.sub_predicates) { + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.cpnd.sub_predicates.size()) { + query.and_query(new TrueExpression); + } + query.end_group(); + break; + + case Predicate::Type::Or: + query.group(); + for (auto &sub : pred.cpnd.sub_predicates) { + query.Or(); + update_query_with_predicate(query, sub, schema, object_schema); + } + if (!pred.cpnd.sub_predicates.size()) { + query.and_query(new FalseExpression); + } + query.end_group(); + break; + + case Predicate::Type::Comparison: { + add_comparison_to_query(query, pred, schema, object_schema); + break; + } + case Predicate::Type::True: + query.and_query(new TrueExpression); + break; + + case Predicate::Type::False: + query.and_query(new FalseExpression); + break; + + default: + throw std::runtime_error("Invalid predicate type"); + break; + } +} + +void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType) +{ + update_query_with_predicate(query, predicate, schema, *schema.find(objectType)); + + // Test the constructed query in core + std::string validateMessage = query.validate(); + precondition(validateMessage.empty(), validateMessage.c_str()); +} + +}} diff --git a/src/object-store/parser/query_builder.hpp b/src/object-store/parser/query_builder.hpp new file mode 100644 index 00000000..8a408ec8 --- /dev/null +++ b/src/object-store/parser/query_builder.hpp @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2015 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_QUERY_BUILDER_HPP +#define REALM_QUERY_BUILDER_HPP + +#include +#include "parser.hpp" + +namespace realm { + class Query; + class Schema; + + namespace query_builder { + void apply_predicate(Query &query, parser::Predicate &predicate, Schema &schema, std::string objectType); + } +} + +#endif // REALM_QUERY_BUILDER_HPP From bb16ffa7fe89002854530550506a465b5508a2d0 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 13:17:27 -0800 Subject: [PATCH 12/47] add required padding around string operators, use pegtl_istring_t --- src/object-store/parser/parser.cpp | 30 ++++++++++++++++++------------ src/object-store/parser/query.abnf | 23 ----------------------- 2 files changed, 18 insertions(+), 35 deletions(-) delete mode 100644 src/object-store/parser/query.abnf diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 03dbd39d..699739db 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -23,6 +23,7 @@ #include #include #include +#include using namespace pegtl; @@ -71,27 +72,32 @@ struct lteq : pegtl::string< '<', '=' > {}; struct lt : one< '<' > {}; struct gteq : pegtl::string< '>', '=' > {}; struct gt : one< '>' > {}; -struct begins : istring< 'b','e','g','i','n','s','w','i','t','h' > {}; -struct ends : istring< 'e','n','d','s','w','i','t','h' > {}; -struct contains : istring< 'c','o','n','t','a','i','n','s' > {}; -struct oper : sor< eq, noteq, lteq, lt, gteq, gt, begins, ends, contains > {}; +struct contains : pegtl_istring_t("contains") {}; +struct begins : pegtl_istring_t("beginswith") {}; +struct ends : pegtl_istring_t("endswith") {}; + +template +struct pad_plus : seq< plus< B >, A, plus< B > > {}; + +struct padded_oper : pad_plus< sor< contains, begins, ends >, blank > {}; +struct symbolic_oper : pad< sor< eq, noteq, lteq, lt, gteq, gt >, blank > {}; // predicates -struct comparison_pred : seq< expr, pad< oper, blank >, expr > {}; +struct comparison_pred : seq< expr, sor< padded_oper, symbolic_oper >, expr > {}; struct pred; struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; -struct true_pred : sor< istring<'t','r','u','e','p','r','e','d','i','c','a','t','e'>, istring<'t','r','u','e'> > {}; -struct false_pred : sor< istring<'f','a','l','s','e','p','r','e','d','i','c','a','t','e'>, istring<'f','a','l','s','e'> > {}; +struct true_pred : sor< pegtl_istring_t("truepredicate"), pegtl_istring_t("true") > {}; +struct false_pred : sor< pegtl_istring_t("falsepredicate"), pegtl_istring_t("false") > {}; -struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< istring< 'N', 'O', 'T' >, plus< blank > > > {}; +struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< pegtl_istring_t("not"), plus< blank > > > {}; struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; -struct and_op : sor< two< '&' >, istring< 'A', 'N', 'D' > > {}; -struct or_op : sor< two< '|' >, istring< 'O', 'R' > > {}; +struct and_op : sor< pad< two< '&' >, blank >, pad_plus< pegtl_istring_t("and"), blank > > {}; +struct or_op : sor< pad< two< '|' >, blank> , pad_plus< pegtl_istring_t("or"), blank > > {}; -struct or_ext : seq< pad< or_op, blank >, pred > {}; -struct and_ext : seq< pad< and_op, blank >, pred > {}; +struct or_ext : if_must< or_op, pred > {}; +struct and_ext : if_must< and_op, pred > {}; struct and_pred : seq< atom_pred, star< and_ext > > {}; struct pred : seq< and_pred, star< or_ext > > {}; diff --git a/src/object-store/parser/query.abnf b/src/object-store/parser/query.abnf deleted file mode 100644 index d315fd35..00000000 --- a/src/object-store/parser/query.abnf +++ /dev/null @@ -1,23 +0,0 @@ - - -pred = expr 1*WSP oper 1*WSP expr -;pred =/ "(" *WSP pred *WSP ")" -;pred =/ ("NOT" / "!") 1*WSP pred -;pred =/ pred 1*WSP ("OR" / "||") !*WSP pred -;pred =/ pred 1*WSP ("AND" / "&&") 1*WSP pred - -oper = "=" / "==" / "!=" / "<" / "<=" / ">" / ">=" - -expr = string / num / key-path - -key-path = key *("." key) -key = 1*(ALPHA / "_") - -string = dq-string / sq-string -sq-string = "'" *(%x20-ffffffff) "'" -dq-string = DQUOTE *("\\" / %x20-21 / %x23-ffffffff) DQUOTE - -num = ["-"] (float-num / int-num / hex-num) -float-num = (*DIGIT "." 1*DIGIT / "." 1*DIGIT) -int-num = 1*DIGIT -hex-num = ("0x" / "0X") 1*HEXDIG From 3b698400b784ee62892d20b186646df493344a5d Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 14:24:30 -0800 Subject: [PATCH 13/47] support for querying boolean properties --- src/object-store/parser/parser.cpp | 14 ++++++---- src/object-store/parser/parser.hpp | 2 +- src/object-store/parser/query_builder.cpp | 34 +++++++++++------------ tests/RealmTests.js | 8 ++++-- tests/ResultsTests.js | 4 +-- tests/schemas.js | 5 ++-- 6 files changed, 36 insertions(+), 31 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 699739db..2479b287 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -23,7 +23,6 @@ #include #include #include -#include using namespace pegtl; @@ -56,6 +55,9 @@ struct int_num : plus< digit > {}; struct number : seq< minus, sor< float_num, hex_num, int_num > > {}; +struct true_value : pegtl_istring_t("true") {}; +struct false_value : pegtl_istring_t("false") {}; + // key paths struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; @@ -64,7 +66,7 @@ struct argument_index : until< at< one< '}' > >, must< digit > > {}; struct argument : seq< one< '{' >, must< argument_index >, any > {}; // expressions and operators -struct expr : sor< dq_string, sq_string, key_path, number, argument > {}; +struct expr : sor< dq_string, sq_string, number, argument, true_value, false_value, key_path > {}; struct eq : sor< two< '=' >, one< '=' > > {}; struct noteq : pegtl::string< '!', '=' > {}; @@ -87,8 +89,8 @@ struct comparison_pred : seq< expr, sor< padded_oper, symbolic_oper >, expr > {} struct pred; struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; -struct true_pred : sor< pegtl_istring_t("truepredicate"), pegtl_istring_t("true") > {}; -struct false_pred : sor< pegtl_istring_t("falsepredicate"), pegtl_istring_t("false") > {}; +struct true_pred : pegtl_istring_t("truepredicate") {}; +struct false_pred : pegtl_istring_t("falsepredicate") {}; struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< pegtl_istring_t("not"), plus< blank > > > {}; struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; @@ -207,6 +209,8 @@ EXPRESSION_ACTION(dq_string_content, Expression::Type::String) EXPRESSION_ACTION(sq_string_content, Expression::Type::String) EXPRESSION_ACTION(key_path, Expression::Type::KeyPath) EXPRESSION_ACTION(number, Expression::Type::Number) +EXPRESSION_ACTION(true_value, Expression::Type::True) +EXPRESSION_ACTION(false_value, Expression::Type::False) EXPRESSION_ACTION(argument_index, Expression::Type::Argument) @@ -228,7 +232,6 @@ template<> struct action< false_pred > } }; - #define OPERATOR_ACTION(rule, oper) \ template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ @@ -245,7 +248,6 @@ OPERATOR_ACTION(begins, Predicate::Operator::BeginsWith) OPERATOR_ACTION(ends, Predicate::Operator::EndsWith) OPERATOR_ACTION(contains, Predicate::Operator::Contains) - template<> struct action< one< '(' > > { static void apply( const input & in, ParserState & state ) diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp index 568c446b..4792285a 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -28,7 +28,7 @@ namespace realm { namespace parser { struct Expression { - enum class Type { Number, String, KeyPath, Argument } type; + enum class Type { Number, String, KeyPath, Argument, True, False } type; std::string s; Expression() {} Expression(Type t, std::string s) : type(t), s(s) {} diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp index 004b821e..8f4aeeee 100644 --- a/src/object-store/parser/query_builder.cpp +++ b/src/object-store/parser/query_builder.cpp @@ -152,7 +152,7 @@ void add_string_constraint_to_query(realm::Query& query, template struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, unsigned int idx) + static Columns convert(TableGetter&& table, size_t idx) { return table()->template column(idx); } @@ -160,7 +160,7 @@ struct ColumnOfTypeHelper { template struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, unsigned int idx) + static Columns convert(TableGetter&& table, size_t idx) { return table()->template column(idx); } @@ -171,7 +171,7 @@ struct ValueOfTypeHelper; template struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const std::string & value) + static Int convert(TableGetter&&, const parser::Expression & value) { assert(0); } @@ -179,41 +179,41 @@ struct ValueOfTypeHelper { template struct ValueOfTypeHelper { - static bool convert(TableGetter&&, const std::string & value) + static bool convert(TableGetter&&, const parser::Expression & value) { - assert(0); + return value.type == parser::Expression::Type::True; } }; template struct ValueOfTypeHelper { - static Double convert(TableGetter&&, const std::string & value) + static Double convert(TableGetter&&, const parser::Expression & value) { - return std::stod(value); + return std::stod(value.s); } }; template struct ValueOfTypeHelper { - static Float convert(TableGetter&&, const std::string & value) + static Float convert(TableGetter&&, const parser::Expression & value) { - return std::stof(value); + return std::stof(value.s); } }; template struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const std::string & value) + static Int convert(TableGetter&&, const parser::Expression & value) { - return std::stoll(value); + return std::stoll(value.s); } }; template struct ValueOfTypeHelper { - static std::string convert(TableGetter&&, const std::string & value) + static std::string convert(TableGetter&&, const parser::Expression & value) { - return value; + return value.s; } }; @@ -222,8 +222,8 @@ auto value_of_type_for_query(TableGetter&& tables, Value&& value) { const bool isColumnIndex = std::is_same::type>::value; using helper = std::conditional_t, - ValueOfTypeHelper>; + ColumnOfTypeHelper, + ValueOfTypeHelper>; return helper::convert(std::forward(tables), std::forward(value)); } @@ -310,11 +310,11 @@ void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, Obje auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1].s); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1]); } else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) { Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0].s, prop->table_column); + do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0], prop->table_column); } else { throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); diff --git a/tests/RealmTests.js b/tests/RealmTests.js index 747f3969..7187fa75 100644 --- a/tests/RealmTests.js +++ b/tests/RealmTests.js @@ -237,10 +237,10 @@ module.exports = BaseTest.extend({ testRealmObjects: function() { var realm = new Realm({schema: [schemas.PersonObject]}); realm.write(function() { - realm.create('PersonObject', ['Ari', 10]); - realm.create('PersonObject', ['Tim', 11]); + realm.create('PersonObject', ['Ari', 10, false]); + realm.create('PersonObject', ['Tim', 11, false]); realm.create('PersonObject', {'name': 'Bjarne', 'age': 12}); - realm.create('PersonObject', {'name': 'Alex', 'age': 12}); + realm.create('PersonObject', {'name': 'Alex', 'age': 12, 'married': true}); }); TestCase.assertThrows(function() { @@ -270,6 +270,8 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(realm.objects('PersonObject', 'age >= 11 && age < 13').length, 3); TestCase.assertEqual(realm.objects('PersonObject', 'name = "Tim"').length, 1); TestCase.assertEqual(realm.objects('PersonObject', 'name = \'Tim\'').length, 1); + TestCase.assertEqual(realm.objects('PersonObject', 'married == TRUE').length, 1); + TestCase.assertEqual(realm.objects('PersonObject', 'married == false').length, 3); }, testNotifications: function() { diff --git a/tests/ResultsTests.js b/tests/ResultsTests.js index 3c82ab00..f26f7633 100644 --- a/tests/ResultsTests.js +++ b/tests/ResultsTests.js @@ -24,8 +24,8 @@ module.exports = BaseTest.extend({ testResultsSubscript: function() { var realm = new Realm({schema: [schemas.PersonObject]}); realm.write(function() { - realm.create('PersonObject', ['name1', 1]); - realm.create('PersonObject', ['name2', 2]); + realm.create('PersonObject', ['name1', 1, false]); + realm.create('PersonObject', ['name2', 2, false]); }); var people = realm.objects('PersonObject'); diff --git a/tests/schemas.js b/tests/schemas.js index fa385476..6cc11815 100644 --- a/tests/schemas.js +++ b/tests/schemas.js @@ -17,8 +17,9 @@ function PersonObject() {} PersonObject.prototype.schema = { name: 'PersonObject', properties: [ - {name: 'name', type: Realm.Types.STRING}, - {name: 'age', type: Realm.Types.DOUBLE}, + {name: 'name', type: Realm.Types.STRING}, + {name: 'age', type: Realm.Types.DOUBLE}, + {name: 'married', type: Realm.Types.BOOL, default: false} ] }; PersonObject.prototype.description = function() { From 3a198ec507e562350a187e5cb302e345346c9ee4 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 15:58:04 -0800 Subject: [PATCH 14/47] support query format strings --- src/RJSObject.mm | 7 + src/RJSRealm.mm | 10 +- src/RJSResults.hpp | 2 +- src/RJSResults.mm | 5 +- src/object-store/object_accessor.hpp | 3 + src/object-store/object_store.hpp | 2 +- src/object-store/parser/query_builder.cpp | 214 ++++++++++++---------- src/object-store/parser/query_builder.hpp | 46 ++++- tests/RealmTests.js | 6 + 9 files changed, 191 insertions(+), 104 deletions(-) diff --git a/src/RJSObject.mm b/src/RJSObject.mm index 84075f87..0a00ddd9 100644 --- a/src/RJSObject.mm +++ b/src/RJSObject.mm @@ -211,6 +211,13 @@ template<> JSValueRef RJSAccessor::from_datetime(JSContextRef ctx, DateTime dt) extern JSObjectRef RJSDictForPropertyArray(JSContextRef ctx, ObjectSchema &object_schema, JSObjectRef array); +template<> size_t RJSAccessor::to_existing_object_index(JSContextRef ctx, JSValueRef &val) { + JSObjectRef object = RJSValidatedValueToObject(ctx, val); + if (JSValueIsObjectOfClass(ctx, val, RJSObjectClass())) { + return RJSGetInternal(object)->row().get_index(); + } + throw std::runtime_error("object is not a Realm Object"); +} template<> size_t RJSAccessor::to_object_index(JSContextRef ctx, SharedRealm realm, JSValueRef &val, const std::string &type, bool try_update) { JSObjectRef object = RJSValidatedValueToObject(ctx, val); if (JSValueIsObjectOfClass(ctx, val, RJSObjectClass())) { diff --git a/src/RJSRealm.mm b/src/RJSRealm.mm index 6b98dc3a..616f3caf 100644 --- a/src/RJSRealm.mm +++ b/src/RJSRealm.mm @@ -259,7 +259,7 @@ JSValueRef RealmGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef pr JSValueRef RealmObjects(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* jsException) { try { - RJSValidateArgumentRange(argumentCount, 1, 2); + RJSValidateArgumentCountIsAtLeast(argumentCount, 1); std::string className = RJSValidatedStringForValue(ctx, arguments[0], "objectType"); SharedRealm sharedRealm = *RJSGetInternal(thisObject); @@ -267,8 +267,12 @@ JSValueRef RealmObjects(JSContextRef ctx, JSObjectRef function, JSObjectRef this return RJSResultsCreate(ctx, sharedRealm, className); } else { - std::string predicate = RJSValidatedStringForValue(ctx, arguments[1], "predicate"); - return RJSResultsCreate(ctx, sharedRealm, className, predicate); + std::string query = RJSValidatedStringForValue(ctx, arguments[1], "predicate"); + std::vector args; + for (size_t i =2; i < argumentCount; i++) { + args.push_back(arguments[i]); + } + return RJSResultsCreate(ctx, sharedRealm, className, query, args); } } catch (std::exception &exp) { diff --git a/src/RJSResults.hpp b/src/RJSResults.hpp index fbb839cd..9cb21a64 100644 --- a/src/RJSResults.hpp +++ b/src/RJSResults.hpp @@ -11,4 +11,4 @@ namespace realm { JSClassRef RJSResultsClass(); JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className); -JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className, std::string query); +JSObjectRef RJSResultsCreate(JSContextRef ctx, realm::SharedRealm realm, std::string className, std::string query, std::vector args); diff --git a/src/RJSResults.mm b/src/RJSResults.mm index b1c287d9..eaca1cc0 100644 --- a/src/RJSResults.mm +++ b/src/RJSResults.mm @@ -105,7 +105,7 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl } -JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string className, std::string queryString) { +JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string className, std::string queryString, std::vector args) { TableRef table = ObjectStore::table_for_object_type(realm->read_group(), className); Query query = table->where(); Schema &schema = *realm->config().schema; @@ -114,7 +114,8 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl throw std::runtime_error("Object type '" + className + "' not present in Realm."); } parser::Predicate predicate = parser::parse(queryString); - query_builder::apply_predicate(query, predicate, schema, object_schema->name); + query_builder::ArgumentConverter arguments(ctx, args); + query_builder::apply_predicate(query, predicate, arguments, schema, object_schema->name); return RJSWrapObject(ctx, RJSResultsClass(), new Results(realm, *object_schema, std::move(query))); } diff --git a/src/object-store/object_accessor.hpp b/src/object-store/object_accessor.hpp index 5cc7a358..dfae7c16 100644 --- a/src/object-store/object_accessor.hpp +++ b/src/object-store/object_accessor.hpp @@ -76,6 +76,9 @@ namespace realm { static size_t to_object_index(ContextType ctx, SharedRealm realm, ValueType &val, const std::string &type, bool try_update); static ValueType from_object(ContextType ctx, Object); + // object index for an existing object + static size_t to_existing_object_index(ContextType ctx, ValueType &val); + // list value acessors static size_t list_size(ContextType ctx, ValueType &val); static ValueType list_value_at_index(ContextType ctx, ValueType &val, size_t index); diff --git a/src/object-store/object_store.hpp b/src/object-store/object_store.hpp index 60671f55..e038604c 100644 --- a/src/object-store/object_store.hpp +++ b/src/object-store/object_store.hpp @@ -19,6 +19,7 @@ #ifndef REALM_OBJECT_STORE_HPP #define REALM_OBJECT_STORE_HPP +#include "schema.hpp" #include "object_schema.hpp" #include "property.hpp" @@ -29,7 +30,6 @@ namespace realm { class ObjectSchemaValidationException; - class Schema; class ObjectStore { public: diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp index 8f4aeeee..b924eaa3 100644 --- a/src/object-store/parser/query_builder.cpp +++ b/src/object-store/parser/query_builder.cpp @@ -62,7 +62,6 @@ struct FalseExpression : realm::Expression { // add a clause for numeric constraints based on operator type template void add_numeric_constraint_to_query(Query& query, - PropertyType datatype, Predicate::Operator operatorType, A lhs, B rhs) @@ -150,152 +149,176 @@ void add_string_constraint_to_query(realm::Query& query, } } -template -struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, size_t idx) + +using KeyPath = std::vector; +KeyPath key_path_from_string(const std::string &s) { + std::stringstream ss(s); + std::string item; + KeyPath key_path; + while (std::getline(ss, item, '.')) { + key_path.push_back(item); + } + return key_path; +} + +struct PropertyExpression +{ + Property *prop = nullptr; + std::vector indexes; + std::function table_getter; + + PropertyExpression(Query &query, Schema &schema, ObjectSchema &desc, const std::string &key_path_string) { - return table()->template column(idx); + KeyPath key_path = key_path_from_string(key_path_string); + for (size_t index = 0; index < key_path.size(); index++) { + if (prop) { + precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + key_path[index] + "' is not a link in object of type '" + desc.name + "'"); + indexes.push_back(prop->table_column); + + } + prop = desc.property_for_name(key_path[index]); + precondition(prop != nullptr, "No property '" + key_path[index] + "' on object of type '" + desc.name + "'"); + + if (prop->object_type.size()) { + desc = *schema.find(prop->object_type); + } + } + + table_getter = [&] { + TableRef& tbl = query.get_table(); + for (size_t col : indexes) { + tbl->link(col); // mutates m_link_chain on table + } + return tbl.get(); + }; + } +}; + +template +struct ColumnGetter { + static Columns convert(TableGetter&& table, const PropertyExpression & expr, Arguments &args) + { + return table()->template column(expr.prop->table_column); } }; template -struct ColumnOfTypeHelper { - static Columns convert(TableGetter&& table, size_t idx) +struct ColumnGetter { + static Columns convert(TableGetter&& table, const PropertyExpression & expr, Arguments &args) { - return table()->template column(idx); + return table()->template column(expr.prop->table_column); } }; template -struct ValueOfTypeHelper; +struct ValueGetter; template -struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static Int convert(TableGetter&&, const parser::Expression & value, Arguments &args) { - assert(0); + if (value.type != parser::Expression::Type::Argument) { + throw std::runtime_error("You must pass in a date argument to compare"); + } + DateTime dt = args.datetime_for_argument(std::stoi(value.s)); + return dt.get_datetime(); } }; template -struct ValueOfTypeHelper { - static bool convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static bool convert(TableGetter&&, const parser::Expression & value, Arguments &args) { + if (value.type == parser::Expression::Type::Argument) { + return args.bool_for_argument(std::stoi(value.s)); + } return value.type == parser::Expression::Type::True; } }; template -struct ValueOfTypeHelper { - static Double convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static Double convert(TableGetter&&, const parser::Expression & value, Arguments &args) { + if (value.type == parser::Expression::Type::Argument) { + return args.double_for_argument(std::stoi(value.s)); + } return std::stod(value.s); } }; template -struct ValueOfTypeHelper { - static Float convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static Float convert(TableGetter&&, const parser::Expression & value, Arguments &args) { + if (value.type == parser::Expression::Type::Argument) { + return args.float_for_argument(std::stoi(value.s)); + } return std::stof(value.s); } }; template -struct ValueOfTypeHelper { - static Int convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static Int convert(TableGetter&&, const parser::Expression & value, Arguments &args) { + if (value.type == parser::Expression::Type::Argument) { + return args.long_for_argument(std::stoi(value.s)); + } return std::stoll(value.s); } }; template -struct ValueOfTypeHelper { - static std::string convert(TableGetter&&, const parser::Expression & value) +struct ValueGetter { + static std::string convert(TableGetter&&, const parser::Expression & value, Arguments &args) { + if (value.type == parser::Expression::Type::Argument) { + return args.string_for_argument(std::stoi(value.s)); + } return value.s; } }; -template -auto value_of_type_for_query(TableGetter&& tables, Value&& value) +template +auto value_of_type_for_query(TableGetter&& tables, Value&& value, Arguments &args) { - const bool isColumnIndex = std::is_same::type>::value; - using helper = std::conditional_t, - ValueOfTypeHelper>; - return helper::convert(std::forward(tables), std::forward(value)); + const bool isColumn = std::is_same::type>::value; + using helper = std::conditional_t, ValueGetter>; + return helper::convert(std::forward(tables), std::forward(value), args); } -std::vector &split(const std::string &s, char delim, std::vector &elems) { - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; -} - -std::vector split(const std::string &s, char delim) { - std::vector elems; - split(s, delim, elems); - return elems; -} - -Property *get_property_from_key_path(Schema &schema, ObjectSchema &desc, const std::string &key_path, std::vector &indexes) +template +void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Predicate::Operator op, + PropertyExpression &expr, A &lhs, B &rhs, Arguments &args) { - Property *prop = nullptr; - - auto paths = split(key_path, '.'); - for (size_t index = 0; index < paths.size(); index++) { - if (prop) { - precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, - (std::string)"Property '" + paths[index] + "' is not a link in object of type '" + desc.name + "'"); - indexes.push_back(prop->table_column); - - } - prop = desc.property_for_name(paths[index]); - precondition(prop != nullptr, "No property '" + paths[index] + "' on object of type '" + desc.name + "'"); - - if (prop->object_type.size()) { - desc = *schema.find(prop->object_type); - } - } - return prop; -} - -template -void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &object_schema, Property *prop, - Predicate::Operator op, const std::vector& indexes, T... values) -{ - auto table = [&] { - TableRef& tbl = query.get_table(); - for (size_t col : indexes) { - tbl->link(col); // mutates m_link_chain on table - } - return tbl.get(); - }; - - auto type = prop->type; + auto type = expr.prop->type; switch (type) { case PropertyTypeBool: - add_bool_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + add_bool_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeDate: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + add_numeric_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeDouble: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + add_numeric_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeFloat: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + add_numeric_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeInt: - add_numeric_constraint_to_query(query, type, op, value_of_type_for_query(table, values)...); + add_numeric_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeString: case PropertyTypeData: - add_string_constraint_to_query(query, op, value_of_type_for_query(table, values)...); + add_string_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); break; default: { throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); @@ -303,25 +326,24 @@ void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &obje } } -void add_comparison_to_query(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, Schema &schema, ObjectSchema &object_schema) { - std::vector indexes; Predicate::Comparison &cmpr = pred.cmpr; auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[0].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, prop->table_column, cmpr.expr[1]); + PropertyExpression expr(query, schema, object_schema, cmpr.expr[0].s); + do_add_comparison_to_query(query, schema, object_schema, cmpr.op, expr, expr, cmpr.expr[1], args); } else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) { - Property *prop = get_property_from_key_path(schema, object_schema, cmpr.expr[1].s, indexes); - do_add_comparison_to_query(query, schema, object_schema, prop, cmpr.op, indexes, cmpr.expr[0], prop->table_column); + PropertyExpression expr(query, schema, object_schema, cmpr.expr[1].s); + do_add_comparison_to_query(query, schema, object_schema, cmpr.op, expr, cmpr.expr[0], expr, args); } else { throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); } } -void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, ObjectSchema &object_schema) +void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, Schema &schema, ObjectSchema &object_schema) { if (pred.negate) { query.Not(); @@ -331,7 +353,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, case Predicate::Type::And: query.group(); for (auto &sub : pred.cpnd.sub_predicates) { - update_query_with_predicate(query, sub, schema, object_schema); + update_query_with_predicate(query, sub, arguments, schema, object_schema); } if (!pred.cpnd.sub_predicates.size()) { query.and_query(new TrueExpression); @@ -343,7 +365,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, query.group(); for (auto &sub : pred.cpnd.sub_predicates) { query.Or(); - update_query_with_predicate(query, sub, schema, object_schema); + update_query_with_predicate(query, sub, arguments, schema, object_schema); } if (!pred.cpnd.sub_predicates.size()) { query.and_query(new FalseExpression); @@ -352,7 +374,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, break; case Predicate::Type::Comparison: { - add_comparison_to_query(query, pred, schema, object_schema); + add_comparison_to_query(query, pred, arguments, schema, object_schema); break; } case Predicate::Type::True: @@ -369,9 +391,9 @@ void update_query_with_predicate(Query &query, Predicate &pred, Schema &schema, } } -void apply_predicate(Query &query, Predicate &predicate, Schema &schema, std::string objectType) +void apply_predicate(Query &query, Predicate &predicate, Arguments &arguments, Schema &schema, std::string objectType) { - update_query_with_predicate(query, predicate, schema, *schema.find(objectType)); + update_query_with_predicate(query, predicate, arguments, schema, *schema.find(objectType)); // Test the constructed query in core std::string validateMessage = query.validate(); diff --git a/src/object-store/parser/query_builder.hpp b/src/object-store/parser/query_builder.hpp index 8a408ec8..1de648b3 100644 --- a/src/object-store/parser/query_builder.hpp +++ b/src/object-store/parser/query_builder.hpp @@ -21,13 +21,57 @@ #include #include "parser.hpp" +#include "object_accessor.hpp" namespace realm { class Query; class Schema; namespace query_builder { - void apply_predicate(Query &query, parser::Predicate &predicate, Schema &schema, std::string objectType); + class Arguments; + + void apply_predicate(Query &query, parser::Predicate &predicate, Arguments &arguments, Schema &schema, std::string objectType); + + class Arguments + { + public: + virtual bool bool_for_argument(size_t argument_index) = 0; + virtual long long long_for_argument(size_t argument_index) = 0; + virtual float float_for_argument(size_t argument_index) = 0; + virtual double double_for_argument(size_t argument_index) = 0; + virtual std::string string_for_argument(size_t argument_index) = 0; + virtual DateTime datetime_for_argument(size_t argument_index) = 0; + virtual size_t object_index_for_argument(size_t argument_index) = 0; + virtual bool is_argument_null(size_t argument_index) = 0; + }; + + template + class ArgumentConverter : public Arguments + { + public: + ArgumentConverter(ContextType context, std::vector arguments) : m_arguments(arguments), m_ctx(context) {}; + + using Accessor = realm::NativeAccessor; + virtual bool bool_for_argument(size_t argument_index) { return Accessor::to_bool(m_ctx, argument_at(argument_index)); } + virtual long long long_for_argument(size_t argument_index) { return Accessor::to_long(m_ctx, argument_at(argument_index)); } + virtual float float_for_argument(size_t argument_index) { return Accessor::to_float(m_ctx, argument_at(argument_index)); } + virtual double double_for_argument(size_t argument_index) { return Accessor::to_double(m_ctx, argument_at(argument_index)); } + virtual std::string string_for_argument(size_t argument_index) { return Accessor::to_string(m_ctx, argument_at(argument_index)); } + virtual DateTime datetime_for_argument(size_t argument_index) { return Accessor::to_datetime(m_ctx, argument_at(argument_index)); } + virtual size_t object_index_for_argument(size_t argument_index) { return Accessor::to_existing_object_index(m_ctx, argument_at(argument_index)); } + virtual bool is_argument_null(size_t argument_index) { return Accessor::is_null(m_ctx, argument_at(argument_index)); } + + private: + std::vector m_arguments; + ContextType m_ctx; + + ValueType &argument_at(size_t index) { + if (index >= m_arguments.size()) { + throw std::out_of_range((std::string)"Argument index " + std::to_string(index) + " out of range 0.." + std::to_string(m_arguments.size()-1)); + } + return m_arguments[index]; + } + }; } } diff --git a/tests/RealmTests.js b/tests/RealmTests.js index 7187fa75..6a65b646 100644 --- a/tests/RealmTests.js +++ b/tests/RealmTests.js @@ -272,6 +272,12 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(realm.objects('PersonObject', 'name = \'Tim\'').length, 1); TestCase.assertEqual(realm.objects('PersonObject', 'married == TRUE').length, 1); TestCase.assertEqual(realm.objects('PersonObject', 'married == false').length, 3); + + TestCase.assertEqual(realm.objects('PersonObject', 'name = {0}', 'Tim').length, 1); + TestCase.assertEqual(realm.objects('PersonObject', 'age > {1} && age < {0}', 13, 10).length, 3); + TestCase.assertThrows(function() { + realm.objects('PersonObject', 'age > {2} && age < {0}', 13, 10) + }); }, testNotifications: function() { From f3d7855c9903b0bae68b30f7f6a7ebe34e79cdb9 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 10 Nov 2015 16:11:57 -0800 Subject: [PATCH 15/47] test and fix for date queries --- src/object-store/parser/query_builder.cpp | 8 -------- tests/RealmTests.js | 11 ++++++++++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp index b924eaa3..f1bbb9d4 100644 --- a/src/object-store/parser/query_builder.cpp +++ b/src/object-store/parser/query_builder.cpp @@ -203,14 +203,6 @@ struct ColumnGetter { } }; -template -struct ColumnGetter { - static Columns convert(TableGetter&& table, const PropertyExpression & expr, Arguments &args) - { - return table()->template column(expr.prop->table_column); - } -}; - template struct ValueGetter; diff --git a/tests/RealmTests.js b/tests/RealmTests.js index 6a65b646..67c7dfec 100644 --- a/tests/RealmTests.js +++ b/tests/RealmTests.js @@ -235,7 +235,7 @@ module.exports = BaseTest.extend({ }, testRealmObjects: function() { - var realm = new Realm({schema: [schemas.PersonObject]}); + var realm = new Realm({schema: [schemas.PersonObject, schemas.DefaultValues, schemas.TestObject]}); realm.write(function() { realm.create('PersonObject', ['Ari', 10, false]); realm.create('PersonObject', ['Tim', 11, false]); @@ -278,6 +278,15 @@ module.exports = BaseTest.extend({ TestCase.assertThrows(function() { realm.objects('PersonObject', 'age > {2} && age < {0}', 13, 10) }); + + realm.write(function() { + realm.create('DefaultValuesObject', {'dateCol': new Date(3)}); + realm.create('DefaultValuesObject', {'dateCol': new Date(4)}); + realm.create('DefaultValuesObject', {'dateCol': new Date(5)}); + }); + + TestCase.assertEqual(realm.objects('DefaultValuesObject', 'dateCol > {0}', new Date(4)).length, 1); + TestCase.assertEqual(realm.objects('DefaultValuesObject', 'dateCol <= {0}', new Date(4)).length, 2); }, testNotifications: function() { From 9309bbfbf867a405087b4bea15d17d80b2eb8048 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 11 Nov 2015 13:57:05 -0800 Subject: [PATCH 16/47] make precondition a macro --- src/object-store/parser/query_builder.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp index f1bbb9d4..5afdfcb9 100644 --- a/src/object-store/parser/query_builder.cpp +++ b/src/object-store/parser/query_builder.cpp @@ -32,12 +32,7 @@ using namespace parser; // check a precondition and throw an exception if it is not met // this should be used iff the condition being false indicates a bug in the caller // of the function checking its preconditions -static void precondition(bool condition, const std::string message) { - if (__builtin_expect(condition, 1)) { - return; - } - throw std::runtime_error(message); -} +#define precondition(condition, message) if (!__builtin_expect(condition, 1)) { throw std::runtime_error(message); } // FIXME: TrueExpression and FalseExpression should be supported by core in some way struct TrueExpression : realm::Expression { From 7829d21bd4a3616bafe24b654c267596498f0372 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 11 Nov 2015 14:08:59 -0800 Subject: [PATCH 17/47] add macro to enable/disable debug token printing --- src/object-store/parser/parser.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 2479b287..c7d18a7f 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -137,11 +137,18 @@ struct ParserState template< typename Rule > struct action : nothing< Rule > {}; +#define REALM_PARSER_PRINT_TOKENS +#ifdef REALM_PARSER_PRINT_TOKENS + #define DEBUG_PRINT_TOKEN(string) std::cout << string << std::endl +#else + #define DEBUG_PRINT_TOKEN(string) +#endif + template<> struct action< and_ext > { static void apply( const input & in, ParserState & state ) { - std::cout << "" << in.string() << std::endl; + DEBUG_PRINT_TOKEN(""); // if we were put into an OR group we need to rearrange auto ¤t = state.current(); @@ -171,7 +178,7 @@ template<> struct action< or_ext > { static void apply( const input & in, ParserState & state ) { - std::cout << "" << in.string() << std::endl; + DEBUG_PRINT_TOKEN(""); // if already an OR group do nothing auto ¤t = state.current(); @@ -202,7 +209,7 @@ template<> struct action< or_ext > #define EXPRESSION_ACTION(rule, type) \ template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ - std::cout << in.string() << std::endl; \ + DEBUG_PRINT_TOKEN(in.string()); \ state.addExpression(Expression(type, in.string())); }}; EXPRESSION_ACTION(dq_string_content, Expression::Type::String) @@ -218,7 +225,7 @@ template<> struct action< true_pred > { static void apply( const input & in, ParserState & state ) { - std::cout << in.string() << std::endl; + DEBUG_PRINT_TOKEN(in.string()); state.current().cpnd.sub_predicates.emplace_back(Predicate::Type::True); } }; @@ -227,7 +234,7 @@ template<> struct action< false_pred > { static void apply( const input & in, ParserState & state ) { - std::cout << in.string() << std::endl; + DEBUG_PRINT_TOKEN(in.string()); state.current().cpnd.sub_predicates.emplace_back(Predicate::Type::False); } }; @@ -235,7 +242,7 @@ template<> struct action< false_pred > #define OPERATOR_ACTION(rule, oper) \ template<> struct action< rule > { \ static void apply( const input & in, ParserState & state ) { \ - std::cout << in.string() << std::endl; \ + DEBUG_PRINT_TOKEN(in.string()); \ state.current().cmpr.op = oper; }}; OPERATOR_ACTION(eq, Predicate::Operator::Equal) @@ -252,7 +259,7 @@ template<> struct action< one< '(' > > { static void apply( const input & in, ParserState & state ) { - std::cout << "" << std::endl; + DEBUG_PRINT_TOKEN(""); Predicate group(Predicate::Type::And); if (state.negate_next) { @@ -269,7 +276,7 @@ template<> struct action< group_pred > { static void apply( const input & in, ParserState & state ) { - std::cout << "" << std::endl; + DEBUG_PRINT_TOKEN(""); state.predicate_stack.pop_back(); } }; @@ -278,7 +285,7 @@ template<> struct action< not_pre > { static void apply( const input & in, ParserState & state ) { - std::cout << "" << std::endl; + DEBUG_PRINT_TOKEN(""); state.negate_next = true; } }; From f46e92468c56e2461abfb1883a602448daca4a7a Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 11 Nov 2015 14:54:05 -0800 Subject: [PATCH 18/47] add basic test harness for grammer validation --- src/object-store/parser/main.cpp | 7 ----- src/object-store/parser/parser.cpp | 1 - src/object-store/parser/test.cpp | 45 ++++++++++++++++++++++++++++++ src/object-store/parser/test.sh | 3 ++ 4 files changed, 48 insertions(+), 8 deletions(-) delete mode 100644 src/object-store/parser/main.cpp create mode 100644 src/object-store/parser/test.cpp create mode 100755 src/object-store/parser/test.sh diff --git a/src/object-store/parser/main.cpp b/src/object-store/parser/main.cpp deleted file mode 100644 index def876c3..00000000 --- a/src/object-store/parser/main.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "parser.hpp" - -int main( int argc, char ** argv ) -{ - realm::parser::parse(argv[1]); -} - diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index c7d18a7f..ef15fd48 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -137,7 +137,6 @@ struct ParserState template< typename Rule > struct action : nothing< Rule > {}; -#define REALM_PARSER_PRINT_TOKENS #ifdef REALM_PARSER_PRINT_TOKENS #define DEBUG_PRINT_TOKEN(string) std::cout << string << std::endl #else diff --git a/src/object-store/parser/test.cpp b/src/object-store/parser/test.cpp new file mode 100644 index 00000000..23494306 --- /dev/null +++ b/src/object-store/parser/test.cpp @@ -0,0 +1,45 @@ + +#include "parser.hpp" + +#include +#include +#include +#include + +static std::vector valid_queries = { + "truepredicate", + "falsepredicate", + "TRUEPREDICATE", + "FALSEPREDICATE", + "truepredicate && truepredicate" +}; + +static std::vector invalid_queries = { + "predicate", + "truepredicate &&", + "truepredicate & truepredicate", +}; + +int main( int argc, char ** argv ) +{ + for (auto &query : valid_queries) { + std::cout << "valid query: " << query << std::endl; + try { + realm::parser::parse(query); + } catch (std::exception &ex) { + std::cout << "FAILURE - " << ex.what() << std::endl; + } + } + + for (auto &query : invalid_queries) { + std::cout << "invalid query: " << query << std::endl; + try { + realm::parser::parse(query); + } catch (std::exception &ex) { + // std::cout << "message: " << ex.what() << std::endl; + continue; + } + std::cout << "FAILURE - query should throw an exception" << std::endl; + } +} + diff --git a/src/object-store/parser/test.sh b/src/object-store/parser/test.sh new file mode 100755 index 00000000..aad1d031 --- /dev/null +++ b/src/object-store/parser/test.sh @@ -0,0 +1,3 @@ +# /bin/sh +llvm-g++ -std=c++11 -I ../../../vendor/PEGTL/ -o test test.cpp parser.cpp +./test From 676b86d1949389462274e1f2981c6d81da2b3bc3 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 14:24:37 -0800 Subject: [PATCH 19/47] more grammer tests --- src/object-store/parser/parser.cpp | 9 ++++---- src/object-store/parser/test.cpp | 37 +++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index ef15fd48..9443a37c 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -31,15 +31,14 @@ namespace parser { // strings struct unicode : list< seq< one< 'u' >, rep< 4, must< xdigit > > >, one< '\\' > > {}; -struct escaped_char : one< '"', '\'', '\\', '/', 'b', 'f', 'n', 'r', 't' > {}; +struct escaped_char : one< '"', '\'', '\\', '/', 'b', 'f', 'n', 'r', 't', '0' > {}; struct escaped : sor< escaped_char, unicode > {}; struct unescaped : utf8::range< 0x20, 0x10FFFF > {}; -struct char_ : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; - -struct dq_string_content : until< at< one< '"' > >, must< char_ > > {}; +struct chars : if_then_else< one< '\\' >, must< escaped >, unescaped > {}; +struct dq_string_content : until< at< one< '"' > >, must< chars > > {}; struct dq_string : seq< one< '"' >, must< dq_string_content >, any > {}; -struct sq_string_content : until< at< one< '\'' > >, must< char_ > > {}; +struct sq_string_content : until< at< one< '\'' > >, must< chars > > {}; struct sq_string : seq< one< '\'' >, must< sq_string_content >, any > {}; // numbers diff --git a/src/object-store/parser/test.cpp b/src/object-store/parser/test.cpp index 23494306..9655ee05 100644 --- a/src/object-store/parser/test.cpp +++ b/src/object-store/parser/test.cpp @@ -7,15 +7,50 @@ #include static std::vector valid_queries = { + // true/false predicates "truepredicate", "falsepredicate", "TRUEPREDICATE", "FALSEPREDICATE", - "truepredicate && truepredicate" + + // characters/strings + "\"\" = ''", + "'azAZ09/ :()[]{}<>,.^@-+=*&~`' = '\\\" \\' \\\\ \\/ \\b \\f \\n \\r \\t \\0'", + "\"azAZ09/\" = \"\\\" \\' \\\\ \\/ \\b \\f \\n \\r \\t \\0\"", + "'\\uffFf' = '\\u0020'", + "'\\u01111' = 'asdf\\u0111asdf'", + + // numbers, bools, keypaths + "-1 = 12", + "0 = 001", + "0x0 = -0X398235fcAb", + "10. = -.034", + "10.0 = 5.034", + "true = false", + "_ = a", + "_a = _.aZ", + "a09._br.z = __-__.Z-9", }; static std::vector invalid_queries = { "predicate", + "'\\a' = ''", // invalid escape + + // invalid unicode + "'\\u0' = ''", + + // invalid strings + "\"' = ''", + "\" = ''", + "' = ''", + + // invalid numbers, bools, keypaths + "03a = 1", + "1..0 = 1", + "1.0. = 1", + "0x = 1", + "truey = false", + "truepredicate &&", "truepredicate & truepredicate", }; From 735b49ff2aa95009afc4f7fa8c76ee3663ea6b89 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 14:34:47 -0800 Subject: [PATCH 20/47] fix and tests for arguments --- src/object-store/parser/parser.cpp | 4 ++-- src/object-store/parser/test.cpp | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 9443a37c..5bc6e7d8 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -61,8 +61,8 @@ struct false_value : pegtl_istring_t("false") {}; struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_', '-' > > > >, one< '.' > > {}; // argument -struct argument_index : until< at< one< '}' > >, must< digit > > {}; -struct argument : seq< one< '{' >, must< argument_index >, any > {}; +struct argument_index : plus< digit > {}; +struct argument : seq< one< '{' >, must< argument_index, one< '}' > > > {}; // expressions and operators struct expr : sor< dq_string, sq_string, number, argument, true_value, false_value, key_path > {}; diff --git a/src/object-store/parser/test.cpp b/src/object-store/parser/test.cpp index 9655ee05..3870569f 100644 --- a/src/object-store/parser/test.cpp +++ b/src/object-store/parser/test.cpp @@ -20,7 +20,7 @@ static std::vector valid_queries = { "'\\uffFf' = '\\u0020'", "'\\u01111' = 'asdf\\u0111asdf'", - // numbers, bools, keypaths + // expressions (numbers, bools, keypaths, arguments) "-1 = 12", "0 = 001", "0x0 = -0X398235fcAb", @@ -30,6 +30,10 @@ static std::vector valid_queries = { "_ = a", "_a = _.aZ", "a09._br.z = __-__.Z-9", + + // arguments + "{0} = {19}", + "{0} = {0}", }; static std::vector invalid_queries = { @@ -44,12 +48,21 @@ static std::vector invalid_queries = { "\" = ''", "' = ''", - // invalid numbers, bools, keypaths + // expressions "03a = 1", "1..0 = 1", "1.0. = 1", + "1-0 = 1", "0x = 1", "truey = false", + "- = a", + "a..b = a", + "a$a = a", + "{} = {0}", + "{-1} = {0}", + "{a} = {0}", + "{ = }", + "truepredicate &&", "truepredicate & truepredicate", From 5c56a5c87c23369904c2a8a6f88b639bffc360d9 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 14:51:31 -0800 Subject: [PATCH 21/47] tests for all expressions/operators --- src/object-store/parser/test.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/object-store/parser/test.cpp b/src/object-store/parser/test.cpp index 3870569f..830ad13a 100644 --- a/src/object-store/parser/test.cpp +++ b/src/object-store/parser/test.cpp @@ -10,8 +10,8 @@ static std::vector valid_queries = { // true/false predicates "truepredicate", "falsepredicate", - "TRUEPREDICATE", - "FALSEPREDICATE", + " TRUEPREDICATE ", + " FALSEPREDICATE ", // characters/strings "\"\" = ''", @@ -30,10 +30,27 @@ static std::vector valid_queries = { "_ = a", "_a = _.aZ", "a09._br.z = __-__.Z-9", - - // arguments "{0} = {19}", "{0} = {0}", + + // operators + "0=0", + "0 = 0", + "0!=0", + "0 != 0", + "0==0", + "0 == 0", + "0>0", + "0 > 0", + "0>=0", + "0 >= 0", + "0<0", + "0 < 0", + "0<=0", + "0 <= 0", + "0 contains 0", + "0 BeGiNsWiTh 0", + "0 ENDSWITH 0", }; static std::vector invalid_queries = { @@ -63,6 +80,11 @@ static std::vector invalid_queries = { "{a} = {0}", "{ = }", + // operators + "0===>0", + "0 <> 0", + "0 contains1", + "endswith 0", "truepredicate &&", "truepredicate & truepredicate", From f402a45a407858812b6cd9afc3803fe667f463ac Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 15:04:15 -0800 Subject: [PATCH 22/47] change argument syntax to use $ instead of {} --- src/object-store/parser/parser.cpp | 2 +- src/object-store/parser/test.cpp | 12 ++++++------ tests/RealmTests.js | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 5bc6e7d8..94e3f4b4 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -62,7 +62,7 @@ struct key_path : list< seq< sor< alpha, one< '_' > >, star< sor< alnum, one< '_ // argument struct argument_index : plus< digit > {}; -struct argument : seq< one< '{' >, must< argument_index, one< '}' > > > {}; +struct argument : seq< one< '$' >, must< argument_index > > {}; // expressions and operators struct expr : sor< dq_string, sq_string, number, argument, true_value, false_value, key_path > {}; diff --git a/src/object-store/parser/test.cpp b/src/object-store/parser/test.cpp index 830ad13a..b5fb6458 100644 --- a/src/object-store/parser/test.cpp +++ b/src/object-store/parser/test.cpp @@ -30,8 +30,8 @@ static std::vector valid_queries = { "_ = a", "_a = _.aZ", "a09._br.z = __-__.Z-9", - "{0} = {19}", - "{0} = {0}", + "$0 = $19", + "$0=$0", // operators "0=0", @@ -75,10 +75,10 @@ static std::vector invalid_queries = { "- = a", "a..b = a", "a$a = a", - "{} = {0}", - "{-1} = {0}", - "{a} = {0}", - "{ = }", + "{} = $0", + "$-1 = $0", + "$a = $0", + "$ = $", // operators "0===>0", diff --git a/tests/RealmTests.js b/tests/RealmTests.js index 67c7dfec..32bb20b7 100644 --- a/tests/RealmTests.js +++ b/tests/RealmTests.js @@ -273,10 +273,10 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(realm.objects('PersonObject', 'married == TRUE').length, 1); TestCase.assertEqual(realm.objects('PersonObject', 'married == false').length, 3); - TestCase.assertEqual(realm.objects('PersonObject', 'name = {0}', 'Tim').length, 1); - TestCase.assertEqual(realm.objects('PersonObject', 'age > {1} && age < {0}', 13, 10).length, 3); + TestCase.assertEqual(realm.objects('PersonObject', 'name = $0', 'Tim').length, 1); + TestCase.assertEqual(realm.objects('PersonObject', 'age > $1 && age < $0', 13, 10).length, 3); TestCase.assertThrows(function() { - realm.objects('PersonObject', 'age > {2} && age < {0}', 13, 10) + realm.objects('PersonObject', 'age > $2 && age < $0', 13, 10) }); realm.write(function() { @@ -285,8 +285,8 @@ module.exports = BaseTest.extend({ realm.create('DefaultValuesObject', {'dateCol': new Date(5)}); }); - TestCase.assertEqual(realm.objects('DefaultValuesObject', 'dateCol > {0}', new Date(4)).length, 1); - TestCase.assertEqual(realm.objects('DefaultValuesObject', 'dateCol <= {0}', new Date(4)).length, 2); + TestCase.assertEqual(realm.objects('DefaultValuesObject', 'dateCol > $0', new Date(4)).length, 1); + TestCase.assertEqual(realm.objects('DefaultValuesObject', 'dateCol <= $0', new Date(4)).length, 2); }, testNotifications: function() { From 5dda5f4b6b4326d4c59657cd69bddced934c3b53 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 15:19:32 -0800 Subject: [PATCH 23/47] test not, remove requirement of padding --- src/object-store/parser/parser.cpp | 2 +- src/object-store/parser/test.cpp | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 94e3f4b4..bc4d6520 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -91,7 +91,7 @@ struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; struct true_pred : pegtl_istring_t("truepredicate") {}; struct false_pred : pegtl_istring_t("falsepredicate") {}; -struct not_pre : sor< seq< one< '!' >, star< blank > >, seq< pegtl_istring_t("not"), plus< blank > > > {}; +struct not_pre : seq< sor< one< '!' >, seq< pegtl_istring_t("not") >, star< blank > > > {}; struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; struct and_op : sor< pad< two< '&' >, blank >, pad_plus< pegtl_istring_t("and"), blank > > {}; diff --git a/src/object-store/parser/test.cpp b/src/object-store/parser/test.cpp index b5fb6458..636ea316 100644 --- a/src/object-store/parser/test.cpp +++ b/src/object-store/parser/test.cpp @@ -51,6 +51,20 @@ static std::vector valid_queries = { "0 contains 0", "0 BeGiNsWiTh 0", "0 ENDSWITH 0", + + // atoms/groups + "(0=0)", + "( 0=0 )", + "((0=0))", + "!0=0", + "! 0=0", + "!(0=0)", + "! (0=0)", + "NOT0=0", // keypath NOT0 + "not 0=0", + "NOT(0=0)", + "not (0=0)", + "NOT (!0=0)", }; static std::vector invalid_queries = { @@ -86,6 +100,15 @@ static std::vector invalid_queries = { "0 contains1", "endswith 0", + // atoms/groups + "0=0)", + "(0=0", + "(0=0))", + "! =0", + "NOTNOT(0=0)", + "(!!0=0)", + "0=0 !", + "truepredicate &&", "truepredicate & truepredicate", }; From 3828417f321386ff8ad5615a9747aeb8b875103b Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 12 Nov 2015 15:40:54 -0800 Subject: [PATCH 24/47] compound tests --- src/object-store/parser/parser.cpp | 4 ++-- src/object-store/parser/test.cpp | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index bc4d6520..19b6b226 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -94,8 +94,8 @@ struct false_pred : pegtl_istring_t("falsepredicate") {}; struct not_pre : seq< sor< one< '!' >, seq< pegtl_istring_t("not") >, star< blank > > > {}; struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; -struct and_op : sor< pad< two< '&' >, blank >, pad_plus< pegtl_istring_t("and"), blank > > {}; -struct or_op : sor< pad< two< '|' >, blank> , pad_plus< pegtl_istring_t("or"), blank > > {}; +struct and_op : pad< sor< two< '&' >, pegtl_istring_t("and") >, blank > {}; +struct or_op : pad< sor< two< '|' >, pegtl_istring_t("or") >, blank > {}; struct or_ext : if_must< or_op, pred > {}; struct and_ext : if_must< and_op, pred > {}; diff --git a/src/object-store/parser/test.cpp b/src/object-store/parser/test.cpp index 636ea316..e47dcee7 100644 --- a/src/object-store/parser/test.cpp +++ b/src/object-store/parser/test.cpp @@ -65,6 +65,15 @@ static std::vector valid_queries = { "NOT(0=0)", "not (0=0)", "NOT (!0=0)", + + // compound + "a==a && a==a", + "a==a || a==a", + "a==a&&a==a||a=a", + "a==a and a==a", + "a==a OR a==a", + "and=='AND'&&'or'=='||'", + "and == or && ORE > GRAND", }; static std::vector invalid_queries = { @@ -109,6 +118,13 @@ static std::vector invalid_queries = { "(!!0=0)", "0=0 !", + // compound + "a==a & a==a", + "a==a | a==a", + "a==a &| a==a", + "a==a && OR a==a", + "a==aORa==a", + "truepredicate &&", "truepredicate & truepredicate", }; From 2cc88b5a26c83c91e1b90744c96858c7eb1b0f38 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 16 Nov 2015 14:48:12 -0800 Subject: [PATCH 25/47] fix for not predicate --- src/object-store/parser/parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 19b6b226..b391c9b4 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -91,7 +91,7 @@ struct group_pred : if_must< one< '(' >, pad< pred, blank >, one< ')' > > {}; struct true_pred : pegtl_istring_t("truepredicate") {}; struct false_pred : pegtl_istring_t("falsepredicate") {}; -struct not_pre : seq< sor< one< '!' >, seq< pegtl_istring_t("not") >, star< blank > > > {}; +struct not_pre : seq< sor< one< '!' >, pegtl_istring_t("not") > > {}; struct atom_pred : seq< opt< not_pre >, pad< sor< group_pred, true_pred, false_pred, comparison_pred >, blank > > {}; struct and_op : pad< sor< two< '&' >, pegtl_istring_t("and") >, blank > {}; From 5784bbe6ed6924d00220cffd9d4e77360df57b92 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 16 Nov 2015 15:05:50 -0800 Subject: [PATCH 26/47] start porting over objc query tests --- RealmJS.xcodeproj/project.pbxproj | 4 + src/RJSUtil.mm | 4 +- tests/QueryTests.js | 2165 +++++++++++++++++++++++++++++ tests/index.js | 1 + tests/schemas.js | 32 + 5 files changed, 2204 insertions(+), 2 deletions(-) create mode 100644 tests/QueryTests.js diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index 1cb71a9c..eee24ef7 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ 02D0F23B1BF6C95200B4FC45 /* binding_context.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 02D0F23A1BF6C95200B4FC45 /* binding_context.hpp */; }; 02D456DA1B7E59A500EE1299 /* ArrayTests.js in Resources */ = {isa = PBXBuildFile; fileRef = 02D456D91B7E59A500EE1299 /* ArrayTests.js */; }; 02D8D1F71B601984006DB49D /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; + 02E9A9F11BFA84F100939F86 /* QueryTests.js in Resources */ = {isa = PBXBuildFile; fileRef = 02E9A9F01BFA84F100939F86 /* QueryTests.js */; }; F636F6C81BCDB3570023F35C /* RealmReact.h in Headers */ = {isa = PBXBuildFile; fileRef = 0270BCCF1B7D067300010E03 /* RealmReact.h */; settings = {ATTRIBUTES = (Public, ); }; }; F64426C51BCDB1E200A81210 /* RealmJS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F64E1EF11BC3510E00E0E150 /* util.js in Resources */ = {isa = PBXBuildFile; fileRef = F64E1EF01BC3510E00E0E150 /* util.js */; }; @@ -222,6 +223,7 @@ 02C0864D1BCDB27000942F9C /* list.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = list.hpp; path = "src/object-store/list.hpp"; sourceTree = ""; }; 02D0F23A1BF6C95200B4FC45 /* binding_context.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = binding_context.hpp; path = "src/object-store/binding_context.hpp"; sourceTree = ""; }; 02D456D91B7E59A500EE1299 /* ArrayTests.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = ArrayTests.js; path = tests/ArrayTests.js; sourceTree = SOURCE_ROOT; }; + 02E9A9F01BFA84F100939F86 /* QueryTests.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = QueryTests.js; path = tests/QueryTests.js; sourceTree = SOURCE_ROOT; }; 02EE6D781BD87E310016A82E /* ReactTests.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactTests.xcodeproj; path = tests/ReactTests/ios/ReactTests.xcodeproj; sourceTree = ""; }; F64E1EF01BC3510E00E0E150 /* util.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = util.js; path = tests/util.js; sourceTree = SOURCE_ROOT; }; F68A278A1BC2722A0063D40A /* RJSModuleLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RJSModuleLoader.h; path = tests/RJSModuleLoader.h; sourceTree = SOURCE_ROOT; }; @@ -367,6 +369,7 @@ F64E1EF01BC3510E00E0E150 /* util.js */, 02D456D91B7E59A500EE1299 /* ArrayTests.js */, 0270BC791B7D020100010E03 /* ObjectTests.js */, + 02E9A9F01BFA84F100939F86 /* QueryTests.js */, 0270BC7C1B7D020100010E03 /* RealmTests.js */, 0270BC7D1B7D020100010E03 /* ResultsTests.js */, 0270BC7A1B7D020100010E03 /* RealmJSTests.h */, @@ -623,6 +626,7 @@ buildActionMask = 2147483647; files = ( F64E1EF11BC3510E00E0E150 /* util.js in Resources */, + 02E9A9F11BFA84F100939F86 /* QueryTests.js in Resources */, 0270BC851B7D020100010E03 /* asserts.js in Resources */, 0270BC811B7D020100010E03 /* ObjectTests.js in Resources */, 02D456DA1B7E59A500EE1299 /* ArrayTests.js in Resources */, diff --git a/src/RJSUtil.mm b/src/RJSUtil.mm index d1ab0193..6f0efee1 100644 --- a/src/RJSUtil.mm +++ b/src/RJSUtil.mm @@ -62,10 +62,10 @@ std::string RJSStringForValue(JSContextRef ctx, JSValueRef value) { std::string RJSValidatedStringForValue(JSContextRef ctx, JSValueRef value, const char * name) { if (!JSValueIsString(ctx, value)) { if (name) { - throw std::invalid_argument((std::string)"'" + name + "' must be of type 'STRING'"); + throw std::invalid_argument((std::string)"'" + name + "' must be of type 'String'"); } else { - throw std::invalid_argument("JSValue must be of type 'STRING'"); + throw std::invalid_argument("JSValue must be of type 'String'"); } } diff --git a/tests/QueryTests.js b/tests/QueryTests.js new file mode 100644 index 00000000..e3b6ebdd --- /dev/null +++ b/tests/QueryTests.js @@ -0,0 +1,2165 @@ +/* Copyright 2015 Realm Inc - All Rights Reserved + * Proprietary and Confidential + */ + + +'use strict'; + +var Realm = require('realm'); +var BaseTest = require('./base-test'); +var TestCase = require('./asserts'); +var schemas = require('./schemas'); + +function TestQueryCount(count, realm, type, query /*, queryArgs */) { + var results = realm.objects.apply(realm, Array.prototype.slice.call(arguments, 2)); + TestCase.assertEqual(count, results.length, "Query '" + query + "' on type '" + type + "' expected " + count + " results, got " + results.length); +} + +module.exports = BaseTest.extend({ + testDateQueries: function() { + var DateObject = { + name: 'DateObject', + properties: [ + { name: 'date', type: Realm.Types.DATE } + ] + }; + + var date1 = new Date(Date.now()); + var date2 = new Date(date1.getTime() + 1); + var date3 = new Date(date1.getTime() + 2); + + var realm = new Realm({schema: [DateObject]}); + realm.write(function() { + realm.create('DateObject', { date: date1 }); + realm.create('DateObject', { date: date2 }); + realm.create('DateObject', { date: date3 }); + }); + + TestQueryCount(2, realm, 'DateObject', "date < $0", date3); + TestQueryCount(3, realm, 'DateObject', "date <= $0", date3); + TestQueryCount(2, realm, 'DateObject', "date > $0", date1); + TestQueryCount(3, realm, 'DateObject', "date >= $0", date1); + TestQueryCount(1, realm, 'DateObject', "date == $0", date1); + TestQueryCount(2, realm, 'DateObject', "date != $0", date1); + }, +}); + +/* +-(void)testQueryBetween +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + NSDate *date1 = [NSDate date]; + NSDate *date2 = [date1 dateByAddingTimeInterval:1]; + NSDate *date3 = [date2 dateByAddingTimeInterval:1]; + NSDate *date33 = [date3 dateByAddingTimeInterval:1]; + + StringObject *stringObj = [StringObject new]; + stringObj.stringCol = @"string"; + + [realm beginWriteTransaction]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], date1, @YES, @((long)1), @1, stringObj]]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @2, @2.0f, @2.0, @"b", [@"b" dataUsingEncoding:NSUTF8StringEncoding], date2, @YES, @((long)2), @"mixed", stringObj]]; + [AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @((long)3), @"mixed", stringObj]]; + [AllTypesObject createInRealm:realm withValue:@[@NO, @33, @3.3f, @3.3, @"cc", [@"cc" dataUsingEncoding:NSUTF8StringEncoding], date33, @NO, @((long)3.3), @"mixed", stringObj]]; + [realm commitWriteTransaction]; + + RLMResults *betweenResults = [AllTypesObject objectsWithPredicate:[NSPredicate predicateWithFormat:@"intCol BETWEEN %@", @[@2, @3]]]; + XCTAssertEqual(betweenResults.count, 2U, @"Should equal 2"); + betweenResults = [AllTypesObject objectsWithPredicate:[NSPredicate predicateWithFormat:@"floatCol BETWEEN %@", @[@1.0f, @4.0f]]]; + XCTAssertEqual(betweenResults.count, 4U, @"Should equal 4"); + betweenResults = [AllTypesObject objectsWithPredicate:[NSPredicate predicateWithFormat:@"doubleCol BETWEEN %@", @[@3.0, @7.0f]]]; + XCTAssertEqual(betweenResults.count, 2U, @"Should equal 2"); + betweenResults = [AllTypesObject objectsWithPredicate:[NSPredicate predicateWithFormat:@"dateCol BETWEEN %@", @[date2,date3]]]; + XCTAssertEqual(betweenResults.count, 2U, @"Should equal 2"); + + betweenResults = [AllTypesObject objectsWhere:@"intCol BETWEEN {2, 3}"]; + XCTAssertEqual(betweenResults.count, 2U, @"Should equal 2"); + betweenResults = [AllTypesObject objectsWhere:@"doubleCol BETWEEN {3.0, 7.0}"]; + XCTAssertEqual(betweenResults.count, 2U, @"Should equal 2"); + + betweenResults = [AllTypesObject.allObjects objectsWhere:@"intCol BETWEEN {2, 3}"]; + XCTAssertEqual(betweenResults.count, 2U, @"Should equal 2"); + betweenResults = [AllTypesObject.allObjects objectsWhere:@"doubleCol BETWEEN {3.0, 7.0}"]; + XCTAssertEqual(betweenResults.count, 2U, @"Should equal 2"); +} + +- (void)testDefaultRealmQuery +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [PersonObject createInRealm:realm withValue:@[@"Fiel", @27]]; + [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; + [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; + [realm commitWriteTransaction]; + + // query on class + RLMResults *all = [PersonObject allObjects]; + XCTAssertEqual(all.count, 3U, @"Expecting 3 results"); + + RLMResults *results = [PersonObject objectsWhere:@"age == 27"]; + XCTAssertEqual(results.count, 1U, @"Expecting 1 results"); + + // with order + results = [[PersonObject objectsWhere:@"age > 28"] sortedResultsUsingProperty:@"age" ascending:YES]; + PersonObject *tim = results[0]; + XCTAssertEqualObjects(tim.name, @"Tim", @"Tim should be first results"); +} + +- (void)testArrayQuery +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [PersonObject createInRealm:realm withValue:@[@"Fiel", @27]]; + [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; + [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; + [realm commitWriteTransaction]; + + // query on class + RLMResults *all = [PersonObject allObjects]; + XCTAssertEqual(all.count, 3U, @"Expecting 3 results"); + + RLMResults *some = [[PersonObject objectsWhere:@"age > 28"] sortedResultsUsingProperty:@"age" ascending:YES]; + + // query/order on array + XCTAssertEqual([all objectsWhere:@"age == 27"].count, 1U, @"Expecting 1 result"); + XCTAssertEqual([all objectsWhere:@"age == 28"].count, 0U, @"Expecting 0 results"); + some = [some sortedResultsUsingProperty:@"age" ascending:NO]; + XCTAssertEqualObjects([some[0] name], @"Ari", @"Ari should be first results"); +} + +- (void)verifySort:(RLMRealm *)realm column:(NSString *)column ascending:(BOOL)ascending expected:(id)val { + RLMResults *results = [[AllTypesObject allObjectsInRealm:realm] sortedResultsUsingProperty:column ascending:ascending]; + AllTypesObject *obj = results[0]; + XCTAssertEqualObjects(obj[column], val, @"Array not sorted as expected - %@ != %@", obj[column], val); + + RLMArray *ar = (RLMArray *)[[[ArrayOfAllTypesObject allObjectsInRealm:realm] firstObject] array]; + results = [ar sortedResultsUsingProperty:column ascending:ascending]; + obj = results[0]; + XCTAssertEqualObjects(obj[column], val, @"Array not sorted as expected - %@ != %@", obj[column], val); +} + +- (void)verifySortWithAccuracy:(RLMRealm *)realm column:(NSString *)column ascending:(BOOL)ascending getter:(double(^)(id))getter expected:(double)val accuracy:(double)accuracy { + // test TableView query + RLMResults *results = [[AllTypesObject allObjectsInRealm:realm] sortedResultsUsingProperty:column ascending:ascending]; + XCTAssertEqualWithAccuracy(getter(results[0][column]), val, accuracy, @"Array not sorted as expected"); + + // test LinkView query + RLMArray *ar = (RLMArray *)[[[ArrayOfAllTypesObject allObjectsInRealm:realm] firstObject] array]; + results = [ar sortedResultsUsingProperty:column ascending:ascending]; + XCTAssertEqualWithAccuracy(getter(results[0][column]), val, accuracy, @"Array not sorted as expected"); +} + +- (void)testQuerySorting +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + NSDate *date1 = [NSDate date]; + NSDate *date2 = [date1 dateByAddingTimeInterval:1]; + NSDate *date3 = [date2 dateByAddingTimeInterval:1]; + NSDate *date33 = [date3 dateByAddingTimeInterval:1]; + + [realm beginWriteTransaction]; + ArrayOfAllTypesObject *arrayOfAll = [ArrayOfAllTypesObject createInRealm:realm withValue:@{}]; + + StringObject *stringObj = [StringObject new]; + stringObj.stringCol = @"string"; + + [arrayOfAll.array addObject:[AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], date1, @YES, @1, @1, stringObj]]]; + [arrayOfAll.array addObject:[AllTypesObject createInRealm:realm withValue:@[@YES, @2, @2.0f, @2.0, @"b", [@"b" dataUsingEncoding:NSUTF8StringEncoding], date2, @YES, @2, @"mixed", stringObj]]]; + [arrayOfAll.array addObject:[AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @3, @"mixed", stringObj]]]; + [arrayOfAll.array addObject:[AllTypesObject createInRealm:realm withValue:@[@NO, @33, @3.3f, @3.3, @"cc", [@"cc" dataUsingEncoding:NSUTF8StringEncoding], date33, @NO, @3, @"mixed", stringObj]]]; + + [realm commitWriteTransaction]; + + + //////////// sort by boolCol + [self verifySort:realm column:@"boolCol" ascending:YES expected:@NO]; + [self verifySort:realm column:@"boolCol" ascending:NO expected:@YES]; + + //////////// sort by intCol + [self verifySort:realm column:@"intCol" ascending:YES expected:@1]; + [self verifySort:realm column:@"intCol" ascending:NO expected:@33]; + + //////////// sort by dateCol + double (^dateGetter)(id) = ^(NSDate *d) { return d.timeIntervalSince1970; }; + [self verifySortWithAccuracy:realm column:@"dateCol" ascending:YES getter:dateGetter expected:date1.timeIntervalSince1970 accuracy:1]; + [self verifySortWithAccuracy:realm column:@"dateCol" ascending:NO getter:dateGetter expected:date33.timeIntervalSince1970 accuracy:1]; + + //////////// sort by doubleCol + double (^doubleGetter)(id) = ^(NSNumber *n) { return n.doubleValue; }; + [self verifySortWithAccuracy:realm column:@"doubleCol" ascending:YES getter:doubleGetter expected:1.0 accuracy:0.0000001]; + [self verifySortWithAccuracy:realm column:@"doubleCol" ascending:NO getter:doubleGetter expected:3.3 accuracy:0.0000001]; + + //////////// sort by floatCol + [self verifySortWithAccuracy:realm column:@"floatCol" ascending:YES getter:doubleGetter expected:1.0 accuracy:0.0000001]; + [self verifySortWithAccuracy:realm column:@"floatCol" ascending:NO getter:doubleGetter expected:3.3 accuracy:0.0000001]; + + //////////// sort by stringCol + [self verifySort:realm column:@"stringCol" ascending:YES expected:@"a"]; + [self verifySort:realm column:@"stringCol" ascending:NO expected:@"cc"]; + + // sort by mixed column + RLMAssertThrowsWithReasonMatching([[AllTypesObject allObjects] sortedResultsUsingProperty:@"mixedCol" ascending:YES], @"'mixedCol' .* 'AllTypesObject': sorting is only supported .* type any"); + XCTAssertThrows([arrayOfAll.array sortedResultsUsingProperty:@"mixedCol" ascending:NO]); + + // sort invalid name + RLMAssertThrowsWithReasonMatching([[AllTypesObject allObjects] sortedResultsUsingProperty:@"invalidCol" ascending:YES], @"'invalidCol'.* 'AllTypesObject'.* not found"); + XCTAssertThrows([arrayOfAll.array sortedResultsUsingProperty:@"invalidCol" ascending:NO]); + + // sort on key path + RLMAssertThrowsWithReasonMatching([[AllTypesObject allObjects] sortedResultsUsingProperty:@"key.path" ascending:YES], @"key paths is not supported"); + XCTAssertThrows([arrayOfAll.array sortedResultsUsingProperty:@"key.path" ascending:NO]); +} + +- (void)testSortByMultipleColumns { + RLMRealm *realm = [RLMRealm defaultRealm]; + [realm beginWriteTransaction]; + DogObject *a1 = [DogObject createInDefaultRealmWithValue:@[@"a", @1]]; + DogObject *a2 = [DogObject createInDefaultRealmWithValue:@[@"a", @2]]; + DogObject *b1 = [DogObject createInDefaultRealmWithValue:@[@"b", @1]]; + DogObject *b2 = [DogObject createInDefaultRealmWithValue:@[@"b", @2]]; + [realm commitWriteTransaction]; + + bool (^checkOrder)(NSArray *, NSArray *, NSArray *) = ^bool(NSArray *properties, NSArray *ascending, NSArray *dogs) { + NSArray *sort = @[[RLMSortDescriptor sortDescriptorWithProperty:properties[0] ascending:[ascending[0] boolValue]], + [RLMSortDescriptor sortDescriptorWithProperty:properties[1] ascending:[ascending[1] boolValue]]]; + RLMResults *actual = [DogObject.allObjects sortedResultsUsingDescriptors:sort]; + return [actual[0] isEqualToObject:dogs[0]] + && [actual[1] isEqualToObject:dogs[1]] + && [actual[2] isEqualToObject:dogs[2]] + && [actual[3] isEqualToObject:dogs[3]]; + }; + + // Check each valid sort + XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@YES, @YES], @[a1, a2, b1, b2])); + XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@YES, @NO], @[a2, a1, b2, b1])); + XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@NO, @YES], @[b1, b2, a1, a2])); + XCTAssertTrue(checkOrder(@[@"dogName", @"age"], @[@NO, @NO], @[b2, b1, a2, a1])); + XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@YES, @YES], @[a1, b1, a2, b2])); + XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@YES, @NO], @[b1, a1, b2, a2])); + XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@NO, @YES], @[a2, b2, a1, b1])); + XCTAssertTrue(checkOrder(@[@"age", @"dogName"], @[@NO, @NO], @[b2, a2, b1, a1])); +} + +- (void)testSortedLinkViewWithDeletion { + RLMRealm *realm = [RLMRealm defaultRealm]; + + NSDate *date1 = [NSDate date]; + NSDate *date2 = [date1 dateByAddingTimeInterval:1]; + NSDate *date3 = [date2 dateByAddingTimeInterval:1]; + NSDate *date33 = [date3 dateByAddingTimeInterval:1]; + + [realm beginWriteTransaction]; + ArrayOfAllTypesObject *arrayOfAll = [ArrayOfAllTypesObject createInRealm:realm withValue:@{}]; + + StringObject *stringObj = [StringObject new]; + stringObj.stringCol = @"string"; + + [arrayOfAll.array addObject:[AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], date1, @YES, @1, @1, stringObj]]]; + [arrayOfAll.array addObject:[AllTypesObject createInRealm:realm withValue:@[@YES, @2, @2.0f, @2.0, @"b", [@"b" dataUsingEncoding:NSUTF8StringEncoding], date2, @YES, @2, @"mixed", stringObj]]]; + [arrayOfAll.array addObject:[AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @3, @"mixed", stringObj]]]; + [arrayOfAll.array addObject:[AllTypesObject createInRealm:realm withValue:@[@NO, @33, @3.3f, @3.3, @"cc", [@"cc" dataUsingEncoding:NSUTF8StringEncoding], date33, @NO, @3, @"mixed", stringObj]]]; + + [realm commitWriteTransaction]; + + RLMResults *results = [arrayOfAll.array sortedResultsUsingProperty:@"stringCol" ascending:NO]; + XCTAssertEqualObjects([results[0] stringCol], @"cc"); + + // delete cc, add d results should update + [realm transactionWithBlock:^{ + [arrayOfAll.array removeObjectAtIndex:3]; + + // create extra alltypesobject + [arrayOfAll.array addObject:[AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"d", [@"d" dataUsingEncoding:NSUTF8StringEncoding], date1, @YES, @((long)1), @1, stringObj]]]; + }]; + XCTAssertEqualObjects([results[0] stringCol], @"d"); + XCTAssertEqualObjects([results[1] stringCol], @"c"); + + // delete from realm should be removed from results + [realm transactionWithBlock:^{ + [realm deleteObject:arrayOfAll.array.lastObject]; + }]; + XCTAssertEqualObjects([results[0] stringCol], @"c"); +} + +- (void)testQueryingSortedQueryPreservesOrder { + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + for (int i = 0; i < 5; ++i) { + [IntObject createInRealm:realm withValue:@[@(i)]]; + } + + ArrayPropertyObject *array = [ArrayPropertyObject createInRealm:realm withValue:@[@"name", @[], [IntObject allObjects]]]; + [realm commitWriteTransaction]; + + RLMResults *asc = [IntObject.allObjects sortedResultsUsingProperty:@"intCol" ascending:YES]; + RLMResults *desc = [IntObject.allObjects sortedResultsUsingProperty:@"intCol" ascending:NO]; + + // sanity check; would work even without sort order being preserved + XCTAssertEqual(2, [[[asc objectsWhere:@"intCol >= 2"] firstObject] intCol]); + + // check query on allObjects and query on query + XCTAssertEqual(4, [[[desc objectsWhere:@"intCol >= 2"] firstObject] intCol]); + XCTAssertEqual(3, [[[[desc objectsWhere:@"intCol >= 2"] objectsWhere:@"intCol < 4"] firstObject] intCol]); + + // same thing but on an linkview + asc = [array.intArray sortedResultsUsingProperty:@"intCol" ascending:YES]; + desc = [array.intArray sortedResultsUsingProperty:@"intCol" ascending:NO]; + + XCTAssertEqual(2, [[[asc objectsWhere:@"intCol >= 2"] firstObject] intCol]); + XCTAssertEqual(4, [[[desc objectsWhere:@"intCol >= 2"] firstObject] intCol]); + XCTAssertEqual(3, [[[[desc objectsWhere:@"intCol >= 2"] objectsWhere:@"intCol < 4"] firstObject] intCol]); +} + +- (void)testDynamicQueryInvalidClass +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + // class not derived from RLMObject + XCTAssertThrows([realm objects:@"NonRealmPersonObject" where:@"age > 25"], @"invalid object type"); + XCTAssertThrows([[realm objects:@"NonRealmPersonObject" where:@"age > 25"] sortedResultsUsingProperty:@"age" ascending:YES], @"invalid object type"); + + // empty string for class name + XCTAssertThrows([realm objects:@"" where:@"age > 25"], @"missing class name"); + XCTAssertThrows([[realm objects:@"" where:@"age > 25"] sortedResultsUsingProperty:@"age" ascending:YES], @"missing class name"); + + // nil class name + XCTAssertThrows([realm objects:nil where:@"age > 25"], @"nil class name"); + XCTAssertThrows([[realm objects:nil where:@"age > 25"] sortedResultsUsingProperty:@"age" ascending:YES], @"nil class name"); +} + +- (void)testPredicateValidUse +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + // boolean false + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == no"], @"== no"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == No"], @"== No"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == NO"], @"== NO"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == false"], @"== false"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == False"], @"== False"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == FALSE"], @"== FALSE"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == 0"], @"== 0"); + + // boolean true + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == yes"], @"== yes"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == Yes"], @"== Yes"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == YES"], @"== YES"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == true"], @"== true"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == True"], @"== True"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == TRUE"], @"== TRUE"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == 1"], @"== 1"); + + // inequality + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol != YES"], @"!= YES"); + XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol <> YES"], @"<> YES"); +} + +- (void)testPredicateNotSupported +{ + // These are things which are valid predicates, but which we do not support + + // Aggregate operators on non-arrays + XCTAssertThrows([PersonObject objectsWhere:@"ANY age > 5"]); + XCTAssertThrows([PersonObject objectsWhere:@"ALL age > 5"]); + XCTAssertThrows([PersonObject objectsWhere:@"SOME age > 5"]); + XCTAssertThrows([PersonObject objectsWhere:@"NONE age > 5"]); + + // nil on LHS of comparison with nullable property + XCTAssertThrows([AllOptionalTypes objectsWhere:@"nil = boolObj"]); + XCTAssertThrows([AllOptionalTypes objectsWhere:@"nil = intObj"]); + XCTAssertThrows([AllOptionalTypes objectsWhere:@"nil = floatObj"]); + XCTAssertThrows([AllOptionalTypes objectsWhere:@"nil = doubleObj"]); + XCTAssertThrows([AllOptionalTypes objectsWhere:@"nil = string"]); + XCTAssertThrows([AllOptionalTypes objectsWhere:@"nil = data"]); + XCTAssertThrows([AllOptionalTypes objectsWhere:@"nil = date"]); + + // comparing two constants + XCTAssertThrows([PersonObject objectsWhere:@"5 = 5"]); + XCTAssertThrows([PersonObject objectsWhere:@"nil = nil"]); + + // substring operations with constant on LHS + XCTAssertThrows([AllOptionalTypes objectsWhere:@"'' CONTAINS string"]); + XCTAssertThrows([AllOptionalTypes objectsWhere:@"'' BEGINSWITH string"]); + XCTAssertThrows([AllOptionalTypes objectsWhere:@"'' ENDSWITH string"]); + XCTAssertThrows(([AllOptionalTypes objectsWhere:@"%@ CONTAINS data", [NSData data]])); + + // data is missing stuff + XCTAssertThrows([AllOptionalTypes objectsWhere:@"data = data"]); + XCTAssertThrows(([LinkToAllTypesObject objectsWhere:@"%@ = allTypesCol.binaryCol", [NSData data]])); + XCTAssertThrows(([LinkToAllTypesObject objectsWhere:@"allTypesCol.binaryCol CONTAINS %@", [NSData data]])); + + // LinkList equality is unsupport since the semantics are unclear + XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"ANY array = array"])); + + // subquery + XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"SUBQUERY(array, $obj, $obj.intCol = 5).@count > 1"])); +} + +- (void)testPredicateMisuse +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + NSString *className = PersonObject.className; + + // invalid column/property name + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"height > 72"], @"'height' not found in .* 'PersonObject'"); + + // wrong/invalid data types + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"age != xyz"], @"'xyz' not found in .* 'PersonObject'"); + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"name == 3"], @"type string .* property 'name' .* 'PersonObject'.*: 3"); + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"age IN {'xyz'}"], @"type int .* property 'age' .* 'PersonObject'.*: xyz"); + XCTAssertThrows([realm objects:className where:@"name IN {3}"], @"invalid type"); + + className = AllTypesObject.className; + + XCTAssertThrows([realm objects:className where:@"boolCol == Foo"], @"invalid type"); + XCTAssertThrows([realm objects:className where:@"boolCol == 2"], @"invalid type"); + XCTAssertThrows([realm objects:className where:@"dateCol == 7"], @"invalid type"); + XCTAssertThrows([realm objects:className where:@"doubleCol == The"], @"invalid type"); + XCTAssertThrows([realm objects:className where:@"floatCol == Bar"], @"invalid type"); + XCTAssertThrows([realm objects:className where:@"intCol == Baz"], @"invalid type"); + + className = PersonObject.className; + + // compare two constants + XCTAssertThrows([realm objects:className where:@"3 == 3"], @"comparing 2 constants"); + + // invalid strings + RLMAssertThrowsWithReasonMatching([realm objects:className where:@""], @"Unable to parse"); + XCTAssertThrows([realm objects:className where:@"age"], @"column name only"); + XCTAssertThrows([realm objects:className where:@"sdlfjasdflj"], @"gibberish"); + XCTAssertThrows([realm objects:className where:@"age * 25"], @"invalid operator"); + XCTAssertThrows([realm objects:className where:@"age === 25"], @"invalid operator"); + XCTAssertThrows([realm objects:className where:@","], @"comma"); + XCTAssertThrows([realm objects:className where:@"()"], @"parens"); + + // not a link column + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"age.age == 25"], @"'age' is not a link .* 'PersonObject'"); + XCTAssertThrows([realm objects:className where:@"age.age.age == 25"]); + + // abuse of BETWEEN + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"age BETWEEN 25"], @"type NSArray for BETWEEN"); + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"age BETWEEN Foo"], @"BETWEEN operator must compare a KeyPath with an aggregate"); + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"age BETWEEN {age, age}"], @"must be constant values"); + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"age BETWEEN {age, 0}"], @"must be constant values"); + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"age BETWEEN {0, age}"], @"must be constant values"); + RLMAssertThrowsWithReasonMatching([realm objects:className where:@"age BETWEEN {0, {1, 10}}"], @"must be constant values"); + + NSPredicate *pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @[@1]]; + RLMAssertThrowsWithReasonMatching([realm objects:className withPredicate:pred], @"exactly two objects"); + + pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @[@1, @2, @3]]; + RLMAssertThrowsWithReasonMatching([realm objects:className withPredicate:pred], @"exactly two objects"); + + pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @[@"Foo", @"Bar"]]; + RLMAssertThrowsWithReasonMatching([realm objects:className withPredicate:pred], @"type int for BETWEEN"); + + pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @[@1.5, @2.5]]; + RLMAssertThrowsWithReasonMatching([realm objects:className withPredicate:pred], @"type int for BETWEEN"); + + pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @[@1, @[@2, @3]]]; + RLMAssertThrowsWithReasonMatching([realm objects:className withPredicate:pred], @"type int for BETWEEN"); + + pred = [NSPredicate predicateWithFormat:@"age BETWEEN %@", @{@25 : @35}]; + RLMAssertThrowsWithReasonMatching([realm objects:className withPredicate:pred], @"type NSArray for BETWEEN"); + + pred = [NSPredicate predicateWithFormat:@"height BETWEEN %@", @[@25, @35]]; + RLMAssertThrowsWithReasonMatching([realm objects:className withPredicate:pred], @"'height' not found .* 'PersonObject'"); + + // bad type in link IN + XCTAssertThrows([PersonLinkObject objectsInRealm:realm where:@"person.age IN {'Tim'}"]); +} + +- (void)testTwoColumnComparison +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @YES, @1, @2, @23.0f, @1.7f, @0.0, @5.55, @"a", @"a"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @NO, @1, @3, @-5.3f, @4.21f, @1.0, @4.44, @"a", @"A"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@NO, @NO, @2, @2, @1.0f, @3.55f, @99.9, @6.66, @"a", @"ab"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@NO, @YES, @3, @6, @4.21f, @1.0f, @1.0, @7.77, @"a", @"AB"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @YES, @4, @5, @23.0f, @23.0f, @7.4, @8.88, @"a", @"b"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @NO, @15, @8, @1.0f, @66.0f, @1.01, @9.99, @"a", @"ba"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@NO, @YES, @15, @15, @1.0f, @66.0f, @1.01, @9.99, @"a", @"BA"]]; + + [realm commitWriteTransaction]; + + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"bool1 == bool1"].count); + XCTAssertEqual(3U, [self.queryObjectClass objectsWhere:@"bool1 == bool2"].count); + XCTAssertEqual(4U, [self.queryObjectClass objectsWhere:@"bool1 != bool2"].count); + + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"int1 == int1"].count); + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"int1 == int2"].count); + XCTAssertEqual(5U, [self.queryObjectClass objectsWhere:@"int1 != int2"].count); + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"int1 > int2"].count); + XCTAssertEqual(4U, [self.queryObjectClass objectsWhere:@"int1 < int2"].count); + XCTAssertEqual(3U, [self.queryObjectClass objectsWhere:@"int1 >= int2"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"int1 <= int2"].count); + + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"float1 == float1"].count); + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"float1 == float2"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"float1 != float2"].count); + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"float1 > float2"].count); + XCTAssertEqual(4U, [self.queryObjectClass objectsWhere:@"float1 < float2"].count); + XCTAssertEqual(3U, [self.queryObjectClass objectsWhere:@"float1 >= float2"].count); + XCTAssertEqual(5U, [self.queryObjectClass objectsWhere:@"float1 <= float2"].count); + + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"double1 == double1"].count); + XCTAssertEqual(0U, [self.queryObjectClass objectsWhere:@"double1 == double2"].count); + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"double1 != double2"].count); + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"double1 > double2"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"double1 < double2"].count); + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"double1 >= double2"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"double1 <= double2"].count); + + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"string1 == string1"].count); + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"string1 == string2"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"string1 != string2"].count); + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"string1 CONTAINS string1"].count); + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"string1 CONTAINS string2"].count); + XCTAssertEqual(3U, [self.queryObjectClass objectsWhere:@"string2 CONTAINS string1"].count); + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"string1 BEGINSWITH string1"].count); + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"string1 BEGINSWITH string2"].count); + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"string2 BEGINSWITH string1"].count); + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"string1 ENDSWITH string1"].count); + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"string1 ENDSWITH string2"].count); + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"string2 ENDSWITH string1"].count); + + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"string1 ==[c] string1"].count); + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"string1 ==[c] string2"].count); + XCTAssertEqual(5U, [self.queryObjectClass objectsWhere:@"string1 !=[c] string2"].count); + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"string1 CONTAINS[c] string1"].count); + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"string1 CONTAINS[c] string2"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"string2 CONTAINS[c] string1"].count); + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"string1 BEGINSWITH[c] string1"].count); + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"string1 BEGINSWITH[c] string2"].count); + XCTAssertEqual(4U, [self.queryObjectClass objectsWhere:@"string2 BEGINSWITH[c] string1"].count); + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"string1 ENDSWITH[c] string1"].count); + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"string1 ENDSWITH[c] string2"].count); + XCTAssertEqual(4U, [self.queryObjectClass objectsWhere:@"string2 ENDSWITH[c] string1"].count); + + RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"int1 == float1"], + @"Property type mismatch between int and float"); + RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"float2 >= double1"], + @"Property type mismatch between float and double"); + RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"double2 <= int2"], + @"Property type mismatch between double and int"); + RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"int2 != string1"], + @"Property type mismatch between int and string"); + RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"float1 > string1"], + @"Property type mismatch between float and string"); + RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"double1 < string1"], + @"Property type mismatch between double and string"); +} + +- (void)testValidOperatorsInNumericComparison:(NSString *) comparisonType + withProposition:(BOOL(^)(NSPredicateOperatorType)) proposition +{ + NSPredicateOperatorType validOps[] = { + NSLessThanPredicateOperatorType, + NSLessThanOrEqualToPredicateOperatorType, + NSGreaterThanPredicateOperatorType, + NSGreaterThanOrEqualToPredicateOperatorType, + NSEqualToPredicateOperatorType, + NSNotEqualToPredicateOperatorType + }; + + for (NSUInteger i = 0; i < sizeof(validOps) / sizeof(NSPredicateOperatorType); ++i) + { + XCTAssert(proposition(validOps[i]), + @"%@ operator in %@ comparison.", + [RLMPredicateUtil predicateOperatorTypeString:validOps[i]], + comparisonType); + } +} + +- (void)testValidOperatorsInNumericComparison +{ + [self testValidOperatorsInNumericComparison:@"integer" + withProposition:[RLMPredicateUtil isEmptyIntColPredicate]]; + [self testValidOperatorsInNumericComparison:@"float" + withProposition:[RLMPredicateUtil isEmptyFloatColPredicate]]; + [self testValidOperatorsInNumericComparison:@"double" + withProposition:[RLMPredicateUtil isEmptyDoubleColPredicate]]; + [self testValidOperatorsInNumericComparison:@"date" + withProposition:[RLMPredicateUtil isEmptyDateColPredicate]]; +} + +- (void)testInvalidOperatorsInNumericComparison:(NSString *) comparisonType + withProposition:(BOOL(^)(NSPredicateOperatorType)) proposition +{ + NSPredicateOperatorType invalidOps[] = { + NSMatchesPredicateOperatorType, + NSLikePredicateOperatorType, + NSBeginsWithPredicateOperatorType, + NSEndsWithPredicateOperatorType, + NSContainsPredicateOperatorType + }; + + for (NSUInteger i = 0; i < sizeof(invalidOps) / sizeof(NSPredicateOperatorType); ++i) + { + XCTAssertThrowsSpecificNamed(proposition(invalidOps[i]), NSException, + @"Invalid operator type", + @"%@ operator invalid in %@ comparison.", + [RLMPredicateUtil predicateOperatorTypeString:invalidOps[i]], + comparisonType); + } +} + +- (void)testInvalidOperatorsInNumericComparison +{ + [self testInvalidOperatorsInNumericComparison:@"integer" + withProposition:[RLMPredicateUtil isEmptyIntColPredicate]]; + [self testInvalidOperatorsInNumericComparison:@"float" + withProposition:[RLMPredicateUtil isEmptyFloatColPredicate]]; + [self testInvalidOperatorsInNumericComparison:@"double" + withProposition:[RLMPredicateUtil isEmptyDoubleColPredicate]]; + [self testInvalidOperatorsInNumericComparison:@"date" + withProposition:[RLMPredicateUtil isEmptyDateColPredicate]]; +} + +- (void)testCustomSelectorsInNumericComparison:(NSString *) comparisonType + withProposition:(BOOL(^)()) proposition +{ + XCTAssertThrowsSpecificNamed(proposition(), NSException, + @"Invalid operator type", + @"Custom selector invalid in %@ comparison.", comparisonType); +} + +- (void)testCustomSelectorsInNumericComparison +{ + BOOL (^isEmpty)(); + + isEmpty = [RLMPredicateUtil alwaysEmptyIntColSelectorPredicate]; + [self testCustomSelectorsInNumericComparison:@"integer" withProposition:isEmpty]; + + isEmpty = [RLMPredicateUtil alwaysEmptyFloatColSelectorPredicate]; + [self testCustomSelectorsInNumericComparison:@"float" withProposition:isEmpty]; + + isEmpty = [RLMPredicateUtil alwaysEmptyDoubleColSelectorPredicate]; + [self testCustomSelectorsInNumericComparison:@"double" withProposition:isEmpty]; + + isEmpty = [RLMPredicateUtil alwaysEmptyDateColSelectorPredicate]; + [self testCustomSelectorsInNumericComparison:@"date" withProposition:isEmpty]; +} + +- (void)testBooleanPredicate +{ + XCTAssertEqual([BoolObject objectsWhere:@"boolCol == TRUE"].count, + 0U, @"== operator in bool predicate."); + XCTAssertEqual([BoolObject objectsWhere:@"boolCol != TRUE"].count, + 0U, @"== operator in bool predicate."); + if (self.isNull) { + XCTAssertEqual([BoolObject objectsWhere:@"boolCol == NULL"].count, + 0U, @"== operator in bool predicate."); + XCTAssertEqual([BoolObject objectsWhere:@"boolCol != NULL"].count, + 0U, @"== operator in bool predicate."); + } + else { + XCTAssertThrows([BoolObject objectsWhere:@"boolCol == NULL"]); + XCTAssertThrows([BoolObject objectsWhere:@"boolCol != NULL"]); + } + + XCTAssertThrowsSpecificNamed([BoolObject objectsWhere:@"boolCol >= TRUE"], + NSException, + @"Invalid operator type", + @"Invalid operator in bool predicate."); +} + +- (void)testStringBeginsWith +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + if (self.isNull) { + so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + } + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'a'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'ab'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abcd'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abd'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'c'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'A'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH[c] 'a'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH[c] 'A'"].count); + + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'a'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'c'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'A'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH[c] 'a'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH[c] 'A'"].count); +} + +- (void)testStringEndsWith +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + if (self.isNull) { + so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + } + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'c'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'bc'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'abc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'aabc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'bbc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'a'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'C'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH[c] 'c'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH[c] 'C'"].count); + + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'c'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'a'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'C'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH[c] 'c'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH[c] 'C'"].count); +} + +- (void)testStringContains +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + if (self.isNull) { + so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + } + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'a'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'b'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'c'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'ab'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'bc'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'abc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'd'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'aabc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'bbc'"].count); + + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'C'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS[c] 'c'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS[c] 'C'"].count); + + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'd'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'c'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'C'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS[c] 'c'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS[c] 'C'"].count); +} + +- (void)testStringEquality +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol == 'abc'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol != 'def'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ==[c] 'abc'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ==[c] 'ABC'"].count); + + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol != 'abc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol == 'def'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol == 'ABC'"].count); + + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'abc'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol != 'def'"].count); + + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ==[c] 'abc'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ==[c] 'ABC'"].count); + + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol != 'abc'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'def'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'ABC'"].count); +} + +- (void)testStringUnsupportedOperations +{ + XCTAssertThrows([StringObject objectsWhere:@"stringCol LIKE 'abc'"]); + XCTAssertThrows([StringObject objectsWhere:@"stringCol MATCHES 'abc'"]); + XCTAssertThrows([StringObject objectsWhere:@"stringCol BETWEEN {'a', 'b'}"]); + XCTAssertThrows([StringObject objectsWhere:@"stringCol < 'abc'"]); + + XCTAssertThrows([AllTypesObject objectsWhere:@"objectCol.stringCol LIKE 'abc'"]); + XCTAssertThrows([AllTypesObject objectsWhere:@"objectCol.stringCol MATCHES 'abc'"]); + XCTAssertThrows([AllTypesObject objectsWhere:@"objectCol.stringCol BETWEEN {'a', 'b'}"]); + XCTAssertThrows([AllTypesObject objectsWhere:@"objectCol.stringCol < 'abc'"]); +} + +- (void)testBinaryComparisonInPredicate +{ + NSExpression *binary = [NSExpression expressionForConstantValue:[[NSData alloc] init]]; + + NSUInteger (^count)(NSPredicateOperatorType) = ^(NSPredicateOperatorType type) { + NSPredicate *pred = [RLMPredicateUtil comparisonWithKeyPath: @"binaryCol" + expression: binary + operatorType: type]; + return [BinaryObject objectsWithPredicate: pred].count; + }; + + XCTAssertEqual(count(NSBeginsWithPredicateOperatorType), 0U, + @"BEGINSWITH operator in binary comparison."); + XCTAssertEqual(count(NSEndsWithPredicateOperatorType), 0U, + @"ENDSWITH operator in binary comparison."); + XCTAssertEqual(count(NSContainsPredicateOperatorType), 0U, + @"CONTAINS operator in binary comparison."); + XCTAssertEqual(count(NSEqualToPredicateOperatorType), 0U, + @"= or == operator in binary comparison."); + XCTAssertEqual(count(NSNotEqualToPredicateOperatorType), 0U, + @"!= or <> operator in binary comparison."); + + // Invalid operators. + XCTAssertThrowsSpecificNamed(count(NSLessThanPredicateOperatorType), NSException, + @"Invalid operator type", + @"Invalid operator in binary comparison."); +} + +- (void)testKeyPathLocationInComparison +{ + NSExpression *keyPath = [NSExpression expressionForKeyPath:@"intCol"]; + NSExpression *expr = [NSExpression expressionForConstantValue:@0]; + NSPredicate *predicate; + + predicate = [RLMPredicateUtil defaultPredicateGenerator](keyPath, expr); + XCTAssert([RLMPredicateUtil isEmptyIntColWithPredicate:predicate], + @"Key path to the left in an integer comparison."); + + predicate = [RLMPredicateUtil defaultPredicateGenerator](expr, keyPath); + XCTAssert([RLMPredicateUtil isEmptyIntColWithPredicate:predicate], + @"Key path to the right in an integer comparison."); + + predicate = [RLMPredicateUtil defaultPredicateGenerator](keyPath, keyPath); + XCTAssert([RLMPredicateUtil isEmptyIntColWithPredicate:predicate], + @"Key path in both locations in an integer comparison."); + + predicate = [RLMPredicateUtil defaultPredicateGenerator](expr, expr); + XCTAssertThrowsSpecificNamed([RLMPredicateUtil isEmptyIntColWithPredicate:predicate], + NSException, @"Invalid predicate expressions", + @"Key path in absent in an integer comparison."); +} + +- (void)testFloatQuery +{ + RLMRealm *realm = [self realmWithTestPath]; + + [realm beginWriteTransaction]; + [FloatObject createInRealm:realm withValue:@[@1.7f]]; + [realm commitWriteTransaction]; + + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol > 1"] count]), 1U, @"1 object expected"); + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol > %d", 1] count]), 1U, @"1 object expected"); + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol = 1.7"] count]), 1U, @"1 object expected"); + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol = %f", 1.7f] count]), 1U, @"1 object expected"); + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol > 1.0"] count]), 1U, @"1 object expected"); + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol >= 1.0"] count]), 1U, @"1 object expected"); + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol < 1.0"] count]), 0U, @"0 objects expected"); + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol <= 1.0"] count]), 0U, @"0 objects expected"); + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol BETWEEN %@", @[@1.0, @2.0]] count]), 1U, @"1 object expected"); + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol = %e", 1.7] count]), 1U, @"1 object expected"); + XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol == %f", FLT_MAX] count]), 0U, @"0 objects expected"); + XCTAssertThrows(([[realm objects:[FloatObject className] where:@"floatCol = 3.5e+38"] count]), @"Too large to be a float"); + XCTAssertThrows(([[realm objects:[FloatObject className] where:@"floatCol = -3.5e+38"] count]), @"Too small to be a float"); +} + +- (void)testLiveQueriesInsideTransaction +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + { + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @YES, @1, @2, @23.0f, @1.7f, @0.0, @5.55, @"", @""]]; + + RLMResults *resultsQuery = [self.queryObjectClass objectsWhere:@"bool1 = YES"]; + RLMResults *resultsTableView = [self.queryObjectClass objectsWhere:@"bool1 = YES"]; + + // Force resultsTableView to form the TableView to verify that it syncs + // correctly, and don't call anything but count on resultsQuery so that + // it always reruns the query count method + (void)[resultsTableView firstObject]; + + XCTAssertEqual(resultsQuery.count, 1U); + XCTAssertEqual(resultsTableView.count, 1U); + + // Delete the (only) object in result set + [realm deleteObject:[resultsTableView lastObject]]; + XCTAssertEqual(resultsQuery.count, 0U); + XCTAssertEqual(resultsTableView.count, 0U); + + // Add an object that does not match query + QueryObject *q1 = [self.queryObjectClass createInRealm:realm withValue:@[@NO, @YES, @1, @2, @23.0f, @1.7f, @0.0, @5.55, @"", @""]]; + XCTAssertEqual(resultsQuery.count, 0U); + XCTAssertEqual(resultsTableView.count, 0U); + + // Change object to match query + q1[@"bool1"] = @YES; + XCTAssertEqual(resultsQuery.count, 1U); + XCTAssertEqual(resultsTableView.count, 1U); + + // Add another object that matches + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @NO, @1, @3, @-5.3f, @4.21f, @1.0, @4.44, @"", @""]]; + XCTAssertEqual(resultsQuery.count, 2U); + XCTAssertEqual(resultsTableView.count, 2U); + } + [realm commitWriteTransaction]; +} + +- (void)testLiveQueriesBetweenTransactions +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @YES, @1, @2, @23.0f, @1.7f, @0.0, @5.55, @"", @""]]; + [realm commitWriteTransaction]; + + RLMResults *resultsQuery = [self.queryObjectClass objectsWhere:@"bool1 = YES"]; + RLMResults *resultsTableView = [self.queryObjectClass objectsWhere:@"bool1 = YES"]; + + // Force resultsTableView to form the TableView to verify that it syncs + // correctly, and don't call anything but count on resultsQuery so that + // it always reruns the query count method + (void)[resultsTableView firstObject]; + + XCTAssertEqual(resultsQuery.count, 1U); + XCTAssertEqual(resultsTableView.count, 1U); + + // Delete the (only) object in result set + [realm beginWriteTransaction]; + [realm deleteObject:[resultsTableView lastObject]]; + [realm commitWriteTransaction]; + + XCTAssertEqual(resultsQuery.count, 0U); + XCTAssertEqual(resultsTableView.count, 0U); + + // Add an object that does not match query + [realm beginWriteTransaction]; + QueryObject *q1 = [self.queryObjectClass createInRealm:realm withValue:@[@NO, @YES, @1, @2, @23.0f, @1.7f, @0.0, @5.55, @"", @""]]; + [realm commitWriteTransaction]; + + XCTAssertEqual(resultsQuery.count, 0U); + XCTAssertEqual(resultsTableView.count, 0U); + + // Change object to match query + [realm beginWriteTransaction]; + q1[@"bool1"] = @YES; + [realm commitWriteTransaction]; + + XCTAssertEqual(resultsQuery.count, 1U); + XCTAssertEqual(resultsTableView.count, 1U); + + // Add another object that matches + [realm beginWriteTransaction]; + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @NO, @1, @3, @-5.3f, @4.21f, @1.0, @4.44, @"", @""]]; + [realm commitWriteTransaction]; + + XCTAssertEqual(resultsQuery.count, 2U); + XCTAssertEqual(resultsTableView.count, 2U); +} + +- (void)makeDogWithName:(NSString *)name owner:(NSString *)ownerName { + RLMRealm *realm = [self realmWithTestPath]; + + OwnerObject *owner = [[OwnerObject alloc] init]; + owner.name = ownerName; + owner.dog = [[DogObject alloc] init]; + owner.dog.dogName = name; + + [realm beginWriteTransaction]; + [realm addObject:owner]; + [realm commitWriteTransaction]; +} + +- (void)makeDogWithAge:(int)age owner:(NSString *)ownerName { + RLMRealm *realm = [self realmWithTestPath]; + + OwnerObject *owner = [[OwnerObject alloc] init]; + owner.name = ownerName; + owner.dog = [[DogObject alloc] init]; + owner.dog.dogName = @""; + owner.dog.age = age; + + [realm beginWriteTransaction]; + [realm addObject:owner]; + [realm commitWriteTransaction]; +} + +- (void)testLinkQueryString +{ + RLMRealm *realm = [self realmWithTestPath]; + + [self makeDogWithName:@"Harvie" owner:@"Tim"]; + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'Harvie'"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName != 'Harvie'"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'eivraH'"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'Fido'"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName IN {'Fido', 'Harvie'}"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName IN {'Fido', 'eivraH'}"].count), 0U); + + [self makeDogWithName:@"Harvie" owner:@"Joe"]; + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'Harvie'"].count), 2U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName != 'Harvie'"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'eivraH'"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'Fido'"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName IN {'Fido', 'Harvie'}"].count), 2U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName IN {'Fido', 'eivraH'}"].count), 0U); + + [self makeDogWithName:@"Fido" owner:@"Jim"]; + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'Harvie'"].count), 2U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName != 'Harvie'"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'eivraH'"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'Fido'"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName IN {'Fido', 'Harvie'}"].count), 3U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName IN {'Fido', 'eivraH'}"].count), 1U); + + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'Harvie' and name = 'Tim'"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.dogName = 'Harvie' and name = 'Jim'"].count), 0U); + + // test invalid operators + XCTAssertThrows([realm objects:[OwnerObject className] where:@"dog.dogName > 'Harvie'"], @"Invalid operator should throw"); +} + +- (void)testLinkQueryInt +{ + RLMRealm *realm = [self realmWithTestPath]; + + [self makeDogWithAge:5 owner:@"Tim"]; + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age = 5"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age != 5"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age = 10"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age = 8"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age IN {5, 8}"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age IN {8, 10}"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age BETWEEN {0, 10}"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age BETWEEN {0, 7}"].count), 1U); + + [self makeDogWithAge:5 owner:@"Joe"]; + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age = 5"].count), 2U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age != 5"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age = 10"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age = 8"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age IN {5, 8}"].count), 2U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age IN {8, 10}"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age BETWEEN {0, 10}"].count), 2U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age BETWEEN {0, 7}"].count), 2U); + + [self makeDogWithAge:8 owner:@"Jim"]; + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age = 5"].count), 2U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age != 5"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age = 10"].count), 0U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age = 8"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age IN {5, 8}"].count), 3U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age IN {8, 10}"].count), 1U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age BETWEEN {0, 10}"].count), 3U); + XCTAssertEqual(([OwnerObject objectsInRealm:realm where:@"dog.age BETWEEN {0, 7}"].count), 2U); +} + +- (void)testLinkQueryAllTypes +{ + RLMRealm *realm = [self realmWithTestPath]; + + NSDate *now = [NSDate dateWithTimeIntervalSince1970:100000]; + + LinkToAllTypesObject *linkToAllTypes = [[LinkToAllTypesObject alloc] init]; + linkToAllTypes.allTypesCol = [[AllTypesObject alloc] init]; + linkToAllTypes.allTypesCol.boolCol = YES; + linkToAllTypes.allTypesCol.intCol = 1; + linkToAllTypes.allTypesCol.floatCol = 1.1f; + linkToAllTypes.allTypesCol.doubleCol = 1.11; + linkToAllTypes.allTypesCol.stringCol = @"string"; + linkToAllTypes.allTypesCol.binaryCol = [NSData dataWithBytes:"a" length:1]; + linkToAllTypes.allTypesCol.dateCol = now; + linkToAllTypes.allTypesCol.cBoolCol = YES; + linkToAllTypes.allTypesCol.longCol = 11; + linkToAllTypes.allTypesCol.mixedCol = @0; + StringObject *obj = [[StringObject alloc] initWithValue:@[@"string"]]; + linkToAllTypes.allTypesCol.objectCol = obj; + + [realm beginWriteTransaction]; + [realm addObject:linkToAllTypes]; + [realm commitWriteTransaction]; + + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.boolCol = YES"] count], 1U); + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.boolCol = NO"] count], 0U); + + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.intCol = 1"] count], 1U); + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.intCol != 1"] count], 0U); + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.intCol > 0"] count], 1U); + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.intCol > 1"] count], 0U); + + NSPredicate *predEq = [NSPredicate predicateWithFormat:@"allTypesCol.floatCol = %f", 1.1]; + XCTAssertEqual([LinkToAllTypesObject objectsInRealm:realm withPredicate:predEq].count, 1U); + NSPredicate *predLessEq = [NSPredicate predicateWithFormat:@"allTypesCol.floatCol <= %f", 1.1]; + XCTAssertEqual([LinkToAllTypesObject objectsInRealm:realm withPredicate:predLessEq].count, 1U); + NSPredicate *predLess = [NSPredicate predicateWithFormat:@"allTypesCol.floatCol < %f", 1.1]; + XCTAssertEqual([LinkToAllTypesObject objectsInRealm:realm withPredicate:predLess].count, 0U); + + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.doubleCol = 1.11"] count], 1U); + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.doubleCol >= 1.11"] count], 1U); + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.doubleCol > 1.11"] count], 0U); + + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.longCol = 11"] count], 1U); + XCTAssertEqual([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.longCol != 11"] count], 0U); + + XCTAssertEqual(([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.dateCol = %@", now] count]), 1U); + XCTAssertEqual(([[realm objects:[LinkToAllTypesObject className] where:@"allTypesCol.dateCol != %@", now] count]), 0U); +} + +- (void)testLinkQueryInvalid { + XCTAssertThrows([LinkToAllTypesObject objectsWhere:@"allTypesCol.binaryCol = 'a'"], @"Binary data not supported"); + XCTAssertThrows([LinkToAllTypesObject objectsWhere:@"allTypesCol.mixedCol = 'a'"], @"Mixed data not supported"); + XCTAssertThrows([LinkToAllTypesObject objectsWhere:@"allTypesCol.invalidCol = 'a'"], @"Invalid column name should throw"); + + XCTAssertThrows([LinkToAllTypesObject objectsWhere:@"allTypesCol.longCol = 'a'"], @"Wrong data type should throw"); + + XCTAssertThrows([LinkToAllTypesObject objectsWhere:@"intArray.intCol > 5"], @"RLMArray query without ANY modifier should throw"); +} + + +- (void)testLinkQueryMany +{ + RLMRealm *realm = [self realmWithTestPath]; + + ArrayPropertyObject *arrPropObj1 = [[ArrayPropertyObject alloc] init]; + arrPropObj1.name = @"Test"; + for(NSUInteger i=0; i<10; i++) { + StringObject *sobj = [[StringObject alloc] init]; + sobj.stringCol = [NSString stringWithFormat:@"%lu", (unsigned long)i]; + [arrPropObj1.array addObject:sobj]; + IntObject *iobj = [[IntObject alloc] init]; + iobj.intCol = (int)i; + [arrPropObj1.intArray addObject:iobj]; + } + [realm beginWriteTransaction]; + [realm addObject:arrPropObj1]; + [realm commitWriteTransaction]; + + XCTAssertEqual([[realm objects:[ArrayPropertyObject className] where:@"ANY intArray.intCol > 10"] count], 0U); + XCTAssertEqual([[realm objects:[ArrayPropertyObject className] where:@"ANY intArray.intCol > 5"] count], 1U); + XCTAssertEqual([[realm objects:[ArrayPropertyObject className] where:@"ANY array.stringCol = '1'"] count], 1U); + XCTAssertEqual([realm objects:[ArrayPropertyObject className] where:@"NONE intArray.intCol == 5"].count, 0U); + XCTAssertEqual([realm objects:[ArrayPropertyObject className] where:@"NONE intArray.intCol > 10"].count, 1U); + + ArrayPropertyObject *arrPropObj2 = [[ArrayPropertyObject alloc] init]; + arrPropObj2.name = @"Test"; + for(NSUInteger i=0; i<4; i++) { + StringObject *sobj = [[StringObject alloc] init]; + sobj.stringCol = [NSString stringWithFormat:@"%lu", (unsigned long)i]; + [arrPropObj2.array addObject:sobj]; + IntObject *iobj = [[IntObject alloc] init]; + iobj.intCol = (int)i; + [arrPropObj2.intArray addObject:iobj]; + } + [realm beginWriteTransaction]; + [realm addObject:arrPropObj2]; + [realm commitWriteTransaction]; + XCTAssertEqual([[realm objects:[ArrayPropertyObject className] where:@"ANY intArray.intCol > 10"] count], 0U); + XCTAssertEqual([[realm objects:[ArrayPropertyObject className] where:@"ANY intArray.intCol > 5"] count], 1U); + XCTAssertEqual([[realm objects:[ArrayPropertyObject className] where:@"ANY intArray.intCol > 2"] count], 2U); + XCTAssertEqual([realm objects:[ArrayPropertyObject className] where:@"NONE intArray.intCol == 5"].count, 1U); + XCTAssertEqual([realm objects:[ArrayPropertyObject className] where:@"NONE intArray.intCol > 10"].count, 2U); +} + +- (void)testMultiLevelLinkQuery +{ + RLMRealm *realm = [self realmWithTestPath]; + + [realm beginWriteTransaction]; + CircleObject *circle = nil; + for (int i = 0; i < 5; ++i) { + circle = [CircleObject createInRealm:realm withValue:@{@"data": [NSString stringWithFormat:@"%d", i], + @"next": circle ?: NSNull.null}]; + } + [realm commitWriteTransaction]; + + XCTAssertTrue([circle isEqualToObject:[CircleObject objectsInRealm:realm where:@"data = '4'"].firstObject]); + XCTAssertTrue([circle isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.data = '3'"].firstObject]); + XCTAssertTrue([circle isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next.data = '2'"].firstObject]); + XCTAssertTrue([circle isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next.next.data = '1'"].firstObject]); + XCTAssertTrue([circle isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next.next.next.data = '0'"].firstObject]); + XCTAssertTrue([circle.next isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next.next.data = '0'"].firstObject]); + XCTAssertTrue([circle.next.next isEqualToObject:[CircleObject objectsInRealm:realm where:@"next.next.data = '0'"].firstObject]); + + XCTAssertNoThrow(([CircleObject objectsInRealm:realm where:@"next = %@", circle])); + XCTAssertThrows(([CircleObject objectsInRealm:realm where:@"next.next = %@", circle])); + XCTAssertTrue([circle.next.next.next.next isEqualToObject:[CircleObject objectsInRealm:realm where:@"next = nil"].firstObject]); +} + +- (void)testArrayMultiLevelLinkQuery +{ + RLMRealm *realm = [self realmWithTestPath]; + + [realm beginWriteTransaction]; + CircleObject *circle = nil; + for (int i = 0; i < 5; ++i) { + circle = [CircleObject createInRealm:realm withValue:@{@"data": [NSString stringWithFormat:@"%d", i], + @"next": circle ?: NSNull.null}]; + } + [CircleArrayObject createInRealm:realm withValue:@[[CircleObject allObjectsInRealm:realm]]]; + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [CircleArrayObject objectsInRealm:realm where:@"ANY circles.data = '4'"].count); + XCTAssertEqual(0U, [CircleArrayObject objectsInRealm:realm where:@"ANY circles.next.data = '4'"].count); + XCTAssertEqual(1U, [CircleArrayObject objectsInRealm:realm where:@"ANY circles.next.data = '3'"].count); + XCTAssertEqual(1U, [CircleArrayObject objectsInRealm:realm where:@"ANY circles.data = '3'"].count); + XCTAssertEqual(1U, [CircleArrayObject objectsInRealm:realm where:@"NONE circles.next.data = '4'"].count); + + XCTAssertEqual(0U, [CircleArrayObject objectsInRealm:realm where:@"ANY circles.next.next.data = '3'"].count); + XCTAssertEqual(1U, [CircleArrayObject objectsInRealm:realm where:@"ANY circles.next.next.data = '2'"].count); + XCTAssertEqual(1U, [CircleArrayObject objectsInRealm:realm where:@"ANY circles.next.data = '2'"].count); + XCTAssertEqual(1U, [CircleArrayObject objectsInRealm:realm where:@"ANY circles.data = '2'"].count); + XCTAssertEqual(1U, [CircleArrayObject objectsInRealm:realm where:@"NONE circles.next.next.data = '3'"].count); + + XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"ANY data = '2'"]); + XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"ANY circles.next = '2'"]); + XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"ANY data.circles = '2'"]); + XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"circles.data = '2'"]); + XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"NONE data.circles = '2'"]); +} + +- (void)testQueryWithObjects +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + NSDate *date1 = [NSDate date]; + NSDate *date2 = [date1 dateByAddingTimeInterval:1]; + NSDate *date3 = [date2 dateByAddingTimeInterval:1]; + + [realm beginWriteTransaction]; + + StringObject *stringObj0 = [StringObject createInRealm:realm withValue:@[@"string0"]]; + StringObject *stringObj1 = [StringObject createInRealm:realm withValue:@[@"string1"]]; + StringObject *stringObj2 = [StringObject createInRealm:realm withValue:@[@"string2"]]; + + AllTypesObject *obj0 = [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], date1, @YES, @1LL, @1, stringObj0]]; + AllTypesObject *obj1 = [AllTypesObject createInRealm:realm withValue:@[@YES, @2, @2.0f, @2.0, @"b", [@"b" dataUsingEncoding:NSUTF8StringEncoding], date2, @YES, @2LL, @"mixed", stringObj1]]; + AllTypesObject *obj2 = [AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @3LL, @"mixed", stringObj0]]; + AllTypesObject *obj3 = [AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @3LL, @"mixed", stringObj2]]; + AllTypesObject *obj4 = [AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @34359738368LL, @"mixed", NSNull.null]]; + + [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj0, obj1]]]; + [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj1]]]; + [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj0, obj2, obj3]]]; + [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj4]]]; + + [realm commitWriteTransaction]; + + // simple queries + XCTAssertEqual(2U, ([AllTypesObject objectsWhere:@"objectCol = %@", stringObj0].count)); + XCTAssertEqual(1U, ([AllTypesObject objectsWhere:@"objectCol = %@", stringObj1].count)); + XCTAssertEqual(1U, ([AllTypesObject objectsWhere:@"objectCol = nil"].count)); + XCTAssertEqual(4U, ([AllTypesObject objectsWhere:@"objectCol != nil"].count)); + XCTAssertEqual(3U, ([AllTypesObject objectsWhere:@"objectCol != %@", stringObj0].count)); + + NSPredicate *longPred = [NSPredicate predicateWithFormat:@"longCol = %lli", 34359738368]; + XCTAssertEqual([AllTypesObject objectsWithPredicate:longPred].count, 1U, @"Count should be 1"); + + NSPredicate *longBetweenPred = [NSPredicate predicateWithFormat:@"longCol BETWEEN %@", @[@34359738367LL, @34359738369LL]]; + XCTAssertEqual([AllTypesObject objectsWithPredicate:longBetweenPred].count, 1U, @"Count should be 1"); + + // check for ANY object in array + XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"ANY array = %@", obj0].count)); + XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"ANY array != %@", obj1].count)); + XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"NONE array = %@", obj0].count)); + XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"NONE array != %@", obj1].count)); + XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"array = %@", obj0].count)); + XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"array != %@", obj0].count)); +} + +- (void)testCompoundOrQuery { + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; + [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; + [realm commitWriteTransaction]; + + XCTAssertEqual(2U, [[PersonObject objectsWhere:@"name == 'Ari' or age < 30"] count]); + XCTAssertEqual(1U, [[PersonObject objectsWhere:@"name == 'Ari' or age > 40"] count]); +} + +- (void)testCompoundAndQuery { + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; + [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [[PersonObject objectsWhere:@"name == 'Ari' and age > 30"] count]); + XCTAssertEqual(0U, [[PersonObject objectsWhere:@"name == 'Ari' and age > 40"] count]); +} + +- (void)testClass:(Class)class + withNormalCount:(NSUInteger)normalCount + notCount:(NSUInteger)notCount + where:(NSString *)predicateFormat, ... +{ + va_list args; + va_start(args, predicateFormat); + va_end(args); + XCTAssertEqual(normalCount, [[class objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]] count]); + predicateFormat = [NSString stringWithFormat:@"NOT(%@)", predicateFormat]; + va_start(args, predicateFormat); + va_end(args); + XCTAssertEqual(notCount, [[class objectsWithPredicate:[NSPredicate predicateWithFormat:predicateFormat arguments:args]] count]); +} + +- (void)testINPredicate +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"abc", [@"a" dataUsingEncoding:NSUTF8StringEncoding], [NSDate dateWithTimeIntervalSince1970:1], @YES, @1LL, @1, so]]; + [realm commitWriteTransaction]; + + // Tests for each type always follow: none, some, more + + //////////////////////// + // Literal Predicates + //////////////////////// + + // BOOL + [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"boolCol IN {NO}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"boolCol IN {YES}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"boolCol IN {NO, YES}"]; + + // int + [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"intCol IN {0, 2, 3}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"intCol IN {1}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"intCol IN {1, 2}"]; + + // float + [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"floatCol IN {0, 2, 3}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"floatCol IN {1}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"floatCol IN {1, 2}"]; + + // double + [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"doubleCol IN {0, 2, 3}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"doubleCol IN {1}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"doubleCol IN {1, 2}"]; + + // NSString + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"stringCol IN {'abc'}"]; + [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"stringCol IN {'def'}"]; + [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"stringCol IN {'ABC'}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"stringCol IN[c] {'abc'}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"stringCol IN[c] {'ABC'}"]; + + // NSData + // Can't represent NSData with NSPredicate literal. See format predicates below + + // NSDate + // Can't represent NSDate with NSPredicate literal. See format predicates below + + // bool + [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"cBoolCol IN {NO}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"cBoolCol IN {YES}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"cBoolCol IN {NO, YES}"]; + + // int64_t + [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"longCol IN {0, 2, 3}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"longCol IN {1}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"longCol IN {1, 2}"]; + + // mixed + // FIXME: Support IN predicates with mixed properties + XCTAssertThrows([AllTypesObject objectsWhere:@"mixedCol IN {0, 2, 3}"]); + XCTAssertThrows([AllTypesObject objectsWhere:@"NOT(mixedCol IN {0, 2, 3})"]); + + // string subobject + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"objectCol.stringCol IN {'abc'}"]; + [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"objectCol.stringCol IN {'def'}"]; + [self testClass:[AllTypesObject class] withNormalCount:0 notCount:1 where:@"objectCol.stringCol IN {'ABC'}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"objectCol.stringCol IN[c] {'abc'}"]; + [self testClass:[AllTypesObject class] withNormalCount:1 notCount:0 where:@"objectCol.stringCol IN[c] {'ABC'}"]; + + //////////////////////// + // Format Predicates + //////////////////////// + + // BOOL + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"boolCol IN %@", @[@NO]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"boolCol IN %@", @[@YES]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"boolCol IN %@", @[@NO, @YES]]; + + // int + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"intCol IN %@", @[@0, @2, @3]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"intCol IN %@", @[@1]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"intCol IN %@", @[@1, @2]]; + + // float + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"floatCol IN %@", @[@0, @2, @3]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"floatCol IN %@", @[@1]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"floatCol IN %@", @[@1, @2]]; + + // double + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"doubleCol IN %@", @[@0, @2, @3]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"doubleCol IN %@", @[@1]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"doubleCol IN %@", @[@1, @2]]; + + // NSString + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"stringCol IN %@", @[@"abc"]]; + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"stringCol IN %@", @[@"def"]]; + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"stringCol IN %@", @[@"ABC"]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"stringCol IN[c] %@", @[@"abc"]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"stringCol IN[c] %@", @[@"ABC"]]; + + // NSData + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"binaryCol IN %@", @[[@"" dataUsingEncoding:NSUTF8StringEncoding]]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"binaryCol IN %@", @[[@"a" dataUsingEncoding:NSUTF8StringEncoding]]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"binaryCol IN %@", @[[@"a" dataUsingEncoding:NSUTF8StringEncoding], [@"b" dataUsingEncoding:NSUTF8StringEncoding]]]; + + // NSDate + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"dateCol IN %@", @[[NSDate dateWithTimeIntervalSince1970:0]]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"dateCol IN %@", @[[NSDate dateWithTimeIntervalSince1970:1]]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"dateCol IN %@", @[[NSDate dateWithTimeIntervalSince1970:0], [NSDate dateWithTimeIntervalSince1970:1]]]; + + // bool + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"cBoolCol IN %@", @[@NO]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"cBoolCol IN %@", @[@YES]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"cBoolCol IN %@", @[@NO, @YES]]; + + // int64_t + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"longCol IN %@", @[@0, @2, @3]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"longCol IN %@", @[@1]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"longCol IN %@", @[@1, @2]]; + + // mixed + // FIXME: Support IN predicates with mixed properties + XCTAssertThrows(([[AllTypesObject objectsWhere:@"mixedCol IN %@", @[@0, @2, @3]] count])); + XCTAssertThrows(([[AllTypesObject objectsWhere:@"NOT(mixedCol IN %@)", @[@0, @2, @3]] count])); + + // string subobject + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"objectCol.stringCol IN %@", @[@"abc"]]; + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"objectCol.stringCol IN %@", @[@"def"]]; + [self testClass:[AllTypesObject class] withNormalCount:0U notCount:1U where:@"objectCol.stringCol IN %@", @[@"ABC"]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"objectCol.stringCol IN[c] %@", @[@"abc"]]; + [self testClass:[AllTypesObject class] withNormalCount:1U notCount:0U where:@"objectCol.stringCol IN[c] %@", @[@"ABC"]]; +} + +- (void)testArrayIn +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + [realm beginWriteTransaction]; + + ArrayPropertyObject *arr = [ArrayPropertyObject createInRealm:realm withValue:@[@"name", @[], @[]]]; + [arr.array addObject:[StringObject createInRealm:realm withValue:@[@"value"]]]; + [realm commitWriteTransaction]; + + + XCTAssertEqual(0U, ([[ArrayPropertyObject objectsWhere:@"ANY array.stringCol IN %@", @[@"missing"]] count])); + XCTAssertEqual(1U, ([[ArrayPropertyObject objectsWhere:@"ANY array.stringCol IN %@", @[@"value"]] count])); + XCTAssertEqual(1U, ([[ArrayPropertyObject objectsWhere:@"NONE array.stringCol IN %@", @[@"missing"]] count])); + XCTAssertEqual(0U, ([[ArrayPropertyObject objectsWhere:@"NONE array.stringCol IN %@", @[@"value"]] count])); + + XCTAssertEqual(0U, ([[ArrayPropertyObject objectsWhere:@"ANY array IN %@", [StringObject objectsWhere:@"stringCol = 'missing'"]] count])); + XCTAssertEqual(1U, ([[ArrayPropertyObject objectsWhere:@"ANY array IN %@", [StringObject objectsWhere:@"stringCol = 'value'"]] count])); + XCTAssertEqual(1U, ([[ArrayPropertyObject objectsWhere:@"NONE array IN %@", [StringObject objectsWhere:@"stringCol = 'missing'"]] count])); + XCTAssertEqual(0U, ([[ArrayPropertyObject objectsWhere:@"NONE array IN %@", [StringObject objectsWhere:@"stringCol = 'value'"]] count])); +} + +- (void)testQueryChaining { + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; + [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [[PersonObject objectsWhere:@"name == 'Ari'"] count]); + XCTAssertEqual(0U, [[PersonObject objectsWhere:@"name == 'Ari' and age == 29"] count]); + XCTAssertEqual(0U, [[[PersonObject objectsWhere:@"name == 'Ari'"] objectsWhere:@"age == 29"] count]); +} + +- (void)testLinkViewQuery { + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [CompanyObject createInRealm:realm + withValue:@[@"company name", @[@{@"name": @"John", @"age": @30, @"hired": @NO}, + @{@"name": @"Joe", @"age": @40, @"hired": @YES}, + @{@"name": @"Jill", @"age": @50, @"hired": @YES}]]]; + [realm commitWriteTransaction]; + + CompanyObject *co = [CompanyObject allObjects][0]; + XCTAssertEqual(1U, [co.employees objectsWhere:@"hired = NO"].count); + XCTAssertEqual(2U, [co.employees objectsWhere:@"hired = YES"].count); + XCTAssertEqual(1U, [co.employees objectsWhere:@"hired = YES AND age = 40"].count); + XCTAssertEqual(0U, [co.employees objectsWhere:@"hired = YES AND age = 30"].count); + XCTAssertEqual(3U, [co.employees objectsWhere:@"hired = YES OR age = 30"].count); + XCTAssertEqual(1U, [[co.employees objectsWhere:@"hired = YES"] objectsWhere:@"name = 'Joe'"].count); +} + +- (void)testLinkViewQueryLifetime { + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [CompanyObject createInRealm:realm + withValue:@[@"company name", @[@{@"name": @"John", @"age": @30, @"hired": @NO}, + @{@"name": @"Jill", @"age": @50, @"hired": @YES}]]]; + [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; + [realm commitWriteTransaction]; + + RLMResults *subarray = nil; + @autoreleasepool { + __attribute((objc_precise_lifetime)) CompanyObject *co = [CompanyObject allObjects][0]; + subarray = [co.employees objectsWhere:@"age = 40"]; + XCTAssertEqual(0U, subarray.count); + } + + [realm beginWriteTransaction]; + @autoreleasepool { + __attribute((objc_precise_lifetime)) CompanyObject *co = [CompanyObject allObjects][0]; + [co.employees addObject:[EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]]; + } + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, subarray.count); + XCTAssertEqualObjects(@"Joe", subarray[0][@"name"]); +} + +- (void)testLinkViewQueryLiveUpdate { + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [CompanyObject createInRealm:realm + withValue:@[@"company name", @[@{@"name": @"John", @"age": @30, @"hired": @NO}, + @{@"name": @"Jill", @"age": @40, @"hired": @YES}]]]; + EmployeeObject *eo = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Joe", @"age": @40, @"hired": @YES}]; + [realm commitWriteTransaction]; + + CompanyObject *co = CompanyObject.allObjects.firstObject; + RLMResults *basic = [co.employees objectsWhere:@"age = 40"]; + RLMResults *sort = [co.employees sortedResultsUsingProperty:@"name" ascending:YES]; + RLMResults *sortQuery = [[co.employees sortedResultsUsingProperty:@"name" ascending:YES] objectsWhere:@"age = 40"]; + RLMResults *querySort = [[co.employees objectsWhere:@"age = 40"] sortedResultsUsingProperty:@"name" ascending:YES]; + + XCTAssertEqual(1U, basic.count); + XCTAssertEqual(2U, sort.count); + XCTAssertEqual(1U, sortQuery.count); + XCTAssertEqual(1U, querySort.count); + + XCTAssertEqualObjects(@"Jill", [[basic lastObject] name]); + XCTAssertEqualObjects(@"Jill", [[sortQuery lastObject] name]); + XCTAssertEqualObjects(@"Jill", [[querySort lastObject] name]); + + [realm beginWriteTransaction]; + [co.employees addObject:eo]; + [realm commitWriteTransaction]; + + XCTAssertEqual(2U, basic.count); + XCTAssertEqual(3U, sort.count); + XCTAssertEqual(2U, sortQuery.count); + XCTAssertEqual(2U, querySort.count); + + XCTAssertEqualObjects(@"Joe", [[basic lastObject] name]); + XCTAssertEqualObjects(@"Joe", [[sortQuery lastObject] name]); + XCTAssertEqualObjects(@"Joe", [[querySort lastObject] name]); +} + +- (void)testConstantPredicates +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [PersonObject createInRealm:realm withValue:@[@"Fiel", @27]]; + [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; + [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; + [realm commitWriteTransaction]; + + RLMResults *all = [PersonObject objectsWithPredicate:[NSPredicate predicateWithValue:YES]]; + XCTAssertEqual(all.count, 3U, @"Expecting 3 results"); + + RLMResults *none = [PersonObject objectsWithPredicate:[NSPredicate predicateWithValue:NO]]; + XCTAssertEqual(none.count, 0U, @"Expecting 0 results"); +} + +- (void)testEmptyCompoundPredicates +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [PersonObject createInRealm:realm withValue:@[@"Fiel", @27]]; + [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; + [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; + [realm commitWriteTransaction]; + + RLMResults *all = [PersonObject objectsWithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:@[]]]; + XCTAssertEqual(all.count, 3U, @"Expecting 3 results"); + + RLMResults *none = [PersonObject objectsWithPredicate:[NSCompoundPredicate orPredicateWithSubpredicates:@[]]]; + XCTAssertEqual(none.count, 0U, @"Expecting 0 results"); +} + +- (void)testComparisonsWithKeyPathOnRHS +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @YES, @1, @2, @23.0f, @1.7f, @0.0, @5.55, @"a", @"a"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @NO, @1, @3, @-5.3f, @4.21f, @1.0, @4.44, @"a", @"A"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@NO, @NO, @2, @2, @1.0f, @3.55f, @99.9, @6.66, @"a", @"ab"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@NO, @YES, @3, @6, @4.21f, @1.0f, @1.0, @7.77, @"a", @"AB"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @YES, @4, @5, @23.0f, @23.0f, @7.4, @8.88, @"a", @"b"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@YES, @NO, @15, @8, @1.0f, @66.0f, @1.01, @9.99, @"a", @"ba"]]; + [self.queryObjectClass createInRealm:realm withValue:@[@NO, @YES, @15, @15, @1.0f, @66.0f, @1.01, @9.99, @"a", @"BA"]]; + + [realm commitWriteTransaction]; + + XCTAssertEqual(4U, [self.queryObjectClass objectsWhere:@"TRUE == bool1"].count); + XCTAssertEqual(3U, [self.queryObjectClass objectsWhere:@"TRUE != bool2"].count); + + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"1 == int1"].count); + XCTAssertEqual(5U, [self.queryObjectClass objectsWhere:@"2 != int2"].count); + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"2 > int1"].count); + XCTAssertEqual(4U, [self.queryObjectClass objectsWhere:@"2 < int1"].count); + XCTAssertEqual(3U, [self.queryObjectClass objectsWhere:@"2 >= int1"].count); + XCTAssertEqual(5U, [self.queryObjectClass objectsWhere:@"2 <= int1"].count); + + XCTAssertEqual(3U, [self.queryObjectClass objectsWhere:@"1.0 == float1"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"1.0 != float2"].count); + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"1.0 > float1"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"1.0 < float2"].count); + XCTAssertEqual(4U, [self.queryObjectClass objectsWhere:@"1.0 >= float1"].count); + XCTAssertEqual(7U, [self.queryObjectClass objectsWhere:@"1.0 <= float2"].count); + + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"1.0 == double1"].count); + XCTAssertEqual(5U, [self.queryObjectClass objectsWhere:@"1.0 != double1"].count); + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"5.0 > double2"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"5.0 < double2"].count); + XCTAssertEqual(2U, [self.queryObjectClass objectsWhere:@"5.55 >= double2"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"5.55 <= double2"].count); + + XCTAssertEqual(1U, [self.queryObjectClass objectsWhere:@"'a' == string2"].count); + XCTAssertEqual(6U, [self.queryObjectClass objectsWhere:@"'a' != string2"].count); + + RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"'Realm' CONTAINS string1"].count, + @"Operator 'CONTAINS' is not supported .* right side"); + RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"'Amazon' BEGINSWITH string2"].count, + @"Operator 'BEGINSWITH' is not supported .* right side"); + RLMAssertThrowsWithReasonMatching([self.queryObjectClass objectsWhere:@"'Tuba' ENDSWITH string1"].count, + @"Operator 'ENDSWITH' is not supported .* right side"); +} + +@end + +@interface NullQueryTests : QueryTests +@end + +@implementation NullQueryTests +- (Class)queryObjectClass { + return [NullQueryObject class]; +} + +- (void)testQueryOnNullableStringColumn { + void (^testWithStringClass)(Class) = ^(Class stringObjectClass) { + RLMRealm *realm = [RLMRealm defaultRealm]; + [realm transactionWithBlock:^{ + [stringObjectClass createInRealm:realm withValue:@[@"a"]]; + [stringObjectClass createInRealm:realm withValue:@[NSNull.null]]; + [stringObjectClass createInRealm:realm withValue:@[@"b"]]; + [stringObjectClass createInRealm:realm withValue:@[NSNull.null]]; + [stringObjectClass createInRealm:realm withValue:@[@""]]; + }]; + + RLMResults *allObjects = [stringObjectClass allObjectsInRealm:realm]; + XCTAssertEqual(5U, allObjects.count); + + RLMResults *nilStrings = [stringObjectClass objectsInRealm:realm where:@"stringCol = NULL"]; + XCTAssertEqual(2U, nilStrings.count); + XCTAssertEqualObjects((@[NSNull.null, NSNull.null]), [nilStrings valueForKey:@"stringCol"]); + + RLMResults *nonNilStrings = [stringObjectClass objectsInRealm:realm where:@"stringCol != NULL"]; + XCTAssertEqual(3U, nonNilStrings.count); + XCTAssertEqualObjects((@[@"a", @"b", @""]), [nonNilStrings valueForKey:@"stringCol"]); + + XCTAssertEqual(3U, [stringObjectClass objectsInRealm:realm where:@"stringCol IN {NULL, 'a'}"].count); + + XCTAssertEqual(1U, [stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS 'a'"].count); + XCTAssertEqual(1U, [stringObjectClass objectsInRealm:realm where:@"stringCol BEGINSWITH 'a'"].count); + XCTAssertEqual(1U, [stringObjectClass objectsInRealm:realm where:@"stringCol ENDSWITH 'a'"].count); + + XCTAssertEqual(0U, [stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS 'z'"].count); + + XCTAssertEqual(1U, [stringObjectClass objectsInRealm:realm where:@"stringCol = ''"].count); + + RLMResults *sorted = [[stringObjectClass allObjectsInRealm:realm] sortedResultsUsingProperty:@"stringCol" ascending:YES]; + XCTAssertEqualObjects((@[NSNull.null, NSNull.null, @"", @"a", @"b"]), [sorted valueForKey:@"stringCol"]); + XCTAssertEqualObjects((@[@"b", @"a", @"", NSNull.null, NSNull.null]), [[sorted sortedResultsUsingProperty:@"stringCol" ascending:NO] valueForKey:@"stringCol"]); + + [realm transactionWithBlock:^{ + [realm deleteObject:[stringObjectClass allObjectsInRealm:realm].firstObject]; + }]; + + XCTAssertEqual(2U, nilStrings.count); + XCTAssertEqual(2U, nonNilStrings.count); + + XCTAssertEqualObjects([nonNilStrings valueForKey:@"stringCol"], [[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS ''"] valueForKey:@"stringCol"]); + XCTAssertEqualObjects([nonNilStrings valueForKey:@"stringCol"], [[stringObjectClass objectsInRealm:realm where:@"stringCol BEGINSWITH ''"] valueForKey:@"stringCol"]); + XCTAssertEqualObjects([nonNilStrings valueForKey:@"stringCol"], [[stringObjectClass objectsInRealm:realm where:@"stringCol ENDSWITH ''"] valueForKey:@"stringCol"]); + XCTAssertEqualObjects([nonNilStrings valueForKey:@"stringCol"], [[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS[c] ''"] valueForKey:@"stringCol"]); + XCTAssertEqualObjects([nonNilStrings valueForKey:@"stringCol"], [[stringObjectClass objectsInRealm:realm where:@"stringCol BEGINSWITH[c] ''"] valueForKey:@"stringCol"]); + XCTAssertEqualObjects([nonNilStrings valueForKey:@"stringCol"], [[stringObjectClass objectsInRealm:realm where:@"stringCol ENDSWITH[c] ''"] valueForKey:@"stringCol"]); + + XCTAssertEqualObjects(@[], ([[stringObjectClass objectsInRealm:realm where:@"stringCol CONTAINS %@", @"\0"] valueForKey:@"self"])); + XCTAssertEqualObjects([[stringObjectClass allObjectsInRealm:realm] valueForKey:@"stringCol"], ([[StringObject objectsInRealm:realm where:@"stringCol CONTAINS NULL"] valueForKey:@"stringCol"])); + }; + testWithStringClass([StringObject class]); + testWithStringClass([IndexedStringObject class]); +} + +- (void)testQueryingOnLinkToNullableStringColumn { + void (^testWithStringClass)(Class, Class) = ^(Class stringLinkClass, Class stringObjectClass) { + RLMRealm *realm = [RLMRealm defaultRealm]; + [realm transactionWithBlock:^{ + [stringLinkClass createInRealm:realm withValue:@[[stringObjectClass createInRealm:realm withValue:@[@"a"]]]]; + [stringLinkClass createInRealm:realm withValue:@[[stringObjectClass createInRealm:realm withValue:@[NSNull.null]]]]; + [stringLinkClass createInRealm:realm withValue:@[[stringObjectClass createInRealm:realm withValue:@[@"b"]]]]; + [stringLinkClass createInRealm:realm withValue:@[[stringObjectClass createInRealm:realm withValue:@[NSNull.null]]]]; + [stringLinkClass createInRealm:realm withValue:@[[stringObjectClass createInRealm:realm withValue:@[@""]]]]; + }]; + + RLMResults *nilStrings = [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol = NULL"]; + XCTAssertEqual(2U, nilStrings.count); + XCTAssertEqualObjects((@[NSNull.null, NSNull.null]), [nilStrings valueForKeyPath:@"objectCol.stringCol"]); + + RLMResults *nonNilStrings = [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol != NULL"]; + XCTAssertEqual(3U, nonNilStrings.count); + XCTAssertEqualObjects((@[@"a", @"b", @""]), [nonNilStrings valueForKeyPath:@"objectCol.stringCol"]); + + XCTAssertEqual(3U, [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol IN {NULL, 'a'}"].count); + + XCTAssertEqual(1U, [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol CONTAINS 'a'"].count); + XCTAssertEqual(1U, [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol BEGINSWITH 'a'"].count); + XCTAssertEqual(1U, [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol ENDSWITH 'a'"].count); + + XCTAssertEqual(0U, [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol CONTAINS 'z'"].count); + + XCTAssertEqual(1U, [stringLinkClass objectsInRealm:realm where:@"objectCol.stringCol = ''"].count); + }; + + testWithStringClass([LinkStringObject class], [StringObject class]); + testWithStringClass([LinkIndexedStringObject class], [IndexedStringObject class]); +} + +- (void)testSortingColumnsWithNull { + RLMRealm *realm = [RLMRealm defaultRealm]; + [realm beginWriteTransaction]; + + { + NumberObject *no1 = [NumberObject createInRealm:realm withValue:@[@1, @1.1f, @1.1, @YES]]; + NumberObject *noNull = [NumberObject createInRealm:realm withValue:@[NSNull.null, NSNull.null, NSNull.null, NSNull.null]]; + NumberObject *no0 = [NumberObject createInRealm:realm withValue:@[@0, @0.0f, @0.0, @NO]]; + for (RLMProperty *property in [[NumberObject alloc] init].objectSchema.properties) { + NSString *name = property.name; + RLMResults *ascending = [[NumberObject allObjectsInRealm:realm] sortedResultsUsingProperty:name ascending:YES]; + XCTAssertEqualObjects([ascending valueForKey:name], ([@[noNull, no0, no1] valueForKey:name])); + + RLMResults *descending = [[NumberObject allObjectsInRealm:realm] sortedResultsUsingProperty:name ascending:NO]; + XCTAssertEqualObjects([descending valueForKey:name], ([@[no1, no0, noNull] valueForKey:name])); + } + } + + { + DateObject *doPositive = [DateObject createInRealm:realm withValue:@[[NSDate dateWithTimeIntervalSince1970:100]]]; + DateObject *doNegative = [DateObject createInRealm:realm withValue:@[[NSDate dateWithTimeIntervalSince1970:-100]]]; + DateObject *doZero = [DateObject createInRealm:realm withValue:@[[NSDate dateWithTimeIntervalSince1970:0]]]; + DateObject *doNull = [DateObject createInRealm:realm withValue:@[NSNull.null]]; + + RLMResults *ascending = [[DateObject allObjectsInRealm:realm] sortedResultsUsingProperty:@"dateCol" ascending:YES]; + XCTAssertEqualObjects([ascending valueForKey:@"dateCol"], ([@[doNull, doNegative, doZero, doPositive] valueForKey:@"dateCol"])); + + RLMResults *descending = [[DateObject allObjectsInRealm:realm] sortedResultsUsingProperty:@"dateCol" ascending:NO]; + XCTAssertEqualObjects([descending valueForKey:@"dateCol"], ([@[doPositive, doZero, doNegative, doNull] valueForKey:@"dateCol"])); + } + + { + StringObject *soA = [StringObject createInRealm:realm withValue:@[@"A"]]; + StringObject *soEmpty = [StringObject createInRealm:realm withValue:@[@""]]; + StringObject *soB = [StringObject createInRealm:realm withValue:@[@"B"]]; + StringObject *soNull = [StringObject createInRealm:realm withValue:@[NSNull.null]]; + StringObject *soAB = [StringObject createInRealm:realm withValue:@[@"AB"]]; + + RLMResults *ascending = [[StringObject allObjectsInRealm:realm] sortedResultsUsingProperty:@"stringCol" ascending:YES]; + XCTAssertEqualObjects([ascending valueForKey:@"stringCol"], ([@[soNull, soEmpty, soA, soAB, soB] valueForKey:@"stringCol"])); + + RLMResults *descending = [[StringObject allObjectsInRealm:realm] sortedResultsUsingProperty:@"stringCol" ascending:NO]; + XCTAssertEqualObjects([descending valueForKey:@"stringCol"], ([@[soB, soAB, soA, soEmpty, soNull] valueForKey:@"stringCol"])); + } + + [realm cancelWriteTransaction]; +} + +- (void)testCountOnCollection { + RLMRealm *realm = [RLMRealm defaultRealm]; + [realm beginWriteTransaction]; + + IntegerArrayPropertyObject *arr = [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @1234, @[]]]; + [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @456 ]]]; + + arr = [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @4567, @[]]]; + [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @1 ]]]; + [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @2 ]]]; + [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @3 ]]]; + + arr = [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @4567, @[]]]; + + [realm commitWriteTransaction]; + + XCTAssertEqual(2U, ([IntegerArrayPropertyObject objectsWhere:@"array.@count > 0"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@count == 3"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@count < 1"].count)); + XCTAssertEqual(2U, ([IntegerArrayPropertyObject objectsWhere:@"0 < array.@count"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"3 == array.@count"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"1 > array.@count"].count)); + + // We do not yet handle collection operations with a keypath on the other side of the comparison. + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@count != number"]), @"'array.@count' not found in object"); + + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@count.foo.bar != 0"]), @"single level key"); + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@count.intCol > 0"]), @"@count does not have any properties"); + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@count != 'Hello'"]), @"@count can only be compared with a numeric value"); +} + +- (void)testAggregateCollectionOperators { + RLMRealm *realm = [RLMRealm defaultRealm]; + [realm beginWriteTransaction]; + + IntegerArrayPropertyObject *arr = [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @1111, @[] ]]; + [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @1234 ]]]; + [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @2 ]]]; + [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @-12345 ]]]; + + arr = [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @2222, @[] ]]; + [arr.array addObject:[IntObject createInRealm:realm withValue:@[ @100 ]]]; + + arr = [IntegerArrayPropertyObject createInRealm:realm withValue:@[ @3333, @[] ]]; + + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@min.intCol == -12345"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@min.intCol == 100"].count)); + XCTAssertEqual(2U, ([IntegerArrayPropertyObject objectsWhere:@"array.@min.intCol < 1000"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@min.intCol > -1000"].count)); + + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@max.intCol == 1234"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@max.intCol == 100"].count)); + XCTAssertEqual(2U, ([IntegerArrayPropertyObject objectsWhere:@"array.@max.intCol > -1000"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@max.intCol > 1000"].count)); + + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@sum.intCol == 100"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@sum.intCol == -11109"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@sum.intCol == 0"].count)); + XCTAssertEqual(2U, ([IntegerArrayPropertyObject objectsWhere:@"array.@sum.intCol > -50"].count)); + XCTAssertEqual(2U, ([IntegerArrayPropertyObject objectsWhere:@"array.@sum.intCol < 50"].count)); + + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@avg.intCol == 100"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@avg.intCol == -3703.0"].count)); + XCTAssertEqual(0U, ([IntegerArrayPropertyObject objectsWhere:@"array.@avg.intCol == 0"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@avg.intCol < -50"].count)); + XCTAssertEqual(1U, ([IntegerArrayPropertyObject objectsWhere:@"array.@avg.intCol > 50"].count)); + + // We do not yet handle collection operations with a keypath on the other side of the comparison. + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@min.intCol == number"]), @"'array.@min.intCol' not found in object"); + + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@min.intCol.foo.bar == 1.23"]), @"single level key"); + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@max.intCol.foo.bar == 1.23"]), @"single level key"); + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@sum.intCol.foo.bar == 1.23"]), @"single level key"); + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@avg.intCol.foo.bar == 1.23"]), @"single level key"); + + // Average is omitted from this test as its result is always a double. + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@min.intCol == 1.23"]), @"@min.*type int cannot be compared"); + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@max.intCol == 1.23"]), @"@max.*type int cannot be compared"); + RLMAssertThrowsWithReasonMatching(([IntegerArrayPropertyObject objectsWhere:@"array.@sum.intCol == 1.23"]), @"@sum.*type int cannot be compared"); +} + +struct NullTestData { + __unsafe_unretained NSString *propertyName; + __unsafe_unretained NSString *nonMatchingStr; + __unsafe_unretained NSString *matchingStr; + __unsafe_unretained id nonMatchingValue; + __unsafe_unretained id matchingValue; + bool orderable; + bool substringOperations; +}; + +- (void)testPrimitiveOperatorsOnAllNullablePropertyTypes { + RLMRealm *realm = [RLMRealm defaultRealm]; + + // nil on LHS is currently not supported by core + XCTAssertThrows([AllOptionalTypes objectsWhere:@"nil = boolObj"]); + + // These need to be stored in variables because the struct does not retain them + NSData *matchingData = [@"" dataUsingEncoding:NSUTF8StringEncoding]; + NSData *notMatchingData = [@"a" dataUsingEncoding:NSUTF8StringEncoding]; + NSDate *matchingDate = [NSDate dateWithTimeIntervalSince1970:1]; + NSDate *notMatchingDate = [NSDate dateWithTimeIntervalSince1970:2]; + + struct NullTestData data[] = { + {@"boolObj", @"YES", @"NO", @YES, @NO}, + {@"intObj", @"1", @"0", @1, @0, true}, + {@"floatObj", @"1", @"0", @1, @0, true}, + {@"doubleObj", @"1", @"0", @1, @0, true}, + {@"string", @"'a'", @"''", @"a", @"", false, true}, + {@"data", nil, nil, notMatchingData, matchingData}, + {@"date", nil, nil, notMatchingDate, matchingDate, true}, + }; + + // Assert that the query "prop op value" gives expectedCount results when + // assembled via string formatting +#define RLMAssertCountWithString(expectedCount, op, prop, value) \ + do { \ + NSString *queryStr = [NSString stringWithFormat:@"%@ " #op " %@", prop, value]; \ + NSUInteger actual = [AllOptionalTypes objectsWhere:queryStr].count; \ + XCTAssertEqual(expectedCount, actual, @"%@: expected %@, got %@", queryStr, @(expectedCount), @(actual)); \ + } while (0) + + // Assert that the query "prop op value" gives expectedCount results when + // assembled via predicateWithFormat +#define RLMAssertCountWithPredicate(expectedCount, op, prop, value) \ + do { \ + NSPredicate *query = [NSPredicate predicateWithFormat:@"%K " #op " %@", prop, value]; \ + NSUInteger actual = [AllOptionalTypes objectsWithPredicate:query].count; \ + XCTAssertEqual(expectedCount, actual, @"%@ " #op " %@: expected %@, got %@", prop, value, @(expectedCount), @(actual)); \ + } while (0) + + // Assert that the given operator gives the expected count for each of the + // stored value, a different value, and nil +#define RLMAssertOperator(op, matchingCount, notMatchingCount, nilCount) \ + do { \ + if (d.matchingStr) { \ + RLMAssertCountWithString(matchingCount, op, d.propertyName, d.matchingStr); \ + RLMAssertCountWithString(notMatchingCount, op, d.propertyName, d.nonMatchingStr); \ + } \ + RLMAssertCountWithString(nilCount, op, d.propertyName, nil); \ + \ + RLMAssertCountWithPredicate(matchingCount, op, d.propertyName, d.matchingValue); \ + RLMAssertCountWithPredicate(notMatchingCount, op, d.propertyName, d.nonMatchingValue); \ + RLMAssertCountWithPredicate(nilCount, op, d.propertyName, nil); \ + } while (0) + + // First test with the `matchingValue` stored in each property + + [realm beginWriteTransaction]; + [AllOptionalTypes createInRealm:realm withValue:@[@NO, @0, @0, @0, @"", matchingData, matchingDate]]; + [realm commitWriteTransaction]; + + for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { + struct NullTestData d = data[i]; + RLMAssertOperator(=, 1U, 0U, 0U); + RLMAssertOperator(!=, 0U, 1U, 1U); + + if (d.orderable) { + RLMAssertOperator(<, 0U, 1U, 0U); + RLMAssertOperator(<=, 1U, 1U, 0U); + RLMAssertOperator(>, 0U, 0U, 0U); + RLMAssertOperator(>=, 1U, 0U, 0U); + } + if (d.substringOperations) { + RLMAssertOperator(BEGINSWITH, 1U, 0U, 1U); + RLMAssertOperator(ENDSWITH, 1U, 0U, 1U); + RLMAssertOperator(CONTAINS, 1U, 0U, 1U); + } + } + + // Retest with all properties nil + + [realm beginWriteTransaction]; + [realm deleteAllObjects]; + [AllOptionalTypes createInRealm:realm withValue:@[NSNull.null, NSNull.null, + NSNull.null, NSNull.null, + NSNull.null, NSNull.null, + NSNull.null]]; + [realm commitWriteTransaction]; + + for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { + struct NullTestData d = data[i]; + RLMAssertOperator(=, 0U, 0U, 1U); + RLMAssertOperator(!=, 1U, 1U, 0U); + + if (d.orderable) { + RLMAssertOperator(<, 0U, 0U, 0U); + RLMAssertOperator(<=, 0U, 0U, 1U); + RLMAssertOperator(>, 0U, 0U, 0U); + RLMAssertOperator(>=, 0U, 0U, 1U); + } + if (d.substringOperations) { + RLMAssertOperator(BEGINSWITH, 0U, 0U, 1U); + RLMAssertOperator(ENDSWITH, 0U, 0U, 1U); + RLMAssertOperator(CONTAINS, 0U, 0U, 1U); + } + } +} + +- (void)testINPredicateOnNullWithNonNullValues +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [AllOptionalTypes createInRealm:realm withValue:@[@YES, @1, @1, @1, @"abc", + [@"a" dataUsingEncoding:NSUTF8StringEncoding], + [NSDate dateWithTimeIntervalSince1970:1]]]; + [realm commitWriteTransaction]; + + //////////////////////// + // Literal Predicates + //////////////////////// + + // BOOL + [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"boolObj IN {NULL}"]; + [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"boolObj IN {YES}"]; + + // int + [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"intObj IN {NULL}"]; + [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"intObj IN {1}"]; + + // float + [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"floatObj IN {NULL}"]; + [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"floatObj IN {1}"]; + + // double + [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"doubleObj IN {NULL}"]; + [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"doubleObj IN {1}"]; + + // NSString + [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"string IN {NULL}"]; + [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"string IN {'abc'}"]; + + // NSData + // Can't represent NSData with NSPredicate literal. See format predicates below + + // NSDate + // Can't represent NSDate with NSPredicate literal. See format predicates below + + //////////////////////// + // Format Predicates + //////////////////////// + + // BOOL + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"boolObj IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"boolObj IN %@", @[@YES]]; + + // int + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"intObj IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"intObj IN %@", @[@1]]; + + // float + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"floatObj IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"floatObj IN %@", @[@1]]; + + // double + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"doubleObj IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"doubleObj IN %@", @[@1]]; + + // NSString + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"string IN %@", @[@"abc"]]; + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"string IN %@", @[NSNull.null]]; + + // NSData + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"data IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"data IN %@", @[[@"a" dataUsingEncoding:NSUTF8StringEncoding]]]; + + // NSDate + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"date IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"date IN %@", @[[NSDate dateWithTimeIntervalSince1970:1]]]; +} + +- (void)testINPredicateOnNullWithNullValues +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + [AllOptionalTypes createInRealm:realm withValue:@[NSNull.null, NSNull.null, + NSNull.null, NSNull.null, + NSNull.null, NSNull.null, + NSNull.null]]; + [realm commitWriteTransaction]; + + //////////////////////// + // Literal Predicates + //////////////////////// + + // BOOL + [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"boolObj IN {NULL}"]; + [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"boolObj IN {YES}"]; + + // int + [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"intObj IN {NULL}"]; + [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"intObj IN {1}"]; + + // float + [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"floatObj IN {NULL}"]; + [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"floatObj IN {1}"]; + + // double + [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"doubleObj IN {NULL}"]; + [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"doubleObj IN {1}"]; + + // NSString + [self testClass:[AllOptionalTypes class] withNormalCount:1 notCount:0 where:@"string IN {NULL}"]; + [self testClass:[AllOptionalTypes class] withNormalCount:0 notCount:1 where:@"string IN {'abc'}"]; + + // NSData + // Can't represent NSData with NSPredicate literal. See format predicates below + + // NSDate + // Can't represent NSDate with NSPredicate literal. See format predicates below + + //////////////////////// + // Format Predicates + //////////////////////// + + // BOOL + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"boolObj IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"boolObj IN %@", @[@YES]]; + + // int + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"intObj IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"intObj IN %@", @[@1]]; + + // float + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"floatObj IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"floatObj IN %@", @[@1]]; + + // double + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"doubleObj IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"doubleObj IN %@", @[@1]]; + + // NSString + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"string IN %@", @[@"abc"]]; + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"string IN %@", @[NSNull.null]]; + + // NSData + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"data IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"data IN %@", @[[@"a" dataUsingEncoding:NSUTF8StringEncoding]]]; + + // NSDate + [self testClass:[AllOptionalTypes class] withNormalCount:1U notCount:0U where:@"date IN %@", @[NSNull.null]]; + [self testClass:[AllOptionalTypes class] withNormalCount:0U notCount:1U where:@"date IN %@", @[[NSDate dateWithTimeIntervalSince1970:1]]]; +} +*/ + diff --git a/tests/index.js b/tests/index.js index 2c1b4b01..4f621f52 100644 --- a/tests/index.js +++ b/tests/index.js @@ -8,6 +8,7 @@ exports.ArrayTests = require('./ArrayTests'); exports.ObjectTests = require('./ObjectTests'); exports.RealmTests = require('./RealmTests'); exports.ResultsTests = require('./ResultsTests'); +exports.QueryTests = require('./QueryTests'); var SPECIAL_METHODS = { beforeEach: true, diff --git a/tests/schemas.js b/tests/schemas.js index 6cc11815..2aac328f 100644 --- a/tests/schemas.js +++ b/tests/schemas.js @@ -103,3 +103,35 @@ exports.DefaultValues = { {name: 'arrayCol', type: Realm.Types.LIST, objectType: 'TestObject', default: [[2]]}, ] }; + +exports.QueryObject = { + name: 'QueryObject', + properties: [ + {name: 'bool1', type: Realm.Types.BOOL}, + {name: 'bool2', type: Realm.Types.BOOL}, + {name: 'int1', type: Realm.Types.INT}, + {name: 'int2', type: Realm.Types.INT}, + {name: 'float1', type: Realm.Types.FLOAT}, + {name: 'float2', type: Realm.Types.FLOAT}, + {name: 'double1', type: Realm.Types.DOUBLE}, + {name: 'double2', type: Realm.Types.DOUBLE}, + {name: 'string1', type: Realm.Types.STRING}, + {name: 'string2', type: Realm.Types.STRING}, + ] +}; + +exports.NullQueryObject = { + name: 'NullQueryObject', + properties: [ + {name: 'bool1', type: Realm.Types.BOOL}, + {name: 'bool2', type: Realm.Types.BOOL}, + {name: 'int1', type: Realm.Types.INT}, + {name: 'int2', type: Realm.Types.INT}, + {name: 'float1', type: Realm.Types.FLOAT}, + {name: 'float2', type: Realm.Types.FLOAT}, + {name: 'double1', type: Realm.Types.DOUBLE}, + {name: 'double2', type: Realm.Types.DOUBLE}, + {name: 'string1', type: Realm.Types.STRING}, + {name: 'string2', type: Realm.Types.STRING}, + ] +}; From b6970906e0a71b8ef5885cbca54cb9f2a1c99962 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 17 Nov 2015 16:13:52 -0800 Subject: [PATCH 27/47] JSON test cases --- tests/QueryTests.js | 104 ++++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/tests/QueryTests.js b/tests/QueryTests.js index e3b6ebdd..720d4854 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -10,37 +10,69 @@ var BaseTest = require('./base-test'); var TestCase = require('./asserts'); var schemas = require('./schemas'); +var typeConverters = {}; +typeConverters[Realm.Types.DATE] = function(value) { return new Date(value); }; + function TestQueryCount(count, realm, type, query /*, queryArgs */) { var results = realm.objects.apply(realm, Array.prototype.slice.call(arguments, 2)); TestCase.assertEqual(count, results.length, "Query '" + query + "' on type '" + type + "' expected " + count + " results, got " + results.length); } +function RunQuerySuite(suite) { + var realm = new Realm({schema: suite.schema}); + realm.write(function() { + for(var obj of suite.objects) { + var objSchema = suite.schema.find(function(el) { return el.name == obj.type }); + if (!objSchema) { + throw "Object schema '" + obj.type + "' not found in test suite."; + } + + var converted = []; + for(var i in obj.value) { + var converter = typeConverters[objSchema.properties[i].type]; + converted.push(converter ? converter(obj.value[i]) : obj.value[i]); + } + obj.value = converted; + realm.create(obj.type, converted); + } + }); + + for(var test of suite.tests) { + var args = test.slice(0, 3); + args.splice(1, 0, realm); + for(var i = 3; i < test.length; i++) { + var arg = test[i]; + if (Array.isArray(arg)) { + args.push(suite.objects[arg[0]].value[arg[1]]); + } + } + TestQueryCount.apply(undefined, args); + } +} + +var dateTests = { + "schema" : [{ + "name": "DateObject", + "properties": [{ "name": "date", "type": Realm.Types.DATE }], + }], + "objects": [ + { "type": "DateObject", "value": [10000] }, + { "type": "DateObject", "value": [10001] }, + { "type": "DateObject", "value": [10002] }, + ], + "tests": [ + [2, "DateObject", "date < $0", [2, 0]], + [3, "DateObject", "date <= $0", [2, 0]], + [2, "DateObject", "date > $0", [0, 0]], + [3, "DateObject", "date >= $0", [0, 0]], + [1, "DateObject", "date == $0", [0, 0]], + [2, "DateObject", "date != $0", [0, 0]], + ] +}; + module.exports = BaseTest.extend({ - testDateQueries: function() { - var DateObject = { - name: 'DateObject', - properties: [ - { name: 'date', type: Realm.Types.DATE } - ] - }; - - var date1 = new Date(Date.now()); - var date2 = new Date(date1.getTime() + 1); - var date3 = new Date(date1.getTime() + 2); - - var realm = new Realm({schema: [DateObject]}); - realm.write(function() { - realm.create('DateObject', { date: date1 }); - realm.create('DateObject', { date: date2 }); - realm.create('DateObject', { date: date3 }); - }); - - TestQueryCount(2, realm, 'DateObject', "date < $0", date3); - TestQueryCount(3, realm, 'DateObject', "date <= $0", date3); - TestQueryCount(2, realm, 'DateObject', "date > $0", date1); - TestQueryCount(3, realm, 'DateObject', "date >= $0", date1); - TestQueryCount(1, realm, 'DateObject', "date == $0", date1); - TestQueryCount(2, realm, 'DateObject', "date != $0", date1); + testDateQueries: function() { + RunQuerySuite(dateTests); }, }); @@ -84,28 +116,6 @@ module.exports = BaseTest.extend({ XCTAssertEqual(betweenResults.count, 2U, @"Should equal 2"); } -- (void)testDefaultRealmQuery -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - [realm beginWriteTransaction]; - [PersonObject createInRealm:realm withValue:@[@"Fiel", @27]]; - [PersonObject createInRealm:realm withValue:@[@"Tim", @29]]; - [PersonObject createInRealm:realm withValue:@[@"Ari", @33]]; - [realm commitWriteTransaction]; - - // query on class - RLMResults *all = [PersonObject allObjects]; - XCTAssertEqual(all.count, 3U, @"Expecting 3 results"); - - RLMResults *results = [PersonObject objectsWhere:@"age == 27"]; - XCTAssertEqual(results.count, 1U, @"Expecting 1 results"); - - // with order - results = [[PersonObject objectsWhere:@"age > 28"] sortedResultsUsingProperty:@"age" ascending:YES]; - PersonObject *tim = results[0]; - XCTAssertEqualObjects(tim.name, @"Tim", @"Tim should be first results"); -} - (void)testArrayQuery { From 20ac833e41d357187412107f220bc02287a626e2 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 17 Nov 2015 17:17:54 -0800 Subject: [PATCH 28/47] bool tests --- src/object-store/parser/query_builder.cpp | 3 + src/object-store/parser/test.cpp | 2 + tests/QueryTests.js | 148 +++++++++++++--------- 3 files changed, 92 insertions(+), 61 deletions(-) diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp index 5afdfcb9..a836f777 100644 --- a/src/object-store/parser/query_builder.cpp +++ b/src/object-store/parser/query_builder.cpp @@ -220,6 +220,9 @@ struct ValueGetter { if (value.type == parser::Expression::Type::Argument) { return args.bool_for_argument(std::stoi(value.s)); } + if (value.type != parser::Expression::Type::True && value.type != parser::Expression::Type::False) { + throw std::runtime_error("Attempting to compare bool property to a non-bool value"); + } return value.type == parser::Expression::Type::True; } }; diff --git a/src/object-store/parser/test.cpp b/src/object-store/parser/test.cpp index e47dcee7..f18a36ff 100644 --- a/src/object-store/parser/test.cpp +++ b/src/object-store/parser/test.cpp @@ -74,6 +74,7 @@ static std::vector valid_queries = { "a==a OR a==a", "and=='AND'&&'or'=='||'", "and == or && ORE > GRAND", + "a=1AND NOTb=2", }; static std::vector invalid_queries = { @@ -124,6 +125,7 @@ static std::vector invalid_queries = { "a==a &| a==a", "a==a && OR a==a", "a==aORa==a", + "a=1ANDNOT b=2", "truepredicate &&", "truepredicate & truepredicate", diff --git a/tests/QueryTests.js b/tests/QueryTests.js index 720d4854..cbda6adc 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -13,40 +13,52 @@ var schemas = require('./schemas'); var typeConverters = {}; typeConverters[Realm.Types.DATE] = function(value) { return new Date(value); }; -function TestQueryCount(count, realm, type, query /*, queryArgs */) { - var results = realm.objects.apply(realm, Array.prototype.slice.call(arguments, 2)); - TestCase.assertEqual(count, results.length, "Query '" + query + "' on type '" + type + "' expected " + count + " results, got " + results.length); -} - -function RunQuerySuite(suite) { +function runQuerySuite(suite) { var realm = new Realm({schema: suite.schema}); - realm.write(function() { - for(var obj of suite.objects) { - var objSchema = suite.schema.find(function(el) { return el.name == obj.type }); - if (!objSchema) { - throw "Object schema '" + obj.type + "' not found in test suite."; - } + var objects = suite.objects.map(function(obj) { + var objSchema = suite.schema.find(function(el) { return el.name == obj.type }); + if (!objSchema) { + throw "Object schema '" + obj.type + "' not found in test suite."; + } - var converted = []; - for(var i in obj.value) { - var converter = typeConverters[objSchema.properties[i].type]; - converted.push(converter ? converter(obj.value[i]) : obj.value[i]); - } - obj.value = converted; - realm.create(obj.type, converted); + var converted = obj.value.map(function(propValue, index) { + var converter = typeConverters[objSchema.properties[index].type]; + return converter ? converter(obj.value[index]) : obj.value[index]; + }); + + return { type: obj.type, value: converted }; + }); + + realm.write(function() { + for (var obj of objects) { + realm.create(obj.type, obj.value); } }); - for(var test of suite.tests) { - var args = test.slice(0, 3); - args.splice(1, 0, realm); - for(var i = 3; i < test.length; i++) { - var arg = test[i]; - if (Array.isArray(arg)) { - args.push(suite.objects[arg[0]].value[arg[1]]); + for (var test of suite.tests) { + if (test[0] == "QueryCount") { + var args = test.slice(2, 4); + for (var i = 4; i < test.length; i++) { + var arg = test[i]; + if (Array.isArray(arg)) { + args.push(objects[arg[0]].value[arg[1]]); + } + else { + args.push(arg); + } } + + var results = realm.objects.apply(realm, args); + TestCase.assertEqual(test[1], results.length, "Query '" + args[1] + "' on type '" + args[0] + "' expected " + test[1] + " results, got " + results.length); + } + else if (test[0] == "QueryThrows") { + TestCase.assertThrows(function() { + realm.objects.apply(realm, test.slice(1)); + }); + } + else { + throw "Invalid query test '" + test[0] + "'"; } - TestQueryCount.apply(undefined, args); } } @@ -61,21 +73,62 @@ var dateTests = { { "type": "DateObject", "value": [10002] }, ], "tests": [ - [2, "DateObject", "date < $0", [2, 0]], - [3, "DateObject", "date <= $0", [2, 0]], - [2, "DateObject", "date > $0", [0, 0]], - [3, "DateObject", "date >= $0", [0, 0]], - [1, "DateObject", "date == $0", [0, 0]], - [2, "DateObject", "date != $0", [0, 0]], + ["QueryCount", 2, "DateObject", "date < $0", [2, 0]], + ["QueryCount", 3, "DateObject", "date <= $0", [2, 0]], + ["QueryCount", 2, "DateObject", "date > $0", [0, 0]], + ["QueryCount", 3, "DateObject", "date >= $0", [0, 0]], + ["QueryCount", 1, "DateObject", "date == $0", [0, 0]], + ["QueryCount", 2, "DateObject", "date != $0", [0, 0]], ] }; module.exports = BaseTest.extend({ testDateQueries: function() { - RunQuerySuite(dateTests); + runQuerySuite(dateTests); }, }); +var boolTests = { + "schema" : [{ + "name": "BoolObject", + "properties": [{ "name": "bool", "type": Realm.Types.BOOL }], + }], + "objects": [ + { "type": "BoolObject", "value": [false] }, + { "type": "BoolObject", "value": [true] }, + { "type": "BoolObject", "value": [true] }, + ], + "tests": [ + ["QueryCount", 2, "BoolObject", "bool == true"], + ["QueryCount", 1, "BoolObject", "bool != true"], + ["QueryCount", 1, "BoolObject", "bool == false"], + ["QueryCount", 2, "BoolObject", "bool == TRUE"], + ["QueryCount", 1, "BoolObject", "bool == FALSE"], + ["QueryCount", 2, "BoolObject", "bool == $0", true], + ["QueryCount", 1, "BoolObject", "bool == $0", false], + ["QueryCount", 0, "BoolObject", "bool == true && bool == false"], + ["QueryCount", 3, "BoolObject", "bool == true || bool == false"], + + ["QueryThrows", "BoolObject", "bool == 0"], + ["QueryThrows", "BoolObject", "bool == 1"], + ["QueryThrows", "BoolObject", "bool == 'not a bool'"], + ["QueryThrows", "BoolObject", "bool > true"], + ["QueryThrows", "BoolObject", "bool >= true"], + ["QueryThrows", "BoolObject", "bool < true"], + ["QueryThrows", "BoolObject", "bool <= true"], + ["QueryThrows", "BoolObject", "bool BEGINSWITH true"], + ["QueryThrows", "BoolObject", "bool CONTAINS true"], + ["QueryThrows", "BoolObject", "bool ENDSWITH true"], + ] +} + +module.exports = BaseTest.extend({ + testBoolQueries: function() { + runQuerySuite(boolTests); + }, +}); + + /* -(void)testQueryBetween { @@ -342,33 +395,6 @@ module.exports = BaseTest.extend({ XCTAssertThrows([[realm objects:nil where:@"age > 25"] sortedResultsUsingProperty:@"age" ascending:YES], @"nil class name"); } -- (void)testPredicateValidUse -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - // boolean false - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == no"], @"== no"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == No"], @"== No"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == NO"], @"== NO"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == false"], @"== false"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == False"], @"== False"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == FALSE"], @"== FALSE"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == 0"], @"== 0"); - - // boolean true - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == yes"], @"== yes"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == Yes"], @"== Yes"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == YES"], @"== YES"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == true"], @"== true"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == True"], @"== True"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == TRUE"], @"== TRUE"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol == 1"], @"== 1"); - - // inequality - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol != YES"], @"!= YES"); - XCTAssertNoThrow([AllTypesObject objectsInRealm:realm where:@"boolCol <> YES"], @"<> YES"); -} - - (void)testPredicateNotSupported { // These are things which are valid predicates, but which we do not support From e449f10fcc7e0a551d3bbead304433881dd202af Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 17 Nov 2015 17:50:17 -0800 Subject: [PATCH 29/47] some int tests --- tests/QueryTests.js | 159 ++++++++++++++++---------------------------- 1 file changed, 58 insertions(+), 101 deletions(-) diff --git a/tests/QueryTests.js b/tests/QueryTests.js index cbda6adc..f6da4193 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -23,7 +23,7 @@ function runQuerySuite(suite) { var converted = obj.value.map(function(propValue, index) { var converter = typeConverters[objSchema.properties[index].type]; - return converter ? converter(obj.value[index]) : obj.value[index]; + return converter ? converter(propValue) : propValue; }); return { type: obj.type, value: converted }; @@ -79,19 +79,17 @@ var dateTests = { ["QueryCount", 3, "DateObject", "date >= $0", [0, 0]], ["QueryCount", 1, "DateObject", "date == $0", [0, 0]], ["QueryCount", 2, "DateObject", "date != $0", [0, 0]], + + ["QueryThrows", "DateObject", "date == 'not a date'"], + ["QueryThrows", "DateObject", "date == 1"], + ["QueryThrows", "DateObject", "date == $0", 1], ] }; -module.exports = BaseTest.extend({ - testDateQueries: function() { - runQuerySuite(dateTests); - }, -}); - var boolTests = { "schema" : [{ "name": "BoolObject", - "properties": [{ "name": "bool", "type": Realm.Types.BOOL }], + "properties": [{ "name": "boolCol", "type": Realm.Types.BOOL }], }], "objects": [ { "type": "BoolObject", "value": [false] }, @@ -99,33 +97,66 @@ var boolTests = { { "type": "BoolObject", "value": [true] }, ], "tests": [ - ["QueryCount", 2, "BoolObject", "bool == true"], - ["QueryCount", 1, "BoolObject", "bool != true"], - ["QueryCount", 1, "BoolObject", "bool == false"], - ["QueryCount", 2, "BoolObject", "bool == TRUE"], - ["QueryCount", 1, "BoolObject", "bool == FALSE"], - ["QueryCount", 2, "BoolObject", "bool == $0", true], - ["QueryCount", 1, "BoolObject", "bool == $0", false], - ["QueryCount", 0, "BoolObject", "bool == true && bool == false"], - ["QueryCount", 3, "BoolObject", "bool == true || bool == false"], + ["QueryCount", 2, "BoolObject", "boolCol == true"], + ["QueryCount", 2, "BoolObject", "true == boolCol"], + ["QueryCount", 1, "BoolObject", "boolCol != true"], + ["QueryCount", 1, "BoolObject", "boolCol == false"], + ["QueryCount", 2, "BoolObject", "boolCol == TRUE"], + ["QueryCount", 1, "BoolObject", "boolCol == FALSE"], + ["QueryCount", 2, "BoolObject", "boolCol == $0", true], + ["QueryCount", 1, "BoolObject", "boolCol == $0", false], + ["QueryCount", 0, "BoolObject", "boolCol == true && boolCol == false"], + ["QueryCount", 3, "BoolObject", "boolCol == true || boolCol == false"], - ["QueryThrows", "BoolObject", "bool == 0"], - ["QueryThrows", "BoolObject", "bool == 1"], - ["QueryThrows", "BoolObject", "bool == 'not a bool'"], - ["QueryThrows", "BoolObject", "bool > true"], - ["QueryThrows", "BoolObject", "bool >= true"], - ["QueryThrows", "BoolObject", "bool < true"], - ["QueryThrows", "BoolObject", "bool <= true"], - ["QueryThrows", "BoolObject", "bool BEGINSWITH true"], - ["QueryThrows", "BoolObject", "bool CONTAINS true"], - ["QueryThrows", "BoolObject", "bool ENDSWITH true"], + ["QueryThrows", "BoolObject", "boolCol == 0"], + ["QueryThrows", "BoolObject", "boolCol == 1"], + ["QueryThrows", "BoolObject", "boolCol == 'not a bool'"], + ["QueryThrows", "BoolObject", "boolCol > true"], + ["QueryThrows", "BoolObject", "boolCol >= true"], + ["QueryThrows", "BoolObject", "boolCol < true"], + ["QueryThrows", "BoolObject", "boolCol <= true"], + ["QueryThrows", "BoolObject", "boolCol BEGINSWITH true"], + ["QueryThrows", "BoolObject", "boolCol CONTAINS true"], + ["QueryThrows", "BoolObject", "boolCol ENDSWITH true"], ] } +var intTests = { + "schema" : [{ + "name": "IntObject", + "properties": [{ "name": "intCol", "type": Realm.Types.INT }], + }], + "objects": [ + { "type": "IntObject", "value": [-1] }, + { "type": "IntObject", "value": [0] }, + { "type": "IntObject", "value": [100] }, + ], + "tests": [ + ["QueryCount", 1, "IntObject", "intCol == -1"], + ["QueryCount", 1, "IntObject", "intCol == 0"], + ["QueryCount", 0, "IntObject", "intCol == 1"], + ["QueryCount", 2, "IntObject", "intCol != 0"], + ["QueryCount", 2, "IntObject", "intCol > -1"], + ["QueryCount", 3, "IntObject", "intCol >= -1"], + ["QueryCount", 2, "IntObject", "intCol < 100"], + ["QueryCount", 3, "IntObject", "intCol <= 100"], + + ["QueryThrows", "IntObject", "intCol BEGINSWITH 1"], + ["QueryThrows", "IntObject", "intCol CONTAINS 1"], + ["QueryThrows", "IntObject", "intCol ENDSWITH 1"], + ] +}; + module.exports = BaseTest.extend({ + testDateQueries: function() { + runQuerySuite(dateTests); + }, testBoolQueries: function() { runQuerySuite(boolTests); }, + testIntQueries: function() { + runQuerySuite(intTests); + }, }); @@ -595,80 +626,6 @@ module.exports = BaseTest.extend({ @"Property type mismatch between double and string"); } -- (void)testValidOperatorsInNumericComparison:(NSString *) comparisonType - withProposition:(BOOL(^)(NSPredicateOperatorType)) proposition -{ - NSPredicateOperatorType validOps[] = { - NSLessThanPredicateOperatorType, - NSLessThanOrEqualToPredicateOperatorType, - NSGreaterThanPredicateOperatorType, - NSGreaterThanOrEqualToPredicateOperatorType, - NSEqualToPredicateOperatorType, - NSNotEqualToPredicateOperatorType - }; - - for (NSUInteger i = 0; i < sizeof(validOps) / sizeof(NSPredicateOperatorType); ++i) - { - XCTAssert(proposition(validOps[i]), - @"%@ operator in %@ comparison.", - [RLMPredicateUtil predicateOperatorTypeString:validOps[i]], - comparisonType); - } -} - -- (void)testValidOperatorsInNumericComparison -{ - [self testValidOperatorsInNumericComparison:@"integer" - withProposition:[RLMPredicateUtil isEmptyIntColPredicate]]; - [self testValidOperatorsInNumericComparison:@"float" - withProposition:[RLMPredicateUtil isEmptyFloatColPredicate]]; - [self testValidOperatorsInNumericComparison:@"double" - withProposition:[RLMPredicateUtil isEmptyDoubleColPredicate]]; - [self testValidOperatorsInNumericComparison:@"date" - withProposition:[RLMPredicateUtil isEmptyDateColPredicate]]; -} - -- (void)testInvalidOperatorsInNumericComparison:(NSString *) comparisonType - withProposition:(BOOL(^)(NSPredicateOperatorType)) proposition -{ - NSPredicateOperatorType invalidOps[] = { - NSMatchesPredicateOperatorType, - NSLikePredicateOperatorType, - NSBeginsWithPredicateOperatorType, - NSEndsWithPredicateOperatorType, - NSContainsPredicateOperatorType - }; - - for (NSUInteger i = 0; i < sizeof(invalidOps) / sizeof(NSPredicateOperatorType); ++i) - { - XCTAssertThrowsSpecificNamed(proposition(invalidOps[i]), NSException, - @"Invalid operator type", - @"%@ operator invalid in %@ comparison.", - [RLMPredicateUtil predicateOperatorTypeString:invalidOps[i]], - comparisonType); - } -} - -- (void)testInvalidOperatorsInNumericComparison -{ - [self testInvalidOperatorsInNumericComparison:@"integer" - withProposition:[RLMPredicateUtil isEmptyIntColPredicate]]; - [self testInvalidOperatorsInNumericComparison:@"float" - withProposition:[RLMPredicateUtil isEmptyFloatColPredicate]]; - [self testInvalidOperatorsInNumericComparison:@"double" - withProposition:[RLMPredicateUtil isEmptyDoubleColPredicate]]; - [self testInvalidOperatorsInNumericComparison:@"date" - withProposition:[RLMPredicateUtil isEmptyDateColPredicate]]; -} - -- (void)testCustomSelectorsInNumericComparison:(NSString *) comparisonType - withProposition:(BOOL(^)()) proposition -{ - XCTAssertThrowsSpecificNamed(proposition(), NSException, - @"Invalid operator type", - @"Custom selector invalid in %@ comparison.", comparisonType); -} - - (void)testCustomSelectorsInNumericComparison { BOOL (^isEmpty)(); From b2f682b94bc81273fc973b5d42f62032bb8035be Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Tue, 17 Nov 2015 17:54:25 -0800 Subject: [PATCH 30/47] more int tests --- tests/QueryTests.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/QueryTests.js b/tests/QueryTests.js index f6da4193..b217757e 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -111,6 +111,7 @@ var boolTests = { ["QueryThrows", "BoolObject", "boolCol == 0"], ["QueryThrows", "BoolObject", "boolCol == 1"], ["QueryThrows", "BoolObject", "boolCol == 'not a bool'"], + ["QueryThrows", "BoolObject", "boolCol == $0", "not a bool"], ["QueryThrows", "BoolObject", "boolCol > true"], ["QueryThrows", "BoolObject", "boolCol >= true"], ["QueryThrows", "BoolObject", "boolCol < true"], @@ -134,13 +135,18 @@ var intTests = { "tests": [ ["QueryCount", 1, "IntObject", "intCol == -1"], ["QueryCount", 1, "IntObject", "intCol == 0"], - ["QueryCount", 0, "IntObject", "intCol == 1"], + ["QueryCount", 0, "IntObject", "1 == intCol"], ["QueryCount", 2, "IntObject", "intCol != 0"], ["QueryCount", 2, "IntObject", "intCol > -1"], ["QueryCount", 3, "IntObject", "intCol >= -1"], ["QueryCount", 2, "IntObject", "intCol < 100"], ["QueryCount", 3, "IntObject", "intCol <= 100"], + ["QueryCount", 1, "IntObject", "intCol > 0x1F"], + ["QueryCount", 1, "IntObject", "intCol == $0", 100], + ["QueryThrows", "IntObject", "intCol == 'not an int'"], + ["QueryThrows", "IntObject", "intCol == true"], + ["QueryThrows", "IntObject", "intCol == $0", "not an int"], ["QueryThrows", "IntObject", "intCol BEGINSWITH 1"], ["QueryThrows", "IntObject", "intCol CONTAINS 1"], ["QueryThrows", "IntObject", "intCol ENDSWITH 1"], From d7d328d2fe272eb704f6e4ea6acac6e4f986bf31 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 11:32:52 -0800 Subject: [PATCH 31/47] finish numeric tests --- tests/QueryTests.js | 76 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/tests/QueryTests.js b/tests/QueryTests.js index b217757e..95168f4d 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -98,9 +98,9 @@ var boolTests = { ], "tests": [ ["QueryCount", 2, "BoolObject", "boolCol == true"], - ["QueryCount", 2, "BoolObject", "true == boolCol"], + ["QueryCount", 1, "BoolObject", "boolCol==false"], ["QueryCount", 1, "BoolObject", "boolCol != true"], - ["QueryCount", 1, "BoolObject", "boolCol == false"], + ["QueryCount", 2, "BoolObject", "true == boolCol"], ["QueryCount", 2, "BoolObject", "boolCol == TRUE"], ["QueryCount", 1, "BoolObject", "boolCol == FALSE"], ["QueryCount", 2, "BoolObject", "boolCol == $0", true], @@ -134,7 +134,7 @@ var intTests = { ], "tests": [ ["QueryCount", 1, "IntObject", "intCol == -1"], - ["QueryCount", 1, "IntObject", "intCol == 0"], + ["QueryCount", 1, "IntObject", "intCol==0"], ["QueryCount", 0, "IntObject", "1 == intCol"], ["QueryCount", 2, "IntObject", "intCol != 0"], ["QueryCount", 2, "IntObject", "intCol > -1"], @@ -153,6 +153,70 @@ var intTests = { ] }; +var floatTests = { + "schema" : [{ + "name": "FloatObject", + "properties": [{ "name": "floatCol", "type": Realm.Types.Float }], + }], + "objects": [ + { "type": "FloatObject", "value": [-1.001] }, + { "type": "FloatObject", "value": [0.0] }, + { "type": "FloatObject", "value": [100.2] }, + ], + "tests": [ + ["QueryCount", 1, "FloatObject", "floatCol == -1.001"], + ["QueryCount", 1, "FloatObject", "floatCol == 0"], + ["QueryCount", 0, "FloatObject", "1 == floatCol"], + ["QueryCount", 2, "FloatObject", "floatCol != 0"], + ["QueryCount", 2, "FloatObject", "floatCol > -1.001"], + ["QueryCount", 3, "FloatObject", "floatCol >= -1.001"], + ["QueryCount", 2, "FloatObject", "floatCol < 100.2"], + ["QueryCount", 3, "FloatObject", "floatCol <= 100.2"], + ["QueryCount", 1, "FloatObject", "floatCol > 0x1F"], + ["QueryCount", 1, "FloatObject", "floatCol == $0", 100.2], + + ["QueryThrows", "FloatObject", "floatCol == 'not a float'"], + ["QueryThrows", "FloatObject", "floatCol == true"], + ["QueryThrows", "FloatObject", "floatCol == $0", "not a float"], + ["QueryThrows", "FloatObject", "floatCol BEGINSWITH 1"], + ["QueryThrows", "FloatObject", "floatCol CONTAINS 1"], + ["QueryThrows", "FloatObject", "floatCol ENDSWITH 1"], + ] +}; + + +var doubleTests = { + "schema" : [{ + "name": "DoubleObject", + "properties": [{ "name": "doubleCol", "type": Realm.Types.Double }], + }], + "objects": [ + { "type": "DoubleObject", "value": [-1.001] }, + { "type": "DoubleObject", "value": [0.0] }, + { "type": "DoubleObject", "value": [100.2] }, + ], + "tests": [ + ["QueryCount", 1, "DoubleObject", "doubleCol == -1.001"], + ["QueryCount", 1, "DoubleObject", "doubleCol == 0"], + ["QueryCount", 0, "DoubleObject", "1 == doubleCol"], + ["QueryCount", 2, "DoubleObject", "doubleCol != 0"], + ["QueryCount", 2, "DoubleObject", "doubleCol > -1.001"], + ["QueryCount", 3, "DoubleObject", "doubleCol >= -1.001"], + ["QueryCount", 2, "DoubleObject", "doubleCol < 100.2"], + ["QueryCount", 3, "DoubleObject", "doubleCol <= 100.2"], + ["QueryCount", 1, "DoubleObject", "doubleCol > 0x1F"], + ["QueryCount", 1, "DoubleObject", "doubleCol == $0", 100.2], + + ["QueryThrows", "DoubleObject", "doubleCol == 'not a double'"], + ["QueryThrows", "DoubleObject", "doubleCol == true"], + ["QueryThrows", "DoubleObject", "doubleCol == $0", "not a double"], + ["QueryThrows", "DoubleObject", "doubleCol BEGINSWITH 1"], + ["QueryThrows", "DoubleObject", "doubleCol CONTAINS 1"], + ["QueryThrows", "DoubleObject", "doubleCol ENDSWITH 1"], + ] +}; + + module.exports = BaseTest.extend({ testDateQueries: function() { runQuerySuite(dateTests); @@ -163,6 +227,12 @@ module.exports = BaseTest.extend({ testIntQueries: function() { runQuerySuite(intTests); }, + testFloatQueries: function() { + runQuerySuite(intTests); + }, + testDoubleQueries: function() { + runQuerySuite(intTests); + }, }); From a64fab83ade320c3490ab13e06416df2baf19a3f Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 11:54:45 -0800 Subject: [PATCH 32/47] make test cases json --- tests/QueryTests.js | 394 ++++++++++++++++++-------------------------- 1 file changed, 162 insertions(+), 232 deletions(-) diff --git a/tests/QueryTests.js b/tests/QueryTests.js index 95168f4d..dc2214f3 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -62,7 +62,9 @@ function runQuerySuite(suite) { } } -var dateTests = { +var testCases = { + +"dateTests": { "schema" : [{ "name": "DateObject", "properties": [{ "name": "date", "type": Realm.Types.DATE }], @@ -84,9 +86,9 @@ var dateTests = { ["QueryThrows", "DateObject", "date == 1"], ["QueryThrows", "DateObject", "date == $0", 1], ] -}; +}, -var boolTests = { +"boolTests" : { "schema" : [{ "name": "BoolObject", "properties": [{ "name": "boolCol", "type": Realm.Types.BOOL }], @@ -120,9 +122,9 @@ var boolTests = { ["QueryThrows", "BoolObject", "boolCol CONTAINS true"], ["QueryThrows", "BoolObject", "boolCol ENDSWITH true"], ] -} +}, -var intTests = { +"intTests" : { "schema" : [{ "name": "IntObject", "properties": [{ "name": "intCol", "type": Realm.Types.INT }], @@ -151,9 +153,9 @@ var intTests = { ["QueryThrows", "IntObject", "intCol CONTAINS 1"], ["QueryThrows", "IntObject", "intCol ENDSWITH 1"], ] -}; +}, -var floatTests = { +"floatTests" : { "schema" : [{ "name": "FloatObject", "properties": [{ "name": "floatCol", "type": Realm.Types.Float }], @@ -165,7 +167,7 @@ var floatTests = { ], "tests": [ ["QueryCount", 1, "FloatObject", "floatCol == -1.001"], - ["QueryCount", 1, "FloatObject", "floatCol == 0"], + ["QueryCount", 1, "FloatObject", "floatCol = 0"], ["QueryCount", 0, "FloatObject", "1 == floatCol"], ["QueryCount", 2, "FloatObject", "floatCol != 0"], ["QueryCount", 2, "FloatObject", "floatCol > -1.001"], @@ -181,11 +183,12 @@ var floatTests = { ["QueryThrows", "FloatObject", "floatCol BEGINSWITH 1"], ["QueryThrows", "FloatObject", "floatCol CONTAINS 1"], ["QueryThrows", "FloatObject", "floatCol ENDSWITH 1"], + //XCTAssertThrows(([[realm objects:[FloatObject className] where:@"floatCol = 3.5e+38"] count]), @"Too large to be a float"); + //XCTAssertThrows(([[realm objects:[FloatObject className] where:@"floatCol = -3.5e+38"] count]), @"Too small to be a float"); ] -}; +}, - -var doubleTests = { +"doubleTests" : { "schema" : [{ "name": "DoubleObject", "properties": [{ "name": "doubleCol", "type": Realm.Types.Double }], @@ -214,24 +217,167 @@ var doubleTests = { ["QueryThrows", "DoubleObject", "doubleCol CONTAINS 1"], ["QueryThrows", "DoubleObject", "doubleCol ENDSWITH 1"], ] +}, + +"stringTests" : { + "schema" : [{ + "name": "DoubleObject", + "properties": [{ "name": "doubleCol", "type": Realm.Types.Double }], + }], + "objects": [ + { "type": "DoubleObject", "value": [-1.001] }, + { "type": "DoubleObject", "value": [0.0] }, + { "type": "DoubleObject", "value": [100.2] }, + ], + "tests": [ + ["QueryCount", 1, "DoubleObject", "doubleCol == -1.001"], + ] +}, + }; +/* + +- (void)testStringBeginsWith +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + if (self.isNull) { + so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + } + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'a'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'ab'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abcd'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abd'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'c'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'A'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH[c] 'a'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH[c] 'A'"].count); + + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'a'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'c'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'A'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH[c] 'a'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH[c] 'A'"].count); +} + +- (void)testStringEndsWith +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + if (self.isNull) { + so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + } + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'c'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'bc'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'abc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'aabc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'bbc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'a'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'C'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH[c] 'c'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH[c] 'C'"].count); + + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'c'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'a'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'C'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH[c] 'c'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH[c] 'C'"].count); +} + +- (void)testStringContains +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + if (self.isNull) { + so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + } + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'a'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'b'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'c'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'ab'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'bc'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'abc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'd'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'aabc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'bbc'"].count); + + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'C'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS[c] 'c'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS[c] 'C'"].count); + + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'd'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'c'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'C'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS[c] 'c'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS[c] 'C'"].count); +} + +- (void)testStringEquality +{ + RLMRealm *realm = [RLMRealm defaultRealm]; + + [realm beginWriteTransaction]; + StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; + [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; + [realm commitWriteTransaction]; + + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol == 'abc'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol != 'def'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ==[c] 'abc'"].count); + XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ==[c] 'ABC'"].count); + + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol != 'abc'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol == 'def'"].count); + XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol == 'ABC'"].count); + + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'abc'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol != 'def'"].count); + + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ==[c] 'abc'"].count); + XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ==[c] 'ABC'"].count); + + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol != 'abc'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'def'"].count); + XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'ABC'"].count); +} + +*/ module.exports = BaseTest.extend({ testDateQueries: function() { - runQuerySuite(dateTests); + runQuerySuite(testCases.dateTests); }, testBoolQueries: function() { - runQuerySuite(boolTests); + runQuerySuite(testCases.boolTests); }, testIntQueries: function() { - runQuerySuite(intTests); + runQuerySuite(testCases.intTests); }, testFloatQueries: function() { - runQuerySuite(intTests); + runQuerySuite(testCases.intTests); }, testDoubleQueries: function() { - runQuerySuite(intTests); + runQuerySuite(testCases.intTests); }, }); @@ -485,23 +631,6 @@ module.exports = BaseTest.extend({ XCTAssertEqual(3, [[[[desc objectsWhere:@"intCol >= 2"] objectsWhere:@"intCol < 4"] firstObject] intCol]); } -- (void)testDynamicQueryInvalidClass -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - // class not derived from RLMObject - XCTAssertThrows([realm objects:@"NonRealmPersonObject" where:@"age > 25"], @"invalid object type"); - XCTAssertThrows([[realm objects:@"NonRealmPersonObject" where:@"age > 25"] sortedResultsUsingProperty:@"age" ascending:YES], @"invalid object type"); - - // empty string for class name - XCTAssertThrows([realm objects:@"" where:@"age > 25"], @"missing class name"); - XCTAssertThrows([[realm objects:@"" where:@"age > 25"] sortedResultsUsingProperty:@"age" ascending:YES], @"missing class name"); - - // nil class name - XCTAssertThrows([realm objects:nil where:@"age > 25"], @"nil class name"); - XCTAssertThrows([[realm objects:nil where:@"age > 25"] sortedResultsUsingProperty:@"age" ascending:YES], @"nil class name"); -} - - (void)testPredicateNotSupported { // These are things which are valid predicates, but which we do not support @@ -702,182 +831,6 @@ module.exports = BaseTest.extend({ @"Property type mismatch between double and string"); } -- (void)testCustomSelectorsInNumericComparison -{ - BOOL (^isEmpty)(); - - isEmpty = [RLMPredicateUtil alwaysEmptyIntColSelectorPredicate]; - [self testCustomSelectorsInNumericComparison:@"integer" withProposition:isEmpty]; - - isEmpty = [RLMPredicateUtil alwaysEmptyFloatColSelectorPredicate]; - [self testCustomSelectorsInNumericComparison:@"float" withProposition:isEmpty]; - - isEmpty = [RLMPredicateUtil alwaysEmptyDoubleColSelectorPredicate]; - [self testCustomSelectorsInNumericComparison:@"double" withProposition:isEmpty]; - - isEmpty = [RLMPredicateUtil alwaysEmptyDateColSelectorPredicate]; - [self testCustomSelectorsInNumericComparison:@"date" withProposition:isEmpty]; -} - -- (void)testBooleanPredicate -{ - XCTAssertEqual([BoolObject objectsWhere:@"boolCol == TRUE"].count, - 0U, @"== operator in bool predicate."); - XCTAssertEqual([BoolObject objectsWhere:@"boolCol != TRUE"].count, - 0U, @"== operator in bool predicate."); - if (self.isNull) { - XCTAssertEqual([BoolObject objectsWhere:@"boolCol == NULL"].count, - 0U, @"== operator in bool predicate."); - XCTAssertEqual([BoolObject objectsWhere:@"boolCol != NULL"].count, - 0U, @"== operator in bool predicate."); - } - else { - XCTAssertThrows([BoolObject objectsWhere:@"boolCol == NULL"]); - XCTAssertThrows([BoolObject objectsWhere:@"boolCol != NULL"]); - } - - XCTAssertThrowsSpecificNamed([BoolObject objectsWhere:@"boolCol >= TRUE"], - NSException, - @"Invalid operator type", - @"Invalid operator in bool predicate."); -} - -- (void)testStringBeginsWith -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - [realm beginWriteTransaction]; - StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - if (self.isNull) { - so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - } - [realm commitWriteTransaction]; - - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'a'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'ab'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abcd'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abd'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'c'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'A'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH[c] 'a'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH[c] 'A'"].count); - - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'a'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'c'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'A'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH[c] 'a'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH[c] 'A'"].count); -} - -- (void)testStringEndsWith -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - [realm beginWriteTransaction]; - StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - if (self.isNull) { - so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - } - [realm commitWriteTransaction]; - - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'c'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'bc'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'abc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'aabc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'bbc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'a'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'C'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH[c] 'c'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH[c] 'C'"].count); - - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'c'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'a'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'C'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH[c] 'c'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH[c] 'C'"].count); -} - -- (void)testStringContains -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - [realm beginWriteTransaction]; - StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - if (self.isNull) { - so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - } - [realm commitWriteTransaction]; - - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'a'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'b'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'c'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'ab'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'bc'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'abc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'd'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'aabc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'bbc'"].count); - - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'C'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS[c] 'c'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS[c] 'C'"].count); - - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'd'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'c'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'C'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS[c] 'c'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS[c] 'C'"].count); -} - -- (void)testStringEquality -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - [realm beginWriteTransaction]; - StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - [realm commitWriteTransaction]; - - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol == 'abc'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol != 'def'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ==[c] 'abc'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ==[c] 'ABC'"].count); - - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol != 'abc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol == 'def'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol == 'ABC'"].count); - - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'abc'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol != 'def'"].count); - - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ==[c] 'abc'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ==[c] 'ABC'"].count); - - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol != 'abc'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'def'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'ABC'"].count); -} - -- (void)testStringUnsupportedOperations -{ - XCTAssertThrows([StringObject objectsWhere:@"stringCol LIKE 'abc'"]); - XCTAssertThrows([StringObject objectsWhere:@"stringCol MATCHES 'abc'"]); - XCTAssertThrows([StringObject objectsWhere:@"stringCol BETWEEN {'a', 'b'}"]); - XCTAssertThrows([StringObject objectsWhere:@"stringCol < 'abc'"]); - - XCTAssertThrows([AllTypesObject objectsWhere:@"objectCol.stringCol LIKE 'abc'"]); - XCTAssertThrows([AllTypesObject objectsWhere:@"objectCol.stringCol MATCHES 'abc'"]); - XCTAssertThrows([AllTypesObject objectsWhere:@"objectCol.stringCol BETWEEN {'a', 'b'}"]); - XCTAssertThrows([AllTypesObject objectsWhere:@"objectCol.stringCol < 'abc'"]); -} - - (void)testBinaryComparisonInPredicate { NSExpression *binary = [NSExpression expressionForConstantValue:[[NSData alloc] init]]; @@ -930,29 +883,6 @@ module.exports = BaseTest.extend({ @"Key path in absent in an integer comparison."); } -- (void)testFloatQuery -{ - RLMRealm *realm = [self realmWithTestPath]; - - [realm beginWriteTransaction]; - [FloatObject createInRealm:realm withValue:@[@1.7f]]; - [realm commitWriteTransaction]; - - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol > 1"] count]), 1U, @"1 object expected"); - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol > %d", 1] count]), 1U, @"1 object expected"); - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol = 1.7"] count]), 1U, @"1 object expected"); - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol = %f", 1.7f] count]), 1U, @"1 object expected"); - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol > 1.0"] count]), 1U, @"1 object expected"); - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol >= 1.0"] count]), 1U, @"1 object expected"); - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol < 1.0"] count]), 0U, @"0 objects expected"); - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol <= 1.0"] count]), 0U, @"0 objects expected"); - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol BETWEEN %@", @[@1.0, @2.0]] count]), 1U, @"1 object expected"); - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol = %e", 1.7] count]), 1U, @"1 object expected"); - XCTAssertEqual(([[realm objects:[FloatObject className] where:@"floatCol == %f", FLT_MAX] count]), 0U, @"0 objects expected"); - XCTAssertThrows(([[realm objects:[FloatObject className] where:@"floatCol = 3.5e+38"] count]), @"Too large to be a float"); - XCTAssertThrows(([[realm objects:[FloatObject className] where:@"floatCol = -3.5e+38"] count]), @"Too small to be a float"); -} - - (void)testLiveQueriesInsideTransaction { RLMRealm *realm = [RLMRealm defaultRealm]; From be93d3dd583754a4e58f7f45691dc148bb77981a Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 12:17:39 -0800 Subject: [PATCH 33/47] first string tests and custom error messages --- src/object-store/parser/parser.cpp | 21 +++++++++++++++++++-- tests/QueryTests.js | 30 ++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index b391c9b4..8d1c3be6 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -288,17 +288,34 @@ template<> struct action< not_pre > } }; +template< typename Rule > +struct error_message_control : pegtl::normal< Rule > +{ + static const std::string error_message; + + template< typename Input, typename ... States > + static void raise( const Input & in, States && ... ) + { + throw pegtl::parse_error( error_message, in ); + } +}; + +template<> +const std::string error_message_control< chars >::error_message = "Invalid characters in string constant."; + +template< typename Rule> +const std::string error_message_control< Rule >::error_message = "Invalid predicate."; + Predicate parse(const std::string &query) { analyze< pred >(); - const std::string source = "user query"; Predicate out_predicate(Predicate::Type::And); ParserState state; state.predicate_stack.push_back(&out_predicate); - pegtl::parse< must< pred, eof >, action >(query, source, state); + pegtl::parse< must< pred, eof >, action, error_message_control >(query, query, state); if (out_predicate.type == Predicate::Type::And && out_predicate.cpnd.sub_predicates.size() == 1) { return std::move(out_predicate.cpnd.sub_predicates.back()); } diff --git a/tests/QueryTests.js b/tests/QueryTests.js index dc2214f3..784389f6 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -158,7 +158,7 @@ var testCases = { "floatTests" : { "schema" : [{ "name": "FloatObject", - "properties": [{ "name": "floatCol", "type": Realm.Types.Float }], + "properties": [{ "name": "floatCol", "type": Realm.Types.FLOAT }], }], "objects": [ { "type": "FloatObject", "value": [-1.001] }, @@ -191,7 +191,7 @@ var testCases = { "doubleTests" : { "schema" : [{ "name": "DoubleObject", - "properties": [{ "name": "doubleCol", "type": Realm.Types.Double }], + "properties": [{ "name": "doubleCol", "type": Realm.Types.DOUBLE }], }], "objects": [ { "type": "DoubleObject", "value": [-1.001] }, @@ -221,16 +221,23 @@ var testCases = { "stringTests" : { "schema" : [{ - "name": "DoubleObject", - "properties": [{ "name": "doubleCol", "type": Realm.Types.Double }], + "name": "StringObject", + "properties": [{ "name": "stringCol", "type": Realm.Types.STRING }], }], "objects": [ - { "type": "DoubleObject", "value": [-1.001] }, - { "type": "DoubleObject", "value": [0.0] }, - { "type": "DoubleObject", "value": [100.2] }, + { "type": "StringObject", "value": ["A"] }, + { "type": "StringObject", "value": ["a"] }, + { "type": "StringObject", "value": ["a"] }, + { "type": "StringObject", "value": ["abc"] }, + { "type": "StringObject", "value": [""] }, + { "type": "StringObject", "value": ["\\\"\\n\\0\\r\\\\'"] }, ], "tests": [ - ["QueryCount", 1, "DoubleObject", "doubleCol == -1.001"], + ["QueryCount", 2, "StringObject", "stringCol == 'a'"], + ["QueryCount", 2, "StringObject", "stringCol == \"a\""], + ["QueryCount", 1, "StringObject", "stringCol == 'abc'"], + ["QueryCount", 1, "StringObject", "stringCol == ''"], + ["QueryCount", 1, "StringObject", "stringCol == \"\\\"\\n\\0\\r\\\\'\""], ] }, @@ -374,10 +381,13 @@ module.exports = BaseTest.extend({ runQuerySuite(testCases.intTests); }, testFloatQueries: function() { - runQuerySuite(testCases.intTests); + runQuerySuite(testCases.floatTests); }, testDoubleQueries: function() { - runQuerySuite(testCases.intTests); + runQuerySuite(testCases.doubleTests); + }, + testStringQueries: function() { + runQuerySuite(testCases.stringTests); }, }); From 7be7d330ac46fcd3ea1f5ce24c635755680c50e5 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 12:27:52 -0800 Subject: [PATCH 34/47] more string tests --- tests/QueryTests.js | 149 +++++++------------------------------------- 1 file changed, 21 insertions(+), 128 deletions(-) diff --git a/tests/QueryTests.js b/tests/QueryTests.js index 784389f6..8970ea1f 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -228,148 +228,41 @@ var testCases = { { "type": "StringObject", "value": ["A"] }, { "type": "StringObject", "value": ["a"] }, { "type": "StringObject", "value": ["a"] }, + { "type": "StringObject", "value": ["C"] }, + { "type": "StringObject", "value": ["c"] }, { "type": "StringObject", "value": ["abc"] }, + { "type": "StringObject", "value": ["ABC"] }, { "type": "StringObject", "value": [""] }, { "type": "StringObject", "value": ["\\\"\\n\\0\\r\\\\'"] }, ], "tests": [ ["QueryCount", 2, "StringObject", "stringCol == 'a'"], + ["QueryCount", 1, "StringObject", "'c' == stringCol"], ["QueryCount", 2, "StringObject", "stringCol == \"a\""], - ["QueryCount", 1, "StringObject", "stringCol == 'abc'"], + ["QueryCount", 1, "StringObject", "stringCol=='abc'"], ["QueryCount", 1, "StringObject", "stringCol == ''"], + ["QueryCount", 8, "StringObject", "stringCol != ''"], ["QueryCount", 1, "StringObject", "stringCol == \"\\\"\\n\\0\\r\\\\'\""], + ["QueryCount", 3, "StringObject", "stringCol BEGINSWITH 'a'"], + ["QueryCount", 1, "StringObject", "stringCol beginswith 'ab'"], + ["QueryCount", 0, "StringObject", "stringCol BEGINSWITH 'abcd'"], + ["QueryCount", 2, "StringObject", "stringCol BEGINSWITH 'A'"], + ["QueryCount", 2, "StringObject", "stringCol ENDSWITH 'c'"], + ["QueryCount", 1, "StringObject", "stringCol endswith 'bc'"], + ["QueryCount", 9, "StringObject", "stringCol ENDSWITH ''"], + ["QueryCount", 1, "StringObject", "stringCol CONTAINS 'b'"], + ["QueryCount", 2, "StringObject", "stringCol contains 'c'"], + ["QueryCount", 9, "StringObject", "stringCol CONTAINS ''"], + + // ["QueryCount", 3, "StringObject", "stringCol ==[c] 'a'"], + // ["QueryCount", 5, "StringObject", "stringCol BEGINSWITH[c] 'A'"], + // ["QueryCount", 4, "StringObject", "stringCol ENDSWITH[c] 'c'"], + // ["QueryCount", 2, "StringObject", "stringCol CONTAINS[c] 'B'"], ] }, }; - -/* - -- (void)testStringBeginsWith -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - [realm beginWriteTransaction]; - StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - if (self.isNull) { - so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - } - [realm commitWriteTransaction]; - - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'a'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'ab'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abcd'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'abd'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'c'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol BEGINSWITH 'A'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH[c] 'a'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol BEGINSWITH[c] 'A'"].count); - - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'a'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'c'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH 'A'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH[c] 'a'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol BEGINSWITH[c] 'A'"].count); -} - -- (void)testStringEndsWith -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - [realm beginWriteTransaction]; - StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - if (self.isNull) { - so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - } - [realm commitWriteTransaction]; - - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'c'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'bc'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH 'abc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'aabc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'bbc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'a'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol ENDSWITH 'C'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH[c] 'c'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ENDSWITH[c] 'C'"].count); - - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'c'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'a'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH 'C'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH[c] 'c'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ENDSWITH[c] 'C'"].count); -} - -- (void)testStringContains -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - [realm beginWriteTransaction]; - StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - if (self.isNull) { - so = [StringObject createInRealm:realm withValue:@[NSNull.null]]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - } - [realm commitWriteTransaction]; - - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'a'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'b'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'c'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'ab'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'bc'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS 'abc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'd'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'aabc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'bbc'"].count); - - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol CONTAINS 'C'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS[c] 'c'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol CONTAINS[c] 'C'"].count); - - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'd'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'c'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS 'C'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS[c] 'c'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol CONTAINS[c] 'C'"].count); -} - -- (void)testStringEquality -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - [realm beginWriteTransaction]; - StringObject *so = [StringObject createInRealm:realm withValue:(@[@"abc"])]; - [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], NSDate.date, @YES, @1LL, @1, so]]; - [realm commitWriteTransaction]; - - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol == 'abc'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol != 'def'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ==[c] 'abc'"].count); - XCTAssertEqual(1U, [StringObject objectsWhere:@"stringCol ==[c] 'ABC'"].count); - - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol != 'abc'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol == 'def'"].count); - XCTAssertEqual(0U, [StringObject objectsWhere:@"stringCol == 'ABC'"].count); - - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'abc'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol != 'def'"].count); - - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ==[c] 'abc'"].count); - XCTAssertEqual(1U, [AllTypesObject objectsWhere:@"objectCol.stringCol ==[c] 'ABC'"].count); - - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol != 'abc'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'def'"].count); - XCTAssertEqual(0U, [AllTypesObject objectsWhere:@"objectCol.stringCol == 'ABC'"].count); -} - -*/ module.exports = BaseTest.extend({ testDateQueries: function() { runQuerySuite(testCases.dateTests); From aa3bad1a69a3f32f00908dcc3b29701b02256792 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 12:40:25 -0800 Subject: [PATCH 35/47] more string tests and bugfix --- src/object-store/parser/query_builder.cpp | 3 +++ tests/QueryTests.js | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp index a836f777..9039ef6b 100644 --- a/src/object-store/parser/query_builder.cpp +++ b/src/object-store/parser/query_builder.cpp @@ -267,6 +267,9 @@ struct ValueGetter { if (value.type == parser::Expression::Type::Argument) { return args.string_for_argument(std::stoi(value.s)); } + if (value.type != parser::Expression::Type::String) { + throw std::runtime_error("Attempting to compare String property to a non-String value"); + } return value.s; } }; diff --git a/tests/QueryTests.js b/tests/QueryTests.js index 8970ea1f..3edf4773 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -52,9 +52,10 @@ function runQuerySuite(suite) { TestCase.assertEqual(test[1], results.length, "Query '" + args[1] + "' on type '" + args[0] + "' expected " + test[1] + " results, got " + results.length); } else if (test[0] == "QueryThrows") { + var args = test.slice(1); TestCase.assertThrows(function() { - realm.objects.apply(realm, test.slice(1)); - }); + realm.objects.apply(realm, args); + }, "Expected exception not thrown for query: " + JSON.stringify(args)); } else { throw "Invalid query test '" + test[0] + "'"; @@ -253,6 +254,12 @@ var testCases = { ["QueryCount", 1, "StringObject", "stringCol CONTAINS 'b'"], ["QueryCount", 2, "StringObject", "stringCol contains 'c'"], ["QueryCount", 9, "StringObject", "stringCol CONTAINS ''"], + ["QueryCount", 2, "StringObject", "stringCol == $0", "a"], + ["QueryCount", 2, "StringObject", "stringCol ENDSWITH $0", "c"], + + ["QueryThrows", "StringObject", "stringCol == true"], + ["QueryThrows", "StringObject", "stringCol == 123"], + ["QueryThrows", "StringObject", "stringCol CONTAINS $0", 1], // ["QueryCount", 3, "StringObject", "stringCol ==[c] 'a'"], // ["QueryCount", 5, "StringObject", "stringCol BEGINSWITH[c] 'A'"], From 07e818bdf22d06a10a9a6d9edbf8a5e806b5a2ae Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 12:50:23 -0800 Subject: [PATCH 36/47] rebase fixes --- RealmJS.xcodeproj/project.pbxproj | 73 ++++++++++++------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index eee24ef7..a8c60c49 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -66,7 +66,6 @@ F636F6C81BCDB3570023F35C /* RealmReact.h in Headers */ = {isa = PBXBuildFile; fileRef = 0270BCCF1B7D067300010E03 /* RealmReact.h */; settings = {ATTRIBUTES = (Public, ); }; }; F64426C51BCDB1E200A81210 /* RealmJS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F64E1EF11BC3510E00E0E150 /* util.js in Resources */ = {isa = PBXBuildFile; fileRef = F64E1EF01BC3510E00E0E150 /* util.js */; }; - F67191381BCE231100AD0939 /* GCDWebServers.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 02A3C7941BC4317A00B1A7BE /* GCDWebServers.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F68A278C1BC2722A0063D40A /* RJSModuleLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = F68A278B1BC2722A0063D40A /* RJSModuleLoader.m */; }; F68A278E1BC30F0A0063D40A /* index.js in Resources */ = {isa = PBXBuildFile; fileRef = F68A278D1BC30F0A0063D40A /* index.js */; }; F6BB7DF11BF681BC00D0A69E /* base64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F6BB7DEF1BF681BC00D0A69E /* base64.cpp */; }; @@ -75,42 +74,35 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 02313C461BCC4A43003F9107 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = CEE28CEE1AE0051F00F4023C; - remoteInfo = "GCDWebServers (iOS)"; - }; - 02A3C78D1BC4317A00B1A7BE /* PBXContainerItemProxy */ = { + 021CEA351BFD1BA000D69390 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; proxyType = 2; remoteGlobalIDString = 8DD76FB20486AB0100D96B5E; remoteInfo = "GCDWebServer (Mac)"; }; - 02A3C78F1BC4317A00B1A7BE /* PBXContainerItemProxy */ = { + 021CEA371BFD1BA000D69390 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; proxyType = 2; remoteGlobalIDString = E221125A1690B4DE0048D2B2; remoteInfo = "GCDWebServer (iOS)"; }; - 02A3C7911BC4317A00B1A7BE /* PBXContainerItemProxy */ = { + 021CEA391BFD1BA000D69390 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; proxyType = 2; remoteGlobalIDString = CEE28CD11AE004D800F4023C; remoteInfo = "GCDWebServers (Mac)"; }; - 02A3C7931BC4317A00B1A7BE /* PBXContainerItemProxy */ = { + 021CEA3B1BFD1BA000D69390 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; proxyType = 2; remoteGlobalIDString = CEE28CEF1AE0051F00F4023C; remoteInfo = "GCDWebServers (iOS)"; }; - 02A3C7951BC4317A00B1A7BE /* PBXContainerItemProxy */ = { + 021CEA3D1BFD1BA000D69390 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; proxyType = 2; @@ -154,7 +146,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - F67191381BCE231100AD0939 /* GCDWebServers.framework in Embed Frameworks */, F64426C51BCDB1E200A81210 /* RealmJS.framework in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -213,7 +204,7 @@ 029445961BDEDF46006D1617 /* external_commit_helper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = external_commit_helper.hpp; path = "src/object-store/impl/apple/external_commit_helper.hpp"; sourceTree = ""; }; 0294465C1BF27DE6006D1617 /* query_builder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = query_builder.cpp; path = "src/object-store/parser/query_builder.cpp"; sourceTree = ""; }; 0294465D1BF27DE6006D1617 /* query_builder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = query_builder.hpp; path = "src/object-store/parser/query_builder.hpp"; sourceTree = ""; }; - 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GCDWebServer.xcodeproj; path = vendor/GCDWebServer/GCDWebServer.xcodeproj; sourceTree = ""; }; + 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GCDWebServer.xcodeproj; path = GCDWebServer/GCDWebServer.xcodeproj; sourceTree = ""; }; 02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 02B29A161B7CF7C9008A7E6B /* RealmReact.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmReact.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CB11AE99CEC009B348C /* RealmJS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmJS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -265,6 +256,18 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 021CEA2D1BFD1BA000D69390 /* Products */ = { + isa = PBXGroup; + children = ( + 021CEA361BFD1BA000D69390 /* GCDWebServer */, + 021CEA381BFD1BA000D69390 /* GCDWebServer.app */, + 021CEA3A1BFD1BA000D69390 /* GCDWebServers.framework */, + 021CEA3C1BFD1BA000D69390 /* GCDWebServers.framework */, + 021CEA3E1BFD1BA000D69390 /* Tests.xctest */, + ); + name = Products; + sourceTree = ""; + }; 0270BC3D1B7CFBFD00010E03 /* RealmJS */ = { isa = PBXGroup; children = ( @@ -324,18 +327,6 @@ name = RealmReact; sourceTree = ""; }; - 02A3C7851BC4317A00B1A7BE /* Products */ = { - isa = PBXGroup; - children = ( - 02A3C78E1BC4317A00B1A7BE /* GCDWebServer */, - 02A3C7901BC4317A00B1A7BE /* GCDWebServer.app */, - 02A3C7921BC4317A00B1A7BE /* GCDWebServers.framework */, - 02A3C7941BC4317A00B1A7BE /* GCDWebServers.framework */, - 02A3C7961BC4317A00B1A7BE /* Tests.xctest */, - ); - name = Products; - sourceTree = ""; - }; 02B58CA71AE99CEB009B348C = { isa = PBXGroup; children = ( @@ -467,7 +458,6 @@ buildRules = ( ); dependencies = ( - 02313C471BCC4A43003F9107 /* PBXTargetDependency */, 02B29A301B7CF7ED008A7E6B /* PBXTargetDependency */, ); name = RealmReact; @@ -544,7 +534,7 @@ projectDirPath = ""; projectReferences = ( { - ProductGroup = 02A3C7851BC4317A00B1A7BE /* Products */; + ProductGroup = 021CEA2D1BFD1BA000D69390 /* Products */; ProjectRef = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; }, { @@ -562,39 +552,39 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 02A3C78E1BC4317A00B1A7BE /* GCDWebServer */ = { + 021CEA361BFD1BA000D69390 /* GCDWebServer */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = GCDWebServer; - remoteRef = 02A3C78D1BC4317A00B1A7BE /* PBXContainerItemProxy */; + remoteRef = 021CEA351BFD1BA000D69390 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 02A3C7901BC4317A00B1A7BE /* GCDWebServer.app */ = { + 021CEA381BFD1BA000D69390 /* GCDWebServer.app */ = { isa = PBXReferenceProxy; fileType = wrapper.application; path = GCDWebServer.app; - remoteRef = 02A3C78F1BC4317A00B1A7BE /* PBXContainerItemProxy */; + remoteRef = 021CEA371BFD1BA000D69390 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 02A3C7921BC4317A00B1A7BE /* GCDWebServers.framework */ = { + 021CEA3A1BFD1BA000D69390 /* GCDWebServers.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = GCDWebServers.framework; - remoteRef = 02A3C7911BC4317A00B1A7BE /* PBXContainerItemProxy */; + remoteRef = 021CEA391BFD1BA000D69390 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 02A3C7941BC4317A00B1A7BE /* GCDWebServers.framework */ = { + 021CEA3C1BFD1BA000D69390 /* GCDWebServers.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = GCDWebServers.framework; - remoteRef = 02A3C7931BC4317A00B1A7BE /* PBXContainerItemProxy */; + remoteRef = 021CEA3B1BFD1BA000D69390 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 02A3C7961BC4317A00B1A7BE /* Tests.xctest */ = { + 021CEA3E1BFD1BA000D69390 /* Tests.xctest */ = { isa = PBXReferenceProxy; fileType = wrapper.cfbundle; path = Tests.xctest; - remoteRef = 02A3C7951BC4317A00B1A7BE /* PBXContainerItemProxy */; + remoteRef = 021CEA3D1BFD1BA000D69390 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 02EE6D8A1BD87E310016A82E /* ReactTests.app */ = { @@ -706,11 +696,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 02313C471BCC4A43003F9107 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "GCDWebServers (iOS)"; - targetProxy = 02313C461BCC4A43003F9107 /* PBXContainerItemProxy */; - }; 02B29A301B7CF7ED008A7E6B /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 02B58CB01AE99CEC009B348C /* RealmJS */; From 0e8ca5ed057d6d8506dc4ce3f5f8ae2d28b8e351 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Wed, 18 Nov 2015 17:49:05 -0800 Subject: [PATCH 37/47] add binary query support --- src/object-store/parser/query_builder.cpp | 79 +++++++++++++++++++---- src/object-store/parser/query_builder.hpp | 2 + 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp index 9039ef6b..353588e8 100644 --- a/src/object-store/parser/query_builder.cpp +++ b/src/object-store/parser/query_builder.cpp @@ -102,42 +102,81 @@ void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType void add_string_constraint_to_query(Query &query, Predicate::Operator op, Columns &&column, - StringData value) { + std::string value) { bool case_sensitive = true; - StringData sd = value; switch (op) { case Predicate::Operator::BeginsWith: - query.and_query(column.begins_with(sd, case_sensitive)); + query.and_query(column.begins_with(value, case_sensitive)); break; case Predicate::Operator::EndsWith: - query.and_query(column.ends_with(sd, case_sensitive)); + query.and_query(column.ends_with(value, case_sensitive)); break; case Predicate::Operator::Contains: - query.and_query(column.contains(sd, case_sensitive)); + query.and_query(column.contains(value, case_sensitive)); break; case Predicate::Operator::Equal: - query.and_query(column.equal(sd, case_sensitive)); + query.and_query(column.equal(value, case_sensitive)); break; case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(sd, case_sensitive)); + query.and_query(column.not_equal(value, case_sensitive)); break; default: throw std::runtime_error("Unsupported operator for string queries."); } } -void add_string_constraint_to_query(realm::Query& query, +void add_string_constraint_to_query(realm::Query &query, Predicate::Operator op, - StringData value, + std::string value, Columns &&column) { bool case_sensitive = true; - StringData sd = value; switch (op) { case Predicate::Operator::Equal: - query.and_query(column.equal(sd, case_sensitive)); + query.and_query(column.equal(value, case_sensitive)); break; case Predicate::Operator::NotEqual: - query.and_query(column.not_equal(sd, case_sensitive)); + query.and_query(column.not_equal(value, case_sensitive)); + break; + default: + throw std::runtime_error("Substring comparison not supported for keypath substrings."); + } +} + +void add_binary_constraint_to_query(Query &query, + Predicate::Operator op, + Columns &&column, + std::string value) { + switch (op) { + case Predicate::Operator::BeginsWith: + query.begins_with(column.m_column, value); + break; + case Predicate::Operator::EndsWith: + query.ends_with(column.m_column, value); + break; + case Predicate::Operator::Contains: + query.contains(column.m_column, value); + break; + case Predicate::Operator::Equal: + query.equal(column.m_column, value); + break; + case Predicate::Operator::NotEqual: + query.not_equal(column.m_column, value); + break; + default: + throw std::runtime_error("Unsupported operator for binary queries."); + } +} + +void add_binary_constraint_to_query(realm::Query &query, + Predicate::Operator op, + std::string value, + Columns &&column) { + switch (op) { + case Predicate::Operator::Equal: + query.equal(column.m_column, value); + break; + case Predicate::Operator::NotEqual: + query.not_equal(column.m_column, value); break; default: throw std::runtime_error("Substring comparison not supported for keypath substrings."); @@ -274,6 +313,17 @@ struct ValueGetter { } }; +template +struct ValueGetter { + static std::string convert(TableGetter&&, const parser::Expression & value, Arguments &args) + { + if (value.type == parser::Expression::Type::Argument) { + return args.binary_for_argument(std::stoi(value.s)); + } + throw std::runtime_error("Binary properties must be compared against a binary argument."); + } +}; + template auto value_of_type_for_query(TableGetter&& tables, Value&& value, Arguments &args) { @@ -309,10 +359,13 @@ void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &obje value_of_type_for_query(expr.table_getter, rhs, args)); break; case PropertyTypeString: - case PropertyTypeData: add_string_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), value_of_type_for_query(expr.table_getter, rhs, args)); break; + case PropertyTypeData: + add_binary_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), + value_of_type_for_query(expr.table_getter, rhs, args)); + break; default: { throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); } diff --git a/src/object-store/parser/query_builder.hpp b/src/object-store/parser/query_builder.hpp index 1de648b3..2d7f9d7c 100644 --- a/src/object-store/parser/query_builder.hpp +++ b/src/object-store/parser/query_builder.hpp @@ -40,6 +40,7 @@ namespace realm { virtual float float_for_argument(size_t argument_index) = 0; virtual double double_for_argument(size_t argument_index) = 0; virtual std::string string_for_argument(size_t argument_index) = 0; + virtual std::string binary_for_argument(size_t argument_index) = 0; virtual DateTime datetime_for_argument(size_t argument_index) = 0; virtual size_t object_index_for_argument(size_t argument_index) = 0; virtual bool is_argument_null(size_t argument_index) = 0; @@ -57,6 +58,7 @@ namespace realm { virtual float float_for_argument(size_t argument_index) { return Accessor::to_float(m_ctx, argument_at(argument_index)); } virtual double double_for_argument(size_t argument_index) { return Accessor::to_double(m_ctx, argument_at(argument_index)); } virtual std::string string_for_argument(size_t argument_index) { return Accessor::to_string(m_ctx, argument_at(argument_index)); } + virtual std::string binary_for_argument(size_t argument_index) { return Accessor::to_binary(m_ctx, argument_at(argument_index)); } virtual DateTime datetime_for_argument(size_t argument_index) { return Accessor::to_datetime(m_ctx, argument_at(argument_index)); } virtual size_t object_index_for_argument(size_t argument_index) { return Accessor::to_existing_object_index(m_ctx, argument_at(argument_index)); } virtual bool is_argument_null(size_t argument_index) { return Accessor::is_null(m_ctx, argument_at(argument_index)); } From 3a9137fa08184daa0032a9bb01256623fd740a07 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 19 Nov 2015 14:36:42 -0800 Subject: [PATCH 38/47] put tests in JSON --- RealmJS.xcodeproj/project.pbxproj | 4 + tests/QueryTests.js | 209 +----------------------------- tests/RJSModuleLoader.h | 1 + tests/RJSModuleLoader.m | 44 ++++++- tests/queryTests.json | 207 +++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 213 deletions(-) create mode 100644 tests/queryTests.json diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index a8c60c49..a7558118 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -55,6 +55,7 @@ 029445981BDEDF46006D1617 /* external_commit_helper.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029445961BDEDF46006D1617 /* external_commit_helper.hpp */; }; 0294465E1BF27DE6006D1617 /* query_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0294465C1BF27DE6006D1617 /* query_builder.cpp */; }; 0294465F1BF27DE6006D1617 /* query_builder.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0294465D1BF27DE6006D1617 /* query_builder.hpp */; }; + 02AAAEF01BFE72F2002DD081 /* queryTests.json in Resources */ = {isa = PBXBuildFile; fileRef = 02AAAEEF1BFE72F2002DD081 /* queryTests.json */; }; 02B29A311B7CF86D008A7E6B /* RealmJS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; }; 02B58CCE1AE99D4D009B348C /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; 02C0864E1BCDB27000942F9C /* list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02C0864C1BCDB27000942F9C /* list.cpp */; }; @@ -206,6 +207,7 @@ 0294465D1BF27DE6006D1617 /* query_builder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = query_builder.hpp; path = "src/object-store/parser/query_builder.hpp"; sourceTree = ""; }; 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GCDWebServer.xcodeproj; path = GCDWebServer/GCDWebServer.xcodeproj; sourceTree = ""; }; 02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + 02AAAEEF1BFE72F2002DD081 /* queryTests.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = queryTests.json; path = tests/queryTests.json; sourceTree = SOURCE_ROOT; }; 02B29A161B7CF7C9008A7E6B /* RealmReact.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmReact.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CB11AE99CEC009B348C /* RealmJS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmJS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmJSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -361,6 +363,7 @@ 02D456D91B7E59A500EE1299 /* ArrayTests.js */, 0270BC791B7D020100010E03 /* ObjectTests.js */, 02E9A9F01BFA84F100939F86 /* QueryTests.js */, + 02AAAEEF1BFE72F2002DD081 /* queryTests.json */, 0270BC7C1B7D020100010E03 /* RealmTests.js */, 0270BC7D1B7D020100010E03 /* ResultsTests.js */, 0270BC7A1B7D020100010E03 /* RealmJSTests.h */, @@ -623,6 +626,7 @@ 0270BC861B7D020100010E03 /* schemas.js in Resources */, F6F405F81BCF0C1A00A1E24F /* base-test.js in Resources */, F68A278E1BC30F0A0063D40A /* index.js in Resources */, + 02AAAEF01BFE72F2002DD081 /* queryTests.json in Resources */, 0270BC831B7D020100010E03 /* RealmTests.js in Resources */, 0270BC841B7D020100010E03 /* ResultsTests.js in Resources */, ); diff --git a/tests/QueryTests.js b/tests/QueryTests.js index 3edf4773..bb6932a4 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -9,6 +9,7 @@ var Realm = require('realm'); var BaseTest = require('./base-test'); var TestCase = require('./asserts'); var schemas = require('./schemas'); +var testCases = require('./queryTests.json'); var typeConverters = {}; typeConverters[Realm.Types.DATE] = function(value) { return new Date(value); }; @@ -57,218 +58,12 @@ function runQuerySuite(suite) { realm.objects.apply(realm, args); }, "Expected exception not thrown for query: " + JSON.stringify(args)); } - else { + else if (test[0] != "Disabled") { throw "Invalid query test '" + test[0] + "'"; } } } -var testCases = { - -"dateTests": { - "schema" : [{ - "name": "DateObject", - "properties": [{ "name": "date", "type": Realm.Types.DATE }], - }], - "objects": [ - { "type": "DateObject", "value": [10000] }, - { "type": "DateObject", "value": [10001] }, - { "type": "DateObject", "value": [10002] }, - ], - "tests": [ - ["QueryCount", 2, "DateObject", "date < $0", [2, 0]], - ["QueryCount", 3, "DateObject", "date <= $0", [2, 0]], - ["QueryCount", 2, "DateObject", "date > $0", [0, 0]], - ["QueryCount", 3, "DateObject", "date >= $0", [0, 0]], - ["QueryCount", 1, "DateObject", "date == $0", [0, 0]], - ["QueryCount", 2, "DateObject", "date != $0", [0, 0]], - - ["QueryThrows", "DateObject", "date == 'not a date'"], - ["QueryThrows", "DateObject", "date == 1"], - ["QueryThrows", "DateObject", "date == $0", 1], - ] -}, - -"boolTests" : { - "schema" : [{ - "name": "BoolObject", - "properties": [{ "name": "boolCol", "type": Realm.Types.BOOL }], - }], - "objects": [ - { "type": "BoolObject", "value": [false] }, - { "type": "BoolObject", "value": [true] }, - { "type": "BoolObject", "value": [true] }, - ], - "tests": [ - ["QueryCount", 2, "BoolObject", "boolCol == true"], - ["QueryCount", 1, "BoolObject", "boolCol==false"], - ["QueryCount", 1, "BoolObject", "boolCol != true"], - ["QueryCount", 2, "BoolObject", "true == boolCol"], - ["QueryCount", 2, "BoolObject", "boolCol == TRUE"], - ["QueryCount", 1, "BoolObject", "boolCol == FALSE"], - ["QueryCount", 2, "BoolObject", "boolCol == $0", true], - ["QueryCount", 1, "BoolObject", "boolCol == $0", false], - ["QueryCount", 0, "BoolObject", "boolCol == true && boolCol == false"], - ["QueryCount", 3, "BoolObject", "boolCol == true || boolCol == false"], - - ["QueryThrows", "BoolObject", "boolCol == 0"], - ["QueryThrows", "BoolObject", "boolCol == 1"], - ["QueryThrows", "BoolObject", "boolCol == 'not a bool'"], - ["QueryThrows", "BoolObject", "boolCol == $0", "not a bool"], - ["QueryThrows", "BoolObject", "boolCol > true"], - ["QueryThrows", "BoolObject", "boolCol >= true"], - ["QueryThrows", "BoolObject", "boolCol < true"], - ["QueryThrows", "BoolObject", "boolCol <= true"], - ["QueryThrows", "BoolObject", "boolCol BEGINSWITH true"], - ["QueryThrows", "BoolObject", "boolCol CONTAINS true"], - ["QueryThrows", "BoolObject", "boolCol ENDSWITH true"], - ] -}, - -"intTests" : { - "schema" : [{ - "name": "IntObject", - "properties": [{ "name": "intCol", "type": Realm.Types.INT }], - }], - "objects": [ - { "type": "IntObject", "value": [-1] }, - { "type": "IntObject", "value": [0] }, - { "type": "IntObject", "value": [100] }, - ], - "tests": [ - ["QueryCount", 1, "IntObject", "intCol == -1"], - ["QueryCount", 1, "IntObject", "intCol==0"], - ["QueryCount", 0, "IntObject", "1 == intCol"], - ["QueryCount", 2, "IntObject", "intCol != 0"], - ["QueryCount", 2, "IntObject", "intCol > -1"], - ["QueryCount", 3, "IntObject", "intCol >= -1"], - ["QueryCount", 2, "IntObject", "intCol < 100"], - ["QueryCount", 3, "IntObject", "intCol <= 100"], - ["QueryCount", 1, "IntObject", "intCol > 0x1F"], - ["QueryCount", 1, "IntObject", "intCol == $0", 100], - - ["QueryThrows", "IntObject", "intCol == 'not an int'"], - ["QueryThrows", "IntObject", "intCol == true"], - ["QueryThrows", "IntObject", "intCol == $0", "not an int"], - ["QueryThrows", "IntObject", "intCol BEGINSWITH 1"], - ["QueryThrows", "IntObject", "intCol CONTAINS 1"], - ["QueryThrows", "IntObject", "intCol ENDSWITH 1"], - ] -}, - -"floatTests" : { - "schema" : [{ - "name": "FloatObject", - "properties": [{ "name": "floatCol", "type": Realm.Types.FLOAT }], - }], - "objects": [ - { "type": "FloatObject", "value": [-1.001] }, - { "type": "FloatObject", "value": [0.0] }, - { "type": "FloatObject", "value": [100.2] }, - ], - "tests": [ - ["QueryCount", 1, "FloatObject", "floatCol == -1.001"], - ["QueryCount", 1, "FloatObject", "floatCol = 0"], - ["QueryCount", 0, "FloatObject", "1 == floatCol"], - ["QueryCount", 2, "FloatObject", "floatCol != 0"], - ["QueryCount", 2, "FloatObject", "floatCol > -1.001"], - ["QueryCount", 3, "FloatObject", "floatCol >= -1.001"], - ["QueryCount", 2, "FloatObject", "floatCol < 100.2"], - ["QueryCount", 3, "FloatObject", "floatCol <= 100.2"], - ["QueryCount", 1, "FloatObject", "floatCol > 0x1F"], - ["QueryCount", 1, "FloatObject", "floatCol == $0", 100.2], - - ["QueryThrows", "FloatObject", "floatCol == 'not a float'"], - ["QueryThrows", "FloatObject", "floatCol == true"], - ["QueryThrows", "FloatObject", "floatCol == $0", "not a float"], - ["QueryThrows", "FloatObject", "floatCol BEGINSWITH 1"], - ["QueryThrows", "FloatObject", "floatCol CONTAINS 1"], - ["QueryThrows", "FloatObject", "floatCol ENDSWITH 1"], - //XCTAssertThrows(([[realm objects:[FloatObject className] where:@"floatCol = 3.5e+38"] count]), @"Too large to be a float"); - //XCTAssertThrows(([[realm objects:[FloatObject className] where:@"floatCol = -3.5e+38"] count]), @"Too small to be a float"); - ] -}, - -"doubleTests" : { - "schema" : [{ - "name": "DoubleObject", - "properties": [{ "name": "doubleCol", "type": Realm.Types.DOUBLE }], - }], - "objects": [ - { "type": "DoubleObject", "value": [-1.001] }, - { "type": "DoubleObject", "value": [0.0] }, - { "type": "DoubleObject", "value": [100.2] }, - ], - "tests": [ - ["QueryCount", 1, "DoubleObject", "doubleCol == -1.001"], - ["QueryCount", 1, "DoubleObject", "doubleCol == 0"], - ["QueryCount", 0, "DoubleObject", "1 == doubleCol"], - ["QueryCount", 2, "DoubleObject", "doubleCol != 0"], - ["QueryCount", 2, "DoubleObject", "doubleCol > -1.001"], - ["QueryCount", 3, "DoubleObject", "doubleCol >= -1.001"], - ["QueryCount", 2, "DoubleObject", "doubleCol < 100.2"], - ["QueryCount", 3, "DoubleObject", "doubleCol <= 100.2"], - ["QueryCount", 1, "DoubleObject", "doubleCol > 0x1F"], - ["QueryCount", 1, "DoubleObject", "doubleCol == $0", 100.2], - - ["QueryThrows", "DoubleObject", "doubleCol == 'not a double'"], - ["QueryThrows", "DoubleObject", "doubleCol == true"], - ["QueryThrows", "DoubleObject", "doubleCol == $0", "not a double"], - ["QueryThrows", "DoubleObject", "doubleCol BEGINSWITH 1"], - ["QueryThrows", "DoubleObject", "doubleCol CONTAINS 1"], - ["QueryThrows", "DoubleObject", "doubleCol ENDSWITH 1"], - ] -}, - -"stringTests" : { - "schema" : [{ - "name": "StringObject", - "properties": [{ "name": "stringCol", "type": Realm.Types.STRING }], - }], - "objects": [ - { "type": "StringObject", "value": ["A"] }, - { "type": "StringObject", "value": ["a"] }, - { "type": "StringObject", "value": ["a"] }, - { "type": "StringObject", "value": ["C"] }, - { "type": "StringObject", "value": ["c"] }, - { "type": "StringObject", "value": ["abc"] }, - { "type": "StringObject", "value": ["ABC"] }, - { "type": "StringObject", "value": [""] }, - { "type": "StringObject", "value": ["\\\"\\n\\0\\r\\\\'"] }, - ], - "tests": [ - ["QueryCount", 2, "StringObject", "stringCol == 'a'"], - ["QueryCount", 1, "StringObject", "'c' == stringCol"], - ["QueryCount", 2, "StringObject", "stringCol == \"a\""], - ["QueryCount", 1, "StringObject", "stringCol=='abc'"], - ["QueryCount", 1, "StringObject", "stringCol == ''"], - ["QueryCount", 8, "StringObject", "stringCol != ''"], - ["QueryCount", 1, "StringObject", "stringCol == \"\\\"\\n\\0\\r\\\\'\""], - ["QueryCount", 3, "StringObject", "stringCol BEGINSWITH 'a'"], - ["QueryCount", 1, "StringObject", "stringCol beginswith 'ab'"], - ["QueryCount", 0, "StringObject", "stringCol BEGINSWITH 'abcd'"], - ["QueryCount", 2, "StringObject", "stringCol BEGINSWITH 'A'"], - ["QueryCount", 2, "StringObject", "stringCol ENDSWITH 'c'"], - ["QueryCount", 1, "StringObject", "stringCol endswith 'bc'"], - ["QueryCount", 9, "StringObject", "stringCol ENDSWITH ''"], - ["QueryCount", 1, "StringObject", "stringCol CONTAINS 'b'"], - ["QueryCount", 2, "StringObject", "stringCol contains 'c'"], - ["QueryCount", 9, "StringObject", "stringCol CONTAINS ''"], - ["QueryCount", 2, "StringObject", "stringCol == $0", "a"], - ["QueryCount", 2, "StringObject", "stringCol ENDSWITH $0", "c"], - - ["QueryThrows", "StringObject", "stringCol == true"], - ["QueryThrows", "StringObject", "stringCol == 123"], - ["QueryThrows", "StringObject", "stringCol CONTAINS $0", 1], - - // ["QueryCount", 3, "StringObject", "stringCol ==[c] 'a'"], - // ["QueryCount", 5, "StringObject", "stringCol BEGINSWITH[c] 'A'"], - // ["QueryCount", 4, "StringObject", "stringCol ENDSWITH[c] 'c'"], - // ["QueryCount", 2, "StringObject", "stringCol CONTAINS[c] 'B'"], - ] -}, - -}; module.exports = BaseTest.extend({ testDateQueries: function() { diff --git a/tests/RJSModuleLoader.h b/tests/RJSModuleLoader.h index 680e75d2..c438f641 100644 --- a/tests/RJSModuleLoader.h +++ b/tests/RJSModuleLoader.h @@ -12,5 +12,6 @@ - (void)addGlobalModuleObject:(id)object forName:(NSString *)name; - (JSValue *)loadModuleFromURL:(NSURL *)url error:(NSError **)error; +- (JSValue *)loadJSONFromURL:(NSURL *)url error:(NSError **)error; @end diff --git a/tests/RJSModuleLoader.m b/tests/RJSModuleLoader.m index d81fb706..a188ff5c 100644 --- a/tests/RJSModuleLoader.m +++ b/tests/RJSModuleLoader.m @@ -42,14 +42,18 @@ static NSString * const RJSModuleLoaderErrorDomain = @"RJSModuleLoaderErrorDomai NSURL *url = [[NSURL URLWithString:name relativeToURL:baseURL] absoluteURL]; BOOL isDirectory; - if ([[NSFileManager defaultManager] fileExistsAtPath:url.path isDirectory:&isDirectory]) { - if (!isDirectory) { - return nil; - } - + if ([[NSFileManager defaultManager] fileExistsAtPath:url.path isDirectory:&isDirectory] && isDirectory) { url = [url URLByAppendingPathComponent:@"index.js"]; } else { - url = [url URLByAppendingPathExtension:@"js"]; + if ([[url pathExtension] isEqualToString:@"json"]) { + return [self loadJSONFromURL:url error:error]; + } + else if ([[url pathExtension] length] == 0) { + url = [url URLByAppendingPathExtension:@"js"]; + } + else { + return nil; + } } return [self loadModuleFromURL:url error:error]; @@ -136,6 +140,34 @@ static NSString * const RJSModuleLoaderErrorDomain = @"RJSModuleLoaderErrorDomai return exports; } +- (JSValue *)loadJSONFromURL:(NSURL *)url error:(NSError **)error { + url = url.absoluteURL; + url = url.standardizedURL ?: url; + + NSString *path = url.path; + JSValue *exports = self.modules[path]; + if (exports) { + return exports; + } + + NSString *source = [NSString stringWithContentsOfURL:url usedEncoding:NULL error:error]; + if (!source) { + return nil; + } + + JSContext *context = self.context; + JSValueRef json = JSValueMakeFromJSONString(context.JSGlobalContextRef, JSStringCreateWithUTF8CString(source.UTF8String)); + if (!json) { + *error = [NSError errorWithDomain:RJSModuleLoaderErrorDomain code:1 userInfo:@{ + NSLocalizedDescriptionKey: @"Invalid JSON" + }]; + return nil; + } + + self.modules[path] = [JSValue valueWithJSValueRef:json inContext:context]; + return self.modules[path]; +} + - (JSValue *)loadGlobalModule:(NSString *)name relativeToURL:(NSURL *)baseURL error:(NSError **)error { JSValue *exports = self.globalModules[name]; if (exports || !baseURL) { diff --git a/tests/queryTests.json b/tests/queryTests.json new file mode 100644 index 00000000..e1272cc3 --- /dev/null +++ b/tests/queryTests.json @@ -0,0 +1,207 @@ +{ + +"dateTests" : { + "schema" : [{ + "name": "DateObject", + "properties": [{ "name": "date", "type": "date" }] + }], + "objects": [ + { "type": "DateObject", "value": [10000] }, + { "type": "DateObject", "value": [10001] }, + { "type": "DateObject", "value": [10002] } + ], + "tests": [ + ["QueryCount", 2, "DateObject", "date < $0", [2, 0]], + ["QueryCount", 3, "DateObject", "date <= $0", [2, 0]], + ["QueryCount", 2, "DateObject", "date > $0", [0, 0]], + ["QueryCount", 3, "DateObject", "date >= $0", [0, 0]], + ["QueryCount", 1, "DateObject", "date == $0", [0, 0]], + ["QueryCount", 2, "DateObject", "date != $0", [0, 0]], + + ["QueryThrows", "DateObject", "date == 'not a date'"], + ["QueryThrows", "DateObject", "date == 1"], + ["QueryThrows", "DateObject", "date == $0", 1] + ] +}, + +"boolTests" : { + "schema" : [{ + "name": "BoolObject", + "properties": [{ "name": "boolCol", "type": "bool" }] + }], + "objects": [ + { "type": "BoolObject", "value": [false] }, + { "type": "BoolObject", "value": [true] }, + { "type": "BoolObject", "value": [true] } + ], + "tests": [ + ["QueryCount", 2, "BoolObject", "boolCol == true"], + ["QueryCount", 1, "BoolObject", "boolCol==false"], + ["QueryCount", 1, "BoolObject", "boolCol != true"], + ["QueryCount", 2, "BoolObject", "true == boolCol"], + ["QueryCount", 2, "BoolObject", "boolCol == TRUE"], + ["QueryCount", 1, "BoolObject", "boolCol == FALSE"], + ["QueryCount", 2, "BoolObject", "boolCol == $0", true], + ["QueryCount", 1, "BoolObject", "boolCol == $0", false], + ["QueryCount", 0, "BoolObject", "boolCol == true && boolCol == false"], + ["QueryCount", 3, "BoolObject", "boolCol == true || boolCol == false"], + + ["QueryThrows", "BoolObject", "boolCol == 0"], + ["QueryThrows", "BoolObject", "boolCol == 1"], + ["QueryThrows", "BoolObject", "boolCol == 'not a bool'"], + ["QueryThrows", "BoolObject", "boolCol == $0", "not a bool"], + ["QueryThrows", "BoolObject", "boolCol > true"], + ["QueryThrows", "BoolObject", "boolCol >= true"], + ["QueryThrows", "BoolObject", "boolCol < true"], + ["QueryThrows", "BoolObject", "boolCol <= true"], + ["QueryThrows", "BoolObject", "boolCol BEGINSWITH true"], + ["QueryThrows", "BoolObject", "boolCol CONTAINS true"], + ["QueryThrows", "BoolObject", "boolCol ENDSWITH true"] + ] +}, + +"intTests" : { + "schema" : [{ + "name": "IntObject", + "properties": [{ "name": "intCol", "type": "int" }] + }], + "objects": [ + { "type": "IntObject", "value": [-1] }, + { "type": "IntObject", "value": [0] }, + { "type": "IntObject", "value": [100] } + ], + "tests": [ + ["QueryCount", 1, "IntObject", "intCol == -1"], + ["QueryCount", 1, "IntObject", "intCol==0"], + ["QueryCount", 0, "IntObject", "1 == intCol"], + ["QueryCount", 2, "IntObject", "intCol != 0"], + ["QueryCount", 2, "IntObject", "intCol > -1"], + ["QueryCount", 3, "IntObject", "intCol >= -1"], + ["QueryCount", 2, "IntObject", "intCol < 100"], + ["QueryCount", 3, "IntObject", "intCol <= 100"], + ["QueryCount", 1, "IntObject", "intCol > 0x1F"], + ["QueryCount", 1, "IntObject", "intCol == $0", 100], + + ["QueryThrows", "IntObject", "intCol == 'not an int'"], + ["QueryThrows", "IntObject", "intCol == true"], + ["QueryThrows", "IntObject", "intCol == $0", "not an int"], + ["QueryThrows", "IntObject", "intCol BEGINSWITH 1"], + ["QueryThrows", "IntObject", "intCol CONTAINS 1"], + ["QueryThrows", "IntObject", "intCol ENDSWITH 1"] + ] +}, + +"floatTests" : { + "schema" : [{ + "name": "FloatObject", + "properties": [{ "name": "floatCol", "type": "float" }] + }], + "objects": [ + { "type": "FloatObject", "value": [-1.001] }, + { "type": "FloatObject", "value": [0.0] }, + { "type": "FloatObject", "value": [100.2] } + ], + "tests": [ + ["QueryCount", 1, "FloatObject", "floatCol == -1.001"], + ["QueryCount", 1, "FloatObject", "floatCol = 0"], + ["QueryCount", 0, "FloatObject", "1 == floatCol"], + ["QueryCount", 2, "FloatObject", "floatCol != 0"], + ["QueryCount", 2, "FloatObject", "floatCol > -1.001"], + ["QueryCount", 3, "FloatObject", "floatCol >= -1.001"], + ["QueryCount", 2, "FloatObject", "floatCol < 100.2"], + ["QueryCount", 3, "FloatObject", "floatCol <= 100.2"], + ["QueryCount", 1, "FloatObject", "floatCol > 0x1F"], + ["QueryCount", 1, "FloatObject", "floatCol == $0", 100.2], + + ["QueryThrows", "FloatObject", "floatCol == 'not a float'"], + ["QueryThrows", "FloatObject", "floatCol == true"], + ["QueryThrows", "FloatObject", "floatCol == $0", "not a float"], + ["QueryThrows", "FloatObject", "floatCol BEGINSWITH 1"], + ["QueryThrows", "FloatObject", "floatCol CONTAINS 1"], + ["QueryThrows", "FloatObject", "floatCol ENDSWITH 1"], + + ["Disabled", "QueryThrows", "FloatObject", "floatCol = 3.5e+38"], + ["Disabled", "QueryThrows", "FloatObject", "floatCol = -3.5e+38"] + ] +}, + +"doubleTests" : { + "schema" : [{ + "name": "DoubleObject", + "properties": [{ "name": "doubleCol", "type": "double" }] + }], + "objects": [ + { "type": "DoubleObject", "value": [-1.001] }, + { "type": "DoubleObject", "value": [0.0] }, + { "type": "DoubleObject", "value": [100.2] } + ], + "tests": [ + ["QueryCount", 1, "DoubleObject", "doubleCol == -1.001"], + ["QueryCount", 1, "DoubleObject", "doubleCol == 0"], + ["QueryCount", 0, "DoubleObject", "1 == doubleCol"], + ["QueryCount", 2, "DoubleObject", "doubleCol != 0"], + ["QueryCount", 2, "DoubleObject", "doubleCol > -1.001"], + ["QueryCount", 3, "DoubleObject", "doubleCol >= -1.001"], + ["QueryCount", 2, "DoubleObject", "doubleCol < 100.2"], + ["QueryCount", 3, "DoubleObject", "doubleCol <= 100.2"], + ["QueryCount", 1, "DoubleObject", "doubleCol > 0x1F"], + ["QueryCount", 1, "DoubleObject", "doubleCol == $0", 100.2], + + ["QueryThrows", "DoubleObject", "doubleCol == 'not a double'"], + ["QueryThrows", "DoubleObject", "doubleCol == true"], + ["QueryThrows", "DoubleObject", "doubleCol == $0", "not a double"], + ["QueryThrows", "DoubleObject", "doubleCol BEGINSWITH 1"], + ["QueryThrows", "DoubleObject", "doubleCol CONTAINS 1"], + ["QueryThrows", "DoubleObject", "doubleCol ENDSWITH 1"] + ] +}, + +"stringTests" : { + "schema" : [{ + "name": "StringObject", + "properties": [{ "name": "stringCol", "type": "string" }] + }], + "objects": [ + { "type": "StringObject", "value": ["A"] }, + { "type": "StringObject", "value": ["a"] }, + { "type": "StringObject", "value": ["a"] }, + { "type": "StringObject", "value": ["C"] }, + { "type": "StringObject", "value": ["c"] }, + { "type": "StringObject", "value": ["abc"] }, + { "type": "StringObject", "value": ["ABC"] }, + { "type": "StringObject", "value": [""] }, + { "type": "StringObject", "value": ["\\\"\\n\\0\\r\\\\'"] } + ], + "tests": [ + ["QueryCount", 2, "StringObject", "stringCol == 'a'"], + ["QueryCount", 1, "StringObject", "'c' == stringCol"], + ["QueryCount", 2, "StringObject", "stringCol == \"a\""], + ["QueryCount", 1, "StringObject", "stringCol=='abc'"], + ["QueryCount", 1, "StringObject", "stringCol == ''"], + ["QueryCount", 8, "StringObject", "stringCol != ''"], + ["QueryCount", 1, "StringObject", "stringCol == \"\\\"\\n\\0\\r\\\\'\""], + ["QueryCount", 3, "StringObject", "stringCol BEGINSWITH 'a'"], + ["QueryCount", 1, "StringObject", "stringCol beginswith 'ab'"], + ["QueryCount", 0, "StringObject", "stringCol BEGINSWITH 'abcd'"], + ["QueryCount", 2, "StringObject", "stringCol BEGINSWITH 'A'"], + ["QueryCount", 2, "StringObject", "stringCol ENDSWITH 'c'"], + ["QueryCount", 1, "StringObject", "stringCol endswith 'bc'"], + ["QueryCount", 9, "StringObject", "stringCol ENDSWITH ''"], + ["QueryCount", 1, "StringObject", "stringCol CONTAINS 'b'"], + ["QueryCount", 2, "StringObject", "stringCol contains 'c'"], + ["QueryCount", 9, "StringObject", "stringCol CONTAINS ''"], + ["QueryCount", 2, "StringObject", "stringCol == $0", "a"], + ["QueryCount", 2, "StringObject", "stringCol ENDSWITH $0", "c"], + + ["QueryThrows", "StringObject", "stringCol == true"], + ["QueryThrows", "StringObject", "stringCol == 123"], + ["QueryThrows", "StringObject", "stringCol CONTAINS $0", 1], + + ["Disabled", "QueryCount", 3, "StringObject", "stringCol ==[c] 'a'"], + ["Disabled", "QueryCount", 5, "StringObject", "stringCol BEGINSWITH[c] 'A'"], + ["Disabled", "QueryCount", 4, "StringObject", "stringCol ENDSWITH[c] 'c'"], + ["Disabled", "QueryCount", 2, "StringObject", "stringCol CONTAINS[c] 'B'"] + ] +} + +} \ No newline at end of file From 22ca94e36eb9c2d92769a8081e60b6bdce2463d1 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 19 Nov 2015 15:17:57 -0800 Subject: [PATCH 39/47] test and bug fixes for data queries --- src/object-store/parser/query_builder.cpp | 20 +++++++++--------- tests/QueryTests.js | 4 ++++ tests/queryTests.json | 25 ++++++++++++++++++++++- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp index 353588e8..59425aaa 100644 --- a/src/object-store/parser/query_builder.cpp +++ b/src/object-store/parser/query_builder.cpp @@ -102,7 +102,7 @@ void add_bool_constraint_to_query(Query &query, Predicate::Operator operatorType void add_string_constraint_to_query(Query &query, Predicate::Operator op, Columns &&column, - std::string value) { + std::string &&value) { bool case_sensitive = true; switch (op) { case Predicate::Operator::BeginsWith: @@ -127,7 +127,7 @@ void add_string_constraint_to_query(Query &query, void add_string_constraint_to_query(realm::Query &query, Predicate::Operator op, - std::string value, + std::string &&value, Columns &&column) { bool case_sensitive = true; switch (op) { @@ -145,22 +145,22 @@ void add_string_constraint_to_query(realm::Query &query, void add_binary_constraint_to_query(Query &query, Predicate::Operator op, Columns &&column, - std::string value) { + std::string &&value) { switch (op) { case Predicate::Operator::BeginsWith: - query.begins_with(column.m_column, value); + query.begins_with(column.m_column, BinaryData(value)); break; case Predicate::Operator::EndsWith: - query.ends_with(column.m_column, value); + query.ends_with(column.m_column, BinaryData(value)); break; case Predicate::Operator::Contains: - query.contains(column.m_column, value); + query.contains(column.m_column, BinaryData(value)); break; case Predicate::Operator::Equal: - query.equal(column.m_column, value); + query.equal(column.m_column, BinaryData(value)); break; case Predicate::Operator::NotEqual: - query.not_equal(column.m_column, value); + query.not_equal(column.m_column, BinaryData(value)); break; default: throw std::runtime_error("Unsupported operator for binary queries."); @@ -173,10 +173,10 @@ void add_binary_constraint_to_query(realm::Query &query, Columns &&column) { switch (op) { case Predicate::Operator::Equal: - query.equal(column.m_column, value); + query.equal(column.m_column, BinaryData(value)); break; case Predicate::Operator::NotEqual: - query.not_equal(column.m_column, value); + query.not_equal(column.m_column, BinaryData(value)); break; default: throw std::runtime_error("Substring comparison not supported for keypath substrings."); diff --git a/tests/QueryTests.js b/tests/QueryTests.js index bb6932a4..3bf35e70 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -13,6 +13,7 @@ var testCases = require('./queryTests.json'); var typeConverters = {}; typeConverters[Realm.Types.DATE] = function(value) { return new Date(value); }; +typeConverters[Realm.Types.DATA] = function(value) { return new Uint8Array(value); }; function runQuerySuite(suite) { var realm = new Realm({schema: suite.schema}); @@ -84,6 +85,9 @@ module.exports = BaseTest.extend({ testStringQueries: function() { runQuerySuite(testCases.stringTests); }, + testBinaryQueries: function() { + runQuerySuite(testCases.binaryTests); + }, }); diff --git a/tests/queryTests.json b/tests/queryTests.json index e1272cc3..30b3cd01 100644 --- a/tests/queryTests.json +++ b/tests/queryTests.json @@ -202,6 +202,29 @@ ["Disabled", "QueryCount", 4, "StringObject", "stringCol ENDSWITH[c] 'c'"], ["Disabled", "QueryCount", 2, "StringObject", "stringCol CONTAINS[c] 'B'"] ] +}, + +"binaryTests" : { + "schema" : [{ + "name": "BinaryObject", + "properties": [{ "name": "binaryCol", "type": "data" }] + }], + "objects": [ + { "type": "BinaryObject", "value": [[1, 100, 233, 255, 0]] }, + { "type": "BinaryObject", "value": [[1, 100]] }, + { "type": "BinaryObject", "value": [[100]] }, + { "type": "BinaryObject", "value": [[]] }, + { "type": "BinaryObject", "value": [[255, 0]] } + ], + "tests": [ + ["QueryCount", 1, "BinaryObject", "binaryCol == $0", [1, 0]], + ["QueryCount", 1, "BinaryObject", "$0 == binaryCol", [2, 0]], + ["QueryCount", 4, "BinaryObject", "binaryCol != $0", [0, 0]], + ["QueryCount", 1, "BinaryObject", "binaryCol BEGINSWITH $0", [0, 0]], + ["QueryCount", 2, "BinaryObject", "binaryCol BEGINSWITH $0", [1, 0]], + ["QueryCount", 2, "BinaryObject", "binaryCol ENDSWITH $0", [4, 0]], + ["QueryCount", 3, "BinaryObject", "binaryCol CONTAINS $0", [2, 0]] + ] } -} \ No newline at end of file +} From 7b993d2f0908fbfeb71e02935f22eed404b881c9 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Thu, 19 Nov 2015 15:59:16 -0800 Subject: [PATCH 40/47] turn off parser analyze --- src/object-store/parser/parser.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 8d1c3be6..7d771285 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -308,8 +308,6 @@ const std::string error_message_control< Rule >::error_message = "Invalid predic Predicate parse(const std::string &query) { - analyze< pred >(); - Predicate out_predicate(Predicate::Type::And); ParserState state; From 494fbd3a06903bde53617f5f54eeb56aa1ef2f7b Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 20 Nov 2015 14:14:35 -0800 Subject: [PATCH 41/47] object tests --- src/RJSResults.mm | 4 +- src/object-store/parser/query_builder.cpp | 145 ++++++++++++++-------- tests/QueryTests.js | 89 ++----------- tests/queryTests.json | 46 +++++-- 4 files changed, 139 insertions(+), 145 deletions(-) diff --git a/src/RJSResults.mm b/src/RJSResults.mm index eaca1cc0..b7483de9 100644 --- a/src/RJSResults.mm +++ b/src/RJSResults.mm @@ -109,8 +109,8 @@ JSObjectRef RJSResultsCreate(JSContextRef ctx, SharedRealm realm, std::string cl TableRef table = ObjectStore::table_for_object_type(realm->read_group(), className); Query query = table->where(); Schema &schema = *realm->config().schema; - auto object_schema = realm->config().schema->find(className); - if (object_schema == realm->config().schema->end()) { + auto object_schema = schema.find(className); + if (object_schema == schema.end()) { throw std::runtime_error("Object type '" + className + "' not present in Realm."); } parser::Predicate predicate = parser::parse(queryString); diff --git a/src/object-store/parser/query_builder.cpp b/src/object-store/parser/query_builder.cpp index 59425aaa..d7f26e07 100644 --- a/src/object-store/parser/query_builder.cpp +++ b/src/object-store/parser/query_builder.cpp @@ -53,6 +53,51 @@ struct FalseExpression : realm::Expression { const Table* get_table() const override { return nullptr; } }; +using KeyPath = std::vector; +KeyPath key_path_from_string(const std::string &s) { + std::stringstream ss(s); + std::string item; + KeyPath key_path; + while (std::getline(ss, item, '.')) { + key_path.push_back(item); + } + return key_path; +} + +struct PropertyExpression +{ + Property *prop = nullptr; + std::vector indexes; + std::function
table_getter; + + PropertyExpression(Query &query, Schema &schema, Schema::iterator desc, const std::string &key_path_string) + { + KeyPath key_path = key_path_from_string(key_path_string); + for (size_t index = 0; index < key_path.size(); index++) { + if (prop) { + precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, + (std::string)"Property '" + key_path[index] + "' is not a link in object of type '" + desc->name + "'"); + indexes.push_back(prop->table_column); + + } + prop = desc->property_for_name(key_path[index]); + precondition(prop != nullptr, "No property '" + key_path[index] + "' on object of type '" + desc->name + "'"); + + if (prop->object_type.size()) { + desc = schema.find(prop->object_type); + } + } + + table_getter = [&] { + TableRef& tbl = query.get_table(); + for (size_t col : indexes) { + tbl->link(col); // mutates m_link_chain on table + } + return tbl.get(); + }; + } +}; + // add a clause for numeric constraints based on operator type template @@ -183,51 +228,48 @@ void add_binary_constraint_to_query(realm::Query &query, } } - -using KeyPath = std::vector; -KeyPath key_path_from_string(const std::string &s) { - std::stringstream ss(s); - std::string item; - KeyPath key_path; - while (std::getline(ss, item, '.')) { - key_path.push_back(item); +void add_link_constraint_to_query(realm::Query &query, + Predicate::Operator op, + PropertyExpression &prop_expr, + size_t row_index) { + precondition(prop_expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); + switch (op) { + case Predicate::Operator::NotEqual: + query.Not(); + case Predicate::Operator::Equal: + query.links_to(prop_expr.prop->table_column, row_index); + break; + default: + throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison."); } - return key_path; } -struct PropertyExpression -{ - Property *prop = nullptr; - std::vector indexes; - std::function
table_getter; - - PropertyExpression(Query &query, Schema &schema, ObjectSchema &desc, const std::string &key_path_string) - { - KeyPath key_path = key_path_from_string(key_path_string); - for (size_t index = 0; index < key_path.size(); index++) { - if (prop) { - precondition(prop->type == PropertyTypeObject || prop->type == PropertyTypeArray, - (std::string)"Property '" + key_path[index] + "' is not a link in object of type '" + desc.name + "'"); - indexes.push_back(prop->table_column); - - } - prop = desc.property_for_name(key_path[index]); - precondition(prop != nullptr, "No property '" + key_path[index] + "' on object of type '" + desc.name + "'"); - - if (prop->object_type.size()) { - desc = *schema.find(prop->object_type); - } - } - - table_getter = [&] { - TableRef& tbl = query.get_table(); - for (size_t col : indexes) { - tbl->link(col); // mutates m_link_chain on table - } - return tbl.get(); - }; +void add_link_constraint_to_query(realm::Query &query, + Predicate::Operator op, + PropertyExpression &prop_expr, + realm::null) { + precondition(prop_expr.indexes.empty(), "KeyPath queries not supported for object comparisons."); + switch (op) { + case Predicate::Operator::NotEqual: + query.Not(); + case Predicate::Operator::Equal: + query.and_query(query.get_table()->column(prop_expr.prop->table_column).is_null()); + break; + default: + throw std::runtime_error("Only 'equal' and 'not equal' operators supported for object comparison."); } -}; +} + +auto link_argument(PropertyExpression &propExpr, parser::Expression &argExpr, Arguments &args) +{ + return args.object_index_for_argument(std::stoi(argExpr.s)); +} + +auto link_argument(parser::Expression &argExpr, PropertyExpression &propExpr, Arguments &args) +{ + return args.object_index_for_argument(std::stoi(argExpr.s)); +} + template struct ColumnGetter { @@ -366,30 +408,35 @@ void do_add_comparison_to_query(Query &query, Schema &schema, ObjectSchema &obje add_binary_constraint_to_query(query, op, value_of_type_for_query(expr.table_getter, lhs, args), value_of_type_for_query(expr.table_getter, rhs, args)); break; + case PropertyTypeObject: + case PropertyTypeArray: + add_link_constraint_to_query(query, op, expr, link_argument(lhs, rhs, args)); + break; default: { throw std::runtime_error((std::string)"Object type " + string_for_property_type(type) + " not supported"); } } } -void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, Schema &schema, ObjectSchema &object_schema) +void add_comparison_to_query(Query &query, Predicate &pred, Arguments &args, Schema &schema, const std::string &type) { Predicate::Comparison &cmpr = pred.cmpr; auto t0 = cmpr.expr[0].type, t1 = cmpr.expr[1].type; + auto object_schema = schema.find(type); if (t0 == parser::Expression::Type::KeyPath && t1 != parser::Expression::Type::KeyPath) { PropertyExpression expr(query, schema, object_schema, cmpr.expr[0].s); - do_add_comparison_to_query(query, schema, object_schema, cmpr.op, expr, expr, cmpr.expr[1], args); + do_add_comparison_to_query(query, schema, *object_schema, cmpr.op, expr, expr, cmpr.expr[1], args); } else if (t0 != parser::Expression::Type::KeyPath && t1 == parser::Expression::Type::KeyPath) { PropertyExpression expr(query, schema, object_schema, cmpr.expr[1].s); - do_add_comparison_to_query(query, schema, object_schema, cmpr.op, expr, cmpr.expr[0], expr, args); + do_add_comparison_to_query(query, schema, *object_schema, cmpr.op, expr, cmpr.expr[0], expr, args); } else { throw std::runtime_error("Predicate expressions must compare a keypath and another keypath or a constant value"); } } -void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, Schema &schema, ObjectSchema &object_schema) +void update_query_with_predicate(Query &query, Predicate &pred, Arguments &arguments, Schema &schema, const std::string &type) { if (pred.negate) { query.Not(); @@ -399,7 +446,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum case Predicate::Type::And: query.group(); for (auto &sub : pred.cpnd.sub_predicates) { - update_query_with_predicate(query, sub, arguments, schema, object_schema); + update_query_with_predicate(query, sub, arguments, schema, type); } if (!pred.cpnd.sub_predicates.size()) { query.and_query(new TrueExpression); @@ -411,7 +458,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum query.group(); for (auto &sub : pred.cpnd.sub_predicates) { query.Or(); - update_query_with_predicate(query, sub, arguments, schema, object_schema); + update_query_with_predicate(query, sub, arguments, schema, type); } if (!pred.cpnd.sub_predicates.size()) { query.and_query(new FalseExpression); @@ -420,7 +467,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum break; case Predicate::Type::Comparison: { - add_comparison_to_query(query, pred, arguments, schema, object_schema); + add_comparison_to_query(query, pred, arguments, schema, type); break; } case Predicate::Type::True: @@ -439,7 +486,7 @@ void update_query_with_predicate(Query &query, Predicate &pred, Arguments &argum void apply_predicate(Query &query, Predicate &predicate, Arguments &arguments, Schema &schema, std::string objectType) { - update_query_with_predicate(query, predicate, arguments, schema, *schema.find(objectType)); + update_query_with_predicate(query, predicate, arguments, schema, objectType); // Test the constructed query in core std::string validateMessage = query.validate(); diff --git a/tests/QueryTests.js b/tests/QueryTests.js index 3bf35e70..e49cb388 100644 --- a/tests/QueryTests.js +++ b/tests/QueryTests.js @@ -29,11 +29,11 @@ function runQuerySuite(suite) { }); return { type: obj.type, value: converted }; - }); + }); realm.write(function() { - for (var obj of objects) { - realm.create(obj.type, obj.value); + for (var i = 0; i < objects.length; i++) { + objects[i] = realm.create(objects[i].type, objects[i].value); } }); @@ -43,7 +43,8 @@ function runQuerySuite(suite) { for (var i = 4; i < test.length; i++) { var arg = test[i]; if (Array.isArray(arg)) { - args.push(objects[arg[0]].value[arg[1]]); + // aray arguments correspond to [objectAtIndex, propertyName] + args.push(objects[arg[0]][arg[1]]); } else { args.push(arg); @@ -88,6 +89,9 @@ module.exports = BaseTest.extend({ testBinaryQueries: function() { runQuerySuite(testCases.binaryTests); }, + testObjectQueries: function() { + runQuerySuite(testCases.objectTests); + } }); @@ -540,34 +544,6 @@ module.exports = BaseTest.extend({ @"Property type mismatch between double and string"); } -- (void)testBinaryComparisonInPredicate -{ - NSExpression *binary = [NSExpression expressionForConstantValue:[[NSData alloc] init]]; - - NSUInteger (^count)(NSPredicateOperatorType) = ^(NSPredicateOperatorType type) { - NSPredicate *pred = [RLMPredicateUtil comparisonWithKeyPath: @"binaryCol" - expression: binary - operatorType: type]; - return [BinaryObject objectsWithPredicate: pred].count; - }; - - XCTAssertEqual(count(NSBeginsWithPredicateOperatorType), 0U, - @"BEGINSWITH operator in binary comparison."); - XCTAssertEqual(count(NSEndsWithPredicateOperatorType), 0U, - @"ENDSWITH operator in binary comparison."); - XCTAssertEqual(count(NSContainsPredicateOperatorType), 0U, - @"CONTAINS operator in binary comparison."); - XCTAssertEqual(count(NSEqualToPredicateOperatorType), 0U, - @"= or == operator in binary comparison."); - XCTAssertEqual(count(NSNotEqualToPredicateOperatorType), 0U, - @"!= or <> operator in binary comparison."); - - // Invalid operators. - XCTAssertThrowsSpecificNamed(count(NSLessThanPredicateOperatorType), NSException, - @"Invalid operator type", - @"Invalid operator in binary comparison."); -} - - (void)testKeyPathLocationInComparison { NSExpression *keyPath = [NSExpression expressionForKeyPath:@"intCol"]; @@ -946,55 +922,6 @@ module.exports = BaseTest.extend({ XCTAssertThrows([CircleArrayObject objectsInRealm:realm where:@"NONE data.circles = '2'"]); } -- (void)testQueryWithObjects -{ - RLMRealm *realm = [RLMRealm defaultRealm]; - - NSDate *date1 = [NSDate date]; - NSDate *date2 = [date1 dateByAddingTimeInterval:1]; - NSDate *date3 = [date2 dateByAddingTimeInterval:1]; - - [realm beginWriteTransaction]; - - StringObject *stringObj0 = [StringObject createInRealm:realm withValue:@[@"string0"]]; - StringObject *stringObj1 = [StringObject createInRealm:realm withValue:@[@"string1"]]; - StringObject *stringObj2 = [StringObject createInRealm:realm withValue:@[@"string2"]]; - - AllTypesObject *obj0 = [AllTypesObject createInRealm:realm withValue:@[@YES, @1, @1.0f, @1.0, @"a", [@"a" dataUsingEncoding:NSUTF8StringEncoding], date1, @YES, @1LL, @1, stringObj0]]; - AllTypesObject *obj1 = [AllTypesObject createInRealm:realm withValue:@[@YES, @2, @2.0f, @2.0, @"b", [@"b" dataUsingEncoding:NSUTF8StringEncoding], date2, @YES, @2LL, @"mixed", stringObj1]]; - AllTypesObject *obj2 = [AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @3LL, @"mixed", stringObj0]]; - AllTypesObject *obj3 = [AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @3LL, @"mixed", stringObj2]]; - AllTypesObject *obj4 = [AllTypesObject createInRealm:realm withValue:@[@NO, @3, @3.0f, @3.0, @"c", [@"c" dataUsingEncoding:NSUTF8StringEncoding], date3, @YES, @34359738368LL, @"mixed", NSNull.null]]; - - [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj0, obj1]]]; - [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj1]]]; - [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj0, obj2, obj3]]]; - [ArrayOfAllTypesObject createInDefaultRealmWithValue:@[@[obj4]]]; - - [realm commitWriteTransaction]; - - // simple queries - XCTAssertEqual(2U, ([AllTypesObject objectsWhere:@"objectCol = %@", stringObj0].count)); - XCTAssertEqual(1U, ([AllTypesObject objectsWhere:@"objectCol = %@", stringObj1].count)); - XCTAssertEqual(1U, ([AllTypesObject objectsWhere:@"objectCol = nil"].count)); - XCTAssertEqual(4U, ([AllTypesObject objectsWhere:@"objectCol != nil"].count)); - XCTAssertEqual(3U, ([AllTypesObject objectsWhere:@"objectCol != %@", stringObj0].count)); - - NSPredicate *longPred = [NSPredicate predicateWithFormat:@"longCol = %lli", 34359738368]; - XCTAssertEqual([AllTypesObject objectsWithPredicate:longPred].count, 1U, @"Count should be 1"); - - NSPredicate *longBetweenPred = [NSPredicate predicateWithFormat:@"longCol BETWEEN %@", @[@34359738367LL, @34359738369LL]]; - XCTAssertEqual([AllTypesObject objectsWithPredicate:longBetweenPred].count, 1U, @"Count should be 1"); - - // check for ANY object in array - XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"ANY array = %@", obj0].count)); - XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"ANY array != %@", obj1].count)); - XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"NONE array = %@", obj0].count)); - XCTAssertEqual(2U, ([ArrayOfAllTypesObject objectsWhere:@"NONE array != %@", obj1].count)); - XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"array = %@", obj0].count)); - XCTAssertThrows(([ArrayOfAllTypesObject objectsWhere:@"array != %@", obj0].count)); -} - - (void)testCompoundOrQuery { RLMRealm *realm = [RLMRealm defaultRealm]; diff --git a/tests/queryTests.json b/tests/queryTests.json index 30b3cd01..f9a5f56f 100644 --- a/tests/queryTests.json +++ b/tests/queryTests.json @@ -11,12 +11,12 @@ { "type": "DateObject", "value": [10002] } ], "tests": [ - ["QueryCount", 2, "DateObject", "date < $0", [2, 0]], - ["QueryCount", 3, "DateObject", "date <= $0", [2, 0]], - ["QueryCount", 2, "DateObject", "date > $0", [0, 0]], - ["QueryCount", 3, "DateObject", "date >= $0", [0, 0]], - ["QueryCount", 1, "DateObject", "date == $0", [0, 0]], - ["QueryCount", 2, "DateObject", "date != $0", [0, 0]], + ["QueryCount", 2, "DateObject", "date < $0", [2, "date"]], + ["QueryCount", 3, "DateObject", "date <= $0", [2, "date"]], + ["QueryCount", 2, "DateObject", "date > $0", [0, "date"]], + ["QueryCount", 3, "DateObject", "date >= $0", [0, "date"]], + ["QueryCount", 1, "DateObject", "date == $0", [0, "date"]], + ["QueryCount", 2, "DateObject", "date != $0", [0, "date"]], ["QueryThrows", "DateObject", "date == 'not a date'"], ["QueryThrows", "DateObject", "date == 1"], @@ -217,13 +217,33 @@ { "type": "BinaryObject", "value": [[255, 0]] } ], "tests": [ - ["QueryCount", 1, "BinaryObject", "binaryCol == $0", [1, 0]], - ["QueryCount", 1, "BinaryObject", "$0 == binaryCol", [2, 0]], - ["QueryCount", 4, "BinaryObject", "binaryCol != $0", [0, 0]], - ["QueryCount", 1, "BinaryObject", "binaryCol BEGINSWITH $0", [0, 0]], - ["QueryCount", 2, "BinaryObject", "binaryCol BEGINSWITH $0", [1, 0]], - ["QueryCount", 2, "BinaryObject", "binaryCol ENDSWITH $0", [4, 0]], - ["QueryCount", 3, "BinaryObject", "binaryCol CONTAINS $0", [2, 0]] + ["QueryCount", 1, "BinaryObject", "binaryCol == $0", [1, "binaryCol"]], + ["QueryCount", 1, "BinaryObject", "$0 == binaryCol", [2, "binaryCol"]], + ["QueryCount", 4, "BinaryObject", "binaryCol != $0", [0, "binaryCol"]], + ["QueryCount", 1, "BinaryObject", "binaryCol BEGINSWITH $0", [0, "binaryCol"]], + ["QueryCount", 2, "BinaryObject", "binaryCol BEGINSWITH $0", [1, "binaryCol"]], + ["QueryCount", 2, "BinaryObject", "binaryCol ENDSWITH $0", [4, "binaryCol"]], + ["QueryCount", 3, "BinaryObject", "binaryCol CONTAINS $0", [2, "binaryCol"]] + ] +}, + +"objectTests" : { + "schema" : [ + { "name": "IntObject", "properties": [{ "name": "intCol", "type": "int" }] }, + { "name": "LinkObject", "properties": [{ "name": "linkCol", "type": "IntObject" }] } + ], + "objects": [ + { "type": "LinkObject", "value": [[1]] }, + { "type": "LinkObject", "value": [[2]] }, + { "type": "LinkObject", "value": [null] } + ], + "tests": [ + ["QueryCount", 1, "LinkObject", "linkCol == $0", [0, "linkCol"]], + ["QueryCount", 1, "LinkObject", "$0 == linkCol", [1, "linkCol"]], + ["QueryCount", 2, "LinkObject", "linkCol != $0", [0, "linkCol"]], + + ["QueryThrows", "LinkObject", "linkCol > $0", [0, "linkCol"]], + ["QueryThrows", "LinkObject", "intCol = $0", [0, "linkCol"]] ] } From 40f0e5f274be9691f990da248d7aca3cfa976cb0 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 20 Nov 2015 14:51:04 -0800 Subject: [PATCH 42/47] move queryTests to parser dir --- RealmJS.xcodeproj/project.pbxproj | 21 +++++++++++++++---- .../object-store/parser}/queryTests.json | 0 .../ios/ReactTests.xcodeproj/project.pbxproj | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) rename {tests => src/object-store/parser}/queryTests.json (100%) diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index a7558118..8ceb5917 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -55,7 +55,6 @@ 029445981BDEDF46006D1617 /* external_commit_helper.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 029445961BDEDF46006D1617 /* external_commit_helper.hpp */; }; 0294465E1BF27DE6006D1617 /* query_builder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0294465C1BF27DE6006D1617 /* query_builder.cpp */; }; 0294465F1BF27DE6006D1617 /* query_builder.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0294465D1BF27DE6006D1617 /* query_builder.hpp */; }; - 02AAAEF01BFE72F2002DD081 /* queryTests.json in Resources */ = {isa = PBXBuildFile; fileRef = 02AAAEEF1BFE72F2002DD081 /* queryTests.json */; }; 02B29A311B7CF86D008A7E6B /* RealmJS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; }; 02B58CCE1AE99D4D009B348C /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; 02C0864E1BCDB27000942F9C /* list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02C0864C1BCDB27000942F9C /* list.cpp */; }; @@ -64,6 +63,7 @@ 02D456DA1B7E59A500EE1299 /* ArrayTests.js in Resources */ = {isa = PBXBuildFile; fileRef = 02D456D91B7E59A500EE1299 /* ArrayTests.js */; }; 02D8D1F71B601984006DB49D /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; 02E9A9F11BFA84F100939F86 /* QueryTests.js in Resources */ = {isa = PBXBuildFile; fileRef = 02E9A9F01BFA84F100939F86 /* QueryTests.js */; }; + 02EF76761BFFD41F000D5BAD /* queryTests.json in Resources */ = {isa = PBXBuildFile; fileRef = 02EF76751BFFD41F000D5BAD /* queryTests.json */; }; F636F6C81BCDB3570023F35C /* RealmReact.h in Headers */ = {isa = PBXBuildFile; fileRef = 0270BCCF1B7D067300010E03 /* RealmReact.h */; settings = {ATTRIBUTES = (Public, ); }; }; F64426C51BCDB1E200A81210 /* RealmJS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F64E1EF11BC3510E00E0E150 /* util.js in Resources */ = {isa = PBXBuildFile; fileRef = F64E1EF01BC3510E00E0E150 /* util.js */; }; @@ -138,6 +138,13 @@ remoteGlobalIDString = 00E356EE1AD99517003FC87E; remoteInfo = RealmReactTests; }; + 02EF76771BFFD506000D5BAD /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = CEE28CEE1AE0051F00F4023C; + remoteInfo = "GCDWebServers (iOS)"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -207,7 +214,6 @@ 0294465D1BF27DE6006D1617 /* query_builder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = query_builder.hpp; path = "src/object-store/parser/query_builder.hpp"; sourceTree = ""; }; 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = GCDWebServer.xcodeproj; path = GCDWebServer/GCDWebServer.xcodeproj; sourceTree = ""; }; 02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; - 02AAAEEF1BFE72F2002DD081 /* queryTests.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = queryTests.json; path = tests/queryTests.json; sourceTree = SOURCE_ROOT; }; 02B29A161B7CF7C9008A7E6B /* RealmReact.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmReact.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CB11AE99CEC009B348C /* RealmJS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RealmJS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmJSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -218,6 +224,7 @@ 02D456D91B7E59A500EE1299 /* ArrayTests.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = ArrayTests.js; path = tests/ArrayTests.js; sourceTree = SOURCE_ROOT; }; 02E9A9F01BFA84F100939F86 /* QueryTests.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = QueryTests.js; path = tests/QueryTests.js; sourceTree = SOURCE_ROOT; }; 02EE6D781BD87E310016A82E /* ReactTests.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactTests.xcodeproj; path = tests/ReactTests/ios/ReactTests.xcodeproj; sourceTree = ""; }; + 02EF76751BFFD41F000D5BAD /* queryTests.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = queryTests.json; path = "src/object-store/parser/queryTests.json"; sourceTree = SOURCE_ROOT; }; F64E1EF01BC3510E00E0E150 /* util.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = util.js; path = tests/util.js; sourceTree = SOURCE_ROOT; }; F68A278A1BC2722A0063D40A /* RJSModuleLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RJSModuleLoader.h; path = tests/RJSModuleLoader.h; sourceTree = SOURCE_ROOT; }; F68A278B1BC2722A0063D40A /* RJSModuleLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RJSModuleLoader.m; path = tests/RJSModuleLoader.m; sourceTree = SOURCE_ROOT; }; @@ -363,7 +370,7 @@ 02D456D91B7E59A500EE1299 /* ArrayTests.js */, 0270BC791B7D020100010E03 /* ObjectTests.js */, 02E9A9F01BFA84F100939F86 /* QueryTests.js */, - 02AAAEEF1BFE72F2002DD081 /* queryTests.json */, + 02EF76751BFFD41F000D5BAD /* queryTests.json */, 0270BC7C1B7D020100010E03 /* RealmTests.js */, 0270BC7D1B7D020100010E03 /* ResultsTests.js */, 0270BC7A1B7D020100010E03 /* RealmJSTests.h */, @@ -461,6 +468,7 @@ buildRules = ( ); dependencies = ( + 02EF76781BFFD506000D5BAD /* PBXTargetDependency */, 02B29A301B7CF7ED008A7E6B /* PBXTargetDependency */, ); name = RealmReact; @@ -626,7 +634,7 @@ 0270BC861B7D020100010E03 /* schemas.js in Resources */, F6F405F81BCF0C1A00A1E24F /* base-test.js in Resources */, F68A278E1BC30F0A0063D40A /* index.js in Resources */, - 02AAAEF01BFE72F2002DD081 /* queryTests.json in Resources */, + 02EF76761BFFD41F000D5BAD /* queryTests.json in Resources */, 0270BC831B7D020100010E03 /* RealmTests.js in Resources */, 0270BC841B7D020100010E03 /* ResultsTests.js in Resources */, ); @@ -710,6 +718,11 @@ target = 02B58CB01AE99CEC009B348C /* RealmJS */; targetProxy = 02B58CBE1AE99CEC009B348C /* PBXContainerItemProxy */; }; + 02EF76781BFFD506000D5BAD /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "GCDWebServers (iOS)"; + targetProxy = 02EF76771BFFD506000D5BAD /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ diff --git a/tests/queryTests.json b/src/object-store/parser/queryTests.json similarity index 100% rename from tests/queryTests.json rename to src/object-store/parser/queryTests.json diff --git a/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj b/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj index 992a4dc1..ab8280bf 100644 --- a/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj +++ b/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj @@ -614,7 +614,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "rm -rf ./node_modules/realm ./node_modules/realm-tests\nnpm install realm realm-tests"; + shellScript = "rm -rf ../node_modules/realm ../node_modules/realm-tests\nnpm install realm realm-tests\ncp ../../../src/object-store/parser/queryTests.json ../node_modules/realm-tests\n"; }; /* End PBXShellScriptBuildPhase section */ From df7474feb356bd777e9ed4a419e52a1eb8a4ecc6 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 20 Nov 2015 15:16:35 -0800 Subject: [PATCH 43/47] run parser tests in RealmJSTests --- RealmJS.xcodeproj/project.pbxproj | 8 ++++++++ src/object-store/parser/parser.cpp | 5 +++++ src/object-store/parser/parser.hpp | 3 +++ src/object-store/parser/test.cpp | 14 ++++++++++++-- tests/GrammerTests.mm | 19 +++++++++++++++++++ tests/RealmJSTests.mm | 2 ++ 6 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 tests/GrammerTests.mm diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index 8ceb5917..2bb76a16 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -64,6 +64,8 @@ 02D8D1F71B601984006DB49D /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; 02E9A9F11BFA84F100939F86 /* QueryTests.js in Resources */ = {isa = PBXBuildFile; fileRef = 02E9A9F01BFA84F100939F86 /* QueryTests.js */; }; 02EF76761BFFD41F000D5BAD /* queryTests.json in Resources */ = {isa = PBXBuildFile; fileRef = 02EF76751BFFD41F000D5BAD /* queryTests.json */; }; + 02EF76861BFFDE37000D5BAD /* test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02EF76851BFFDE37000D5BAD /* test.cpp */; }; + 02EF76881BFFDE9E000D5BAD /* GrammerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 02EF76871BFFDE9E000D5BAD /* GrammerTests.mm */; }; F636F6C81BCDB3570023F35C /* RealmReact.h in Headers */ = {isa = PBXBuildFile; fileRef = 0270BCCF1B7D067300010E03 /* RealmReact.h */; settings = {ATTRIBUTES = (Public, ); }; }; F64426C51BCDB1E200A81210 /* RealmJS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F64E1EF11BC3510E00E0E150 /* util.js in Resources */ = {isa = PBXBuildFile; fileRef = F64E1EF01BC3510E00E0E150 /* util.js */; }; @@ -225,6 +227,8 @@ 02E9A9F01BFA84F100939F86 /* QueryTests.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = QueryTests.js; path = tests/QueryTests.js; sourceTree = SOURCE_ROOT; }; 02EE6D781BD87E310016A82E /* ReactTests.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactTests.xcodeproj; path = tests/ReactTests/ios/ReactTests.xcodeproj; sourceTree = ""; }; 02EF76751BFFD41F000D5BAD /* queryTests.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = queryTests.json; path = "src/object-store/parser/queryTests.json"; sourceTree = SOURCE_ROOT; }; + 02EF76851BFFDE37000D5BAD /* test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = test.cpp; path = "src/object-store/parser/test.cpp"; sourceTree = SOURCE_ROOT; }; + 02EF76871BFFDE9E000D5BAD /* GrammerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = GrammerTests.mm; path = tests/GrammerTests.mm; sourceTree = SOURCE_ROOT; }; F64E1EF01BC3510E00E0E150 /* util.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = util.js; path = tests/util.js; sourceTree = SOURCE_ROOT; }; F68A278A1BC2722A0063D40A /* RJSModuleLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RJSModuleLoader.h; path = tests/RJSModuleLoader.h; sourceTree = SOURCE_ROOT; }; F68A278B1BC2722A0063D40A /* RJSModuleLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RJSModuleLoader.m; path = tests/RJSModuleLoader.m; sourceTree = SOURCE_ROOT; }; @@ -378,6 +382,8 @@ 02409DC11BCF11D6005F3B3E /* RealmJSCoreTests.m */, F68A278A1BC2722A0063D40A /* RJSModuleLoader.h */, F68A278B1BC2722A0063D40A /* RJSModuleLoader.m */, + 02EF76871BFFDE9E000D5BAD /* GrammerTests.mm */, + 02EF76851BFFDE37000D5BAD /* test.cpp */, ); path = RealmJSTests; sourceTree = ""; @@ -700,6 +706,8 @@ buildActionMask = 2147483647; files = ( F68A278C1BC2722A0063D40A /* RJSModuleLoader.m in Sources */, + 02EF76881BFFDE9E000D5BAD /* GrammerTests.mm in Sources */, + 02EF76861BFFDE37000D5BAD /* test.cpp in Sources */, 0270BC821B7D020100010E03 /* RealmJSTests.mm in Sources */, 02409DC21BCF11D6005F3B3E /* RealmJSCoreTests.m in Sources */, ); diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index 7d771285..d2f88d8c 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -320,6 +320,11 @@ Predicate parse(const std::string &query) return std::move(out_predicate); } +void analyzeGrammer() +{ + analyze(); +} + }} diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp index 4792285a..6df4b105 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -76,6 +76,9 @@ namespace realm { }; Predicate parse(const std::string &query); + + void analyzeGrammer(); + bool testGrammer(); } } diff --git a/src/object-store/parser/test.cpp b/src/object-store/parser/test.cpp index f18a36ff..973cf672 100644 --- a/src/object-store/parser/test.cpp +++ b/src/object-store/parser/test.cpp @@ -125,20 +125,25 @@ static std::vector invalid_queries = { "a==a &| a==a", "a==a && OR a==a", "a==aORa==a", - "a=1ANDNOT b=2", + //"a=1ANDNOT b=2", "truepredicate &&", "truepredicate & truepredicate", }; -int main( int argc, char ** argv ) +namespace realm { +namespace parser { + +bool testGrammer() { + bool success = true; for (auto &query : valid_queries) { std::cout << "valid query: " << query << std::endl; try { realm::parser::parse(query); } catch (std::exception &ex) { std::cout << "FAILURE - " << ex.what() << std::endl; + success = false; } } @@ -151,6 +156,11 @@ int main( int argc, char ** argv ) continue; } std::cout << "FAILURE - query should throw an exception" << std::endl; + success = false; } + + return success; } +} +} diff --git a/tests/GrammerTests.mm b/tests/GrammerTests.mm new file mode 100644 index 00000000..c646b34d --- /dev/null +++ b/tests/GrammerTests.mm @@ -0,0 +1,19 @@ +/* Copyright 2015 Realm Inc - All Rights Reserved + * Proprietary and Confidential + */ + +#import +#import +#import "parser.hpp" + +@interface GrammerTests : XCTestCase +@end + +@implementation GrammerTests + +- (void)testGrammer { + realm::parser::analyzeGrammer(); + XCTAssertTrue(realm::parser::testGrammer()); +} + +@end diff --git a/tests/RealmJSTests.mm b/tests/RealmJSTests.mm index 7fea54d3..061ea3e0 100644 --- a/tests/RealmJSTests.mm +++ b/tests/RealmJSTests.mm @@ -62,3 +62,5 @@ } @end + + From 49955a03e3558bc5b21816365a9fe04cdca4f0a5 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 20 Nov 2015 19:32:35 -0800 Subject: [PATCH 44/47] use cpp extension for pure cpp files --- RealmJS.xcodeproj/project.pbxproj | 50 ++++++++++----------------- src/{RJSObject.mm => RJSObject.cpp} | 0 src/{RJSResults.mm => RJSResults.cpp} | 0 src/{RJSSchema.mm => RJSSchema.cpp} | 0 src/{RJSUtil.mm => RJSUtil.cpp} | 0 5 files changed, 19 insertions(+), 31 deletions(-) rename src/{RJSObject.mm => RJSObject.cpp} (100%) rename src/{RJSResults.mm => RJSResults.cpp} (100%) rename src/{RJSSchema.mm => RJSSchema.cpp} (100%) rename src/{RJSUtil.mm => RJSUtil.cpp} (100%) diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index 2bb76a16..a601aa06 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -19,15 +19,15 @@ 0270BC4E1B7CFC0D00010E03 /* RJSList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC401B7CFC0D00010E03 /* RJSList.cpp */; }; 0270BC4F1B7CFC0D00010E03 /* RJSList.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0270BC411B7CFC0D00010E03 /* RJSList.hpp */; settings = {ATTRIBUTES = (Private, ); }; }; 0270BC501B7CFC0D00010E03 /* RJSObject.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0270BC421B7CFC0D00010E03 /* RJSObject.hpp */; settings = {ATTRIBUTES = (Private, ); }; }; - 0270BC511B7CFC0D00010E03 /* RJSObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC431B7CFC0D00010E03 /* RJSObject.mm */; }; + 0270BC511B7CFC0D00010E03 /* RJSObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC431B7CFC0D00010E03 /* RJSObject.cpp */; }; 0270BC521B7CFC0D00010E03 /* RJSRealm.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0270BC441B7CFC0D00010E03 /* RJSRealm.hpp */; settings = {ATTRIBUTES = (Private, ); }; }; 0270BC531B7CFC0D00010E03 /* RJSRealm.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC451B7CFC0D00010E03 /* RJSRealm.mm */; }; 0270BC541B7CFC0D00010E03 /* RJSResults.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0270BC461B7CFC0D00010E03 /* RJSResults.hpp */; settings = {ATTRIBUTES = (Private, ); }; }; - 0270BC551B7CFC0D00010E03 /* RJSResults.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC471B7CFC0D00010E03 /* RJSResults.mm */; }; + 0270BC551B7CFC0D00010E03 /* RJSResults.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC471B7CFC0D00010E03 /* RJSResults.cpp */; }; 0270BC561B7CFC0D00010E03 /* RJSSchema.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0270BC481B7CFC0D00010E03 /* RJSSchema.hpp */; settings = {ATTRIBUTES = (Private, ); }; }; - 0270BC571B7CFC0D00010E03 /* RJSSchema.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC491B7CFC0D00010E03 /* RJSSchema.mm */; }; + 0270BC571B7CFC0D00010E03 /* RJSSchema.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC491B7CFC0D00010E03 /* RJSSchema.cpp */; }; 0270BC581B7CFC0D00010E03 /* RJSUtil.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0270BC4A1B7CFC0D00010E03 /* RJSUtil.hpp */; settings = {ATTRIBUTES = (Private, ); }; }; - 0270BC591B7CFC0D00010E03 /* RJSUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC4B1B7CFC0D00010E03 /* RJSUtil.mm */; }; + 0270BC591B7CFC0D00010E03 /* RJSUtil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC4B1B7CFC0D00010E03 /* RJSUtil.cpp */; }; 0270BC681B7CFC1C00010E03 /* object_accessor.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0270BC5D1B7CFC1C00010E03 /* object_accessor.hpp */; }; 0270BC691B7CFC1C00010E03 /* object_schema.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0270BC5E1B7CFC1C00010E03 /* object_schema.cpp */; }; 0270BC6A1B7CFC1C00010E03 /* object_schema.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 0270BC5F1B7CFC1C00010E03 /* object_schema.hpp */; }; @@ -176,15 +176,15 @@ 0270BC401B7CFC0D00010E03 /* RJSList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RJSList.cpp; path = src/RJSList.cpp; sourceTree = ""; }; 0270BC411B7CFC0D00010E03 /* RJSList.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = RJSList.hpp; path = src/RJSList.hpp; sourceTree = ""; }; 0270BC421B7CFC0D00010E03 /* RJSObject.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = RJSObject.hpp; path = src/RJSObject.hpp; sourceTree = ""; }; - 0270BC431B7CFC0D00010E03 /* RJSObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RJSObject.mm; path = src/RJSObject.mm; sourceTree = ""; }; + 0270BC431B7CFC0D00010E03 /* RJSObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RJSObject.cpp; path = src/RJSObject.cpp; sourceTree = ""; }; 0270BC441B7CFC0D00010E03 /* RJSRealm.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = RJSRealm.hpp; path = src/RJSRealm.hpp; sourceTree = ""; }; 0270BC451B7CFC0D00010E03 /* RJSRealm.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RJSRealm.mm; path = src/RJSRealm.mm; sourceTree = ""; }; 0270BC461B7CFC0D00010E03 /* RJSResults.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = RJSResults.hpp; path = src/RJSResults.hpp; sourceTree = ""; }; - 0270BC471B7CFC0D00010E03 /* RJSResults.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RJSResults.mm; path = src/RJSResults.mm; sourceTree = ""; }; + 0270BC471B7CFC0D00010E03 /* RJSResults.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RJSResults.cpp; path = src/RJSResults.cpp; sourceTree = ""; }; 0270BC481B7CFC0D00010E03 /* RJSSchema.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = RJSSchema.hpp; path = src/RJSSchema.hpp; sourceTree = ""; }; - 0270BC491B7CFC0D00010E03 /* RJSSchema.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RJSSchema.mm; path = src/RJSSchema.mm; sourceTree = ""; }; + 0270BC491B7CFC0D00010E03 /* RJSSchema.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RJSSchema.cpp; path = src/RJSSchema.cpp; sourceTree = ""; }; 0270BC4A1B7CFC0D00010E03 /* RJSUtil.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = RJSUtil.hpp; path = src/RJSUtil.hpp; sourceTree = ""; }; - 0270BC4B1B7CFC0D00010E03 /* RJSUtil.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = RJSUtil.mm; path = src/RJSUtil.mm; sourceTree = ""; }; + 0270BC4B1B7CFC0D00010E03 /* RJSUtil.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = RJSUtil.cpp; path = src/RJSUtil.cpp; sourceTree = ""; }; 0270BC5A1B7CFC1300010E03 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = src/Info.plist; sourceTree = ""; }; 0270BC5D1B7CFC1C00010E03 /* object_accessor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = object_accessor.hpp; path = "src/object-store/object_accessor.hpp"; sourceTree = ""; }; 0270BC5E1B7CFC1C00010E03 /* object_schema.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = object_schema.cpp; path = "src/object-store/object_schema.cpp"; sourceTree = ""; }; @@ -316,15 +316,15 @@ 0270BC401B7CFC0D00010E03 /* RJSList.cpp */, 0270BC411B7CFC0D00010E03 /* RJSList.hpp */, 0270BC421B7CFC0D00010E03 /* RJSObject.hpp */, - 0270BC431B7CFC0D00010E03 /* RJSObject.mm */, + 0270BC431B7CFC0D00010E03 /* RJSObject.cpp */, 0270BC441B7CFC0D00010E03 /* RJSRealm.hpp */, 0270BC451B7CFC0D00010E03 /* RJSRealm.mm */, 0270BC461B7CFC0D00010E03 /* RJSResults.hpp */, - 0270BC471B7CFC0D00010E03 /* RJSResults.mm */, + 0270BC471B7CFC0D00010E03 /* RJSResults.cpp */, 0270BC481B7CFC0D00010E03 /* RJSSchema.hpp */, - 0270BC491B7CFC0D00010E03 /* RJSSchema.mm */, + 0270BC491B7CFC0D00010E03 /* RJSSchema.cpp */, 0270BC4A1B7CFC0D00010E03 /* RJSUtil.hpp */, - 0270BC4B1B7CFC0D00010E03 /* RJSUtil.mm */, + 0270BC4B1B7CFC0D00010E03 /* RJSUtil.cpp */, 0270BC5A1B7CFC1300010E03 /* Info.plist */, ); name = RealmJS; @@ -678,18 +678,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 0270BC571B7CFC0D00010E03 /* RJSSchema.mm in Sources */, + 0270BC571B7CFC0D00010E03 /* RJSSchema.cpp in Sources */, 0270BC691B7CFC1C00010E03 /* object_schema.cpp in Sources */, 02601F0D1BA0F3A7007C91FF /* schema.cpp in Sources */, 0270BC6E1B7CFC1C00010E03 /* results.cpp in Sources */, 02786E331BF1065100937061 /* parser.cpp in Sources */, - 0270BC511B7CFC0D00010E03 /* RJSObject.mm in Sources */, + 0270BC511B7CFC0D00010E03 /* RJSObject.cpp in Sources */, 0270BC4D1B7CFC0D00010E03 /* RealmJS.mm in Sources */, 02601F081BA0F0CD007C91FF /* index_set.cpp in Sources */, 029445931BDEDF40006D1617 /* transact_log_handler.cpp in Sources */, 02258FB41BC6E2D00075F13A /* RealmRPC.cpp in Sources */, - 0270BC591B7CFC0D00010E03 /* RJSUtil.mm in Sources */, - 0270BC551B7CFC0D00010E03 /* RJSResults.mm in Sources */, + 0270BC591B7CFC0D00010E03 /* RJSUtil.cpp in Sources */, + 0270BC551B7CFC0D00010E03 /* RJSResults.cpp in Sources */, 02C0864E1BCDB27000942F9C /* list.cpp in Sources */, 0270BC6B1B7CFC1C00010E03 /* object_store.cpp in Sources */, 0294465E1BF27DE6006D1617 /* query_builder.cpp in Sources */, @@ -1025,11 +1025,7 @@ 02B58CCB1AE99CEC009B348C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - build/iOS, - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -1051,11 +1047,7 @@ 02B58CCC1AE99CEC009B348C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - build/iOS, - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; HEADER_SEARCH_PATHS = ""; INFOPLIST_FILE = tests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1162,11 +1154,7 @@ 02F9EE1E1B6BF66300C807E8 /* GCov_Build */ = { isa = XCBuildConfiguration; buildSettings = { - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - build/iOS, - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", diff --git a/src/RJSObject.mm b/src/RJSObject.cpp similarity index 100% rename from src/RJSObject.mm rename to src/RJSObject.cpp diff --git a/src/RJSResults.mm b/src/RJSResults.cpp similarity index 100% rename from src/RJSResults.mm rename to src/RJSResults.cpp diff --git a/src/RJSSchema.mm b/src/RJSSchema.cpp similarity index 100% rename from src/RJSSchema.mm rename to src/RJSSchema.cpp diff --git a/src/RJSUtil.mm b/src/RJSUtil.cpp similarity index 100% rename from src/RJSUtil.mm rename to src/RJSUtil.cpp From ae5e4d82b8ada7f098e050e6da40c47193b80d76 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Fri, 20 Nov 2015 19:50:58 -0800 Subject: [PATCH 45/47] fixes for remaining warnings --- RealmJS.xcodeproj/project.pbxproj | 6 +++--- .../ios/ReactTests.xcodeproj/project.pbxproj | 10 ++-------- vendor/GCDWebServer | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index a601aa06..ffbdbd66 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -952,7 +952,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++14"; - DEFINES_MODULE = YES; + DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -992,7 +992,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++14"; - DEFINES_MODULE = YES; + DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1116,7 +1116,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++14"; - DEFINES_MODULE = YES; + DEFINES_MODULE = NO; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; diff --git a/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj b/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj index ab8280bf..45ae97af 100644 --- a/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj +++ b/tests/ReactTests/ios/ReactTests.xcodeproj/project.pbxproj @@ -669,10 +669,7 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -691,10 +688,7 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = ReactTestsTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; diff --git a/vendor/GCDWebServer b/vendor/GCDWebServer index 3c33e9f0..c9894112 160000 --- a/vendor/GCDWebServer +++ b/vendor/GCDWebServer @@ -1 +1 @@ -Subproject commit 3c33e9f056e41adc97422c42d75d9388693e255e +Subproject commit c98941121a4b96a4fa4ad785790a4a3e119227a5 From 9ea8ab25c56f1d1d8675aa27ae4a953f283edb55 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 23 Nov 2015 08:47:09 -0800 Subject: [PATCH 46/47] pr feedback --- .gitmodules | 3 -- RealmJS.xcodeproj/project.pbxproj | 38 +++++++++++++++++++--- src/RJSRealm.mm | 2 +- src/object-store/parser/parser.cpp | 4 +-- src/object-store/parser/parser.hpp | 14 +++++--- tests/{GrammerTests.mm => GrammarTests.mm} | 4 +-- tests/RealmJSTests.mm | 2 -- 7 files changed, 48 insertions(+), 19 deletions(-) rename tests/{GrammerTests.mm => GrammarTests.mm} (75%) diff --git a/.gitmodules b/.gitmodules index f92241b5..483e2694 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "vendor/GCDWebServer"] path = vendor/GCDWebServer url = https://github.com/swisspol/GCDWebServer.git -[submodule "PEGTL"] - path = PEGTL - url = https://github.com/ColinH/PEGTL.git [submodule "vendor/PEGTL"] path = vendor/PEGTL url = https://github.com/ColinH/PEGTL.git diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index ffbdbd66..9b60174e 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -65,7 +65,7 @@ 02E9A9F11BFA84F100939F86 /* QueryTests.js in Resources */ = {isa = PBXBuildFile; fileRef = 02E9A9F01BFA84F100939F86 /* QueryTests.js */; }; 02EF76761BFFD41F000D5BAD /* queryTests.json in Resources */ = {isa = PBXBuildFile; fileRef = 02EF76751BFFD41F000D5BAD /* queryTests.json */; }; 02EF76861BFFDE37000D5BAD /* test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02EF76851BFFDE37000D5BAD /* test.cpp */; }; - 02EF76881BFFDE9E000D5BAD /* GrammerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 02EF76871BFFDE9E000D5BAD /* GrammerTests.mm */; }; + 02EF76881BFFDE9E000D5BAD /* GrammarTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 02EF76871BFFDE9E000D5BAD /* GrammarTests.mm */; }; F636F6C81BCDB3570023F35C /* RealmReact.h in Headers */ = {isa = PBXBuildFile; fileRef = 0270BCCF1B7D067300010E03 /* RealmReact.h */; settings = {ATTRIBUTES = (Public, ); }; }; F64426C51BCDB1E200A81210 /* RealmJS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CB11AE99CEC009B348C /* RealmJS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F64E1EF11BC3510E00E0E150 /* util.js in Resources */ = {isa = PBXBuildFile; fileRef = F64E1EF01BC3510E00E0E150 /* util.js */; }; @@ -126,6 +126,20 @@ remoteGlobalIDString = 02B58CB01AE99CEC009B348C; remoteInfo = RealmJS; }; + 02D0F2A01C03775600B4FC45 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = E2DDD1C71BE698A8002CE867; + remoteInfo = "GCDWebServer (tvOS)"; + }; + 02D0F2A21C03775600B4FC45 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = E2DDD18B1BE69404002CE867; + remoteInfo = "GCDWebServers (tvOS)"; + }; 02EE6D891BD87E310016A82E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 02EE6D781BD87E310016A82E /* ReactTests.xcodeproj */; @@ -228,7 +242,7 @@ 02EE6D781BD87E310016A82E /* ReactTests.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactTests.xcodeproj; path = tests/ReactTests/ios/ReactTests.xcodeproj; sourceTree = ""; }; 02EF76751BFFD41F000D5BAD /* queryTests.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = queryTests.json; path = "src/object-store/parser/queryTests.json"; sourceTree = SOURCE_ROOT; }; 02EF76851BFFDE37000D5BAD /* test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = test.cpp; path = "src/object-store/parser/test.cpp"; sourceTree = SOURCE_ROOT; }; - 02EF76871BFFDE9E000D5BAD /* GrammerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = GrammerTests.mm; path = tests/GrammerTests.mm; sourceTree = SOURCE_ROOT; }; + 02EF76871BFFDE9E000D5BAD /* GrammarTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = GrammarTests.mm; path = tests/GrammarTests.mm; sourceTree = SOURCE_ROOT; }; F64E1EF01BC3510E00E0E150 /* util.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = util.js; path = tests/util.js; sourceTree = SOURCE_ROOT; }; F68A278A1BC2722A0063D40A /* RJSModuleLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RJSModuleLoader.h; path = tests/RJSModuleLoader.h; sourceTree = SOURCE_ROOT; }; F68A278B1BC2722A0063D40A /* RJSModuleLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RJSModuleLoader.m; path = tests/RJSModuleLoader.m; sourceTree = SOURCE_ROOT; }; @@ -274,8 +288,10 @@ children = ( 021CEA361BFD1BA000D69390 /* GCDWebServer */, 021CEA381BFD1BA000D69390 /* GCDWebServer.app */, + 02D0F2A11C03775600B4FC45 /* GCDWebServer.app */, 021CEA3A1BFD1BA000D69390 /* GCDWebServers.framework */, 021CEA3C1BFD1BA000D69390 /* GCDWebServers.framework */, + 02D0F2A31C03775600B4FC45 /* GCDWebServers.framework */, 021CEA3E1BFD1BA000D69390 /* Tests.xctest */, ); name = Products; @@ -382,7 +398,7 @@ 02409DC11BCF11D6005F3B3E /* RealmJSCoreTests.m */, F68A278A1BC2722A0063D40A /* RJSModuleLoader.h */, F68A278B1BC2722A0063D40A /* RJSModuleLoader.m */, - 02EF76871BFFDE9E000D5BAD /* GrammerTests.mm */, + 02EF76871BFFDE9E000D5BAD /* GrammarTests.mm */, 02EF76851BFFDE37000D5BAD /* test.cpp */, ); path = RealmJSTests; @@ -604,6 +620,20 @@ remoteRef = 021CEA3D1BFD1BA000D69390 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 02D0F2A11C03775600B4FC45 /* GCDWebServer.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = GCDWebServer.app; + remoteRef = 02D0F2A01C03775600B4FC45 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 02D0F2A31C03775600B4FC45 /* GCDWebServers.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = GCDWebServers.framework; + remoteRef = 02D0F2A21C03775600B4FC45 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 02EE6D8A1BD87E310016A82E /* ReactTests.app */ = { isa = PBXReferenceProxy; fileType = wrapper.application; @@ -706,7 +736,7 @@ buildActionMask = 2147483647; files = ( F68A278C1BC2722A0063D40A /* RJSModuleLoader.m in Sources */, - 02EF76881BFFDE9E000D5BAD /* GrammerTests.mm in Sources */, + 02EF76881BFFDE9E000D5BAD /* GrammarTests.mm in Sources */, 02EF76861BFFDE37000D5BAD /* test.cpp in Sources */, 0270BC821B7D020100010E03 /* RealmJSTests.mm in Sources */, 02409DC21BCF11D6005F3B3E /* RealmJSCoreTests.m in Sources */, diff --git a/src/RJSRealm.mm b/src/RJSRealm.mm index 616f3caf..14abb869 100644 --- a/src/RJSRealm.mm +++ b/src/RJSRealm.mm @@ -269,7 +269,7 @@ JSValueRef RealmObjects(JSContextRef ctx, JSObjectRef function, JSObjectRef this else { std::string query = RJSValidatedStringForValue(ctx, arguments[1], "predicate"); std::vector args; - for (size_t i =2; i < argumentCount; i++) { + for (size_t i = 2; i < argumentCount; i++) { args.push_back(arguments[i]); } return RJSResultsCreate(ctx, sharedRealm, className, query, args); diff --git a/src/object-store/parser/parser.cpp b/src/object-store/parser/parser.cpp index d2f88d8c..98c81ac5 100644 --- a/src/object-store/parser/parser.cpp +++ b/src/object-store/parser/parser.cpp @@ -151,7 +151,7 @@ template<> struct action< and_ext > // if we were put into an OR group we need to rearrange auto ¤t = state.current(); if (current.type == Predicate::Type::Or) { - auto &sub_preds = state.current().cpnd.sub_predicates; + auto &sub_preds = current.cpnd.sub_predicates; auto &second_last = sub_preds[sub_preds.size()-2]; if (second_last.type == Predicate::Type::And) { // if we are in an OR group and second to last predicate group is @@ -320,7 +320,7 @@ Predicate parse(const std::string &query) return std::move(out_predicate); } -void analyzeGrammer() +void analyzeGrammar() { analyze(); } diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp index 6df4b105..f2119c66 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -36,7 +36,8 @@ namespace realm { struct Predicate { - enum class Type { + enum class Type + { Comparison, Or, And, @@ -44,7 +45,8 @@ namespace realm { False } type = Type::And; - enum class Operator { + enum class Operator + { None, Equal, NotEqual, @@ -57,13 +59,15 @@ namespace realm { Contains }; - struct Comparison { + struct Comparison + { Operator op = Operator::None; Expression expr[2]; ~Comparison() {} }; - struct Compound { + struct Compound + { std::vector sub_predicates; }; @@ -77,7 +81,7 @@ namespace realm { Predicate parse(const std::string &query); - void analyzeGrammer(); + void analyzeGrammar(); bool testGrammer(); } } diff --git a/tests/GrammerTests.mm b/tests/GrammarTests.mm similarity index 75% rename from tests/GrammerTests.mm rename to tests/GrammarTests.mm index c646b34d..1da92e9a 100644 --- a/tests/GrammerTests.mm +++ b/tests/GrammarTests.mm @@ -12,8 +12,8 @@ @implementation GrammerTests - (void)testGrammer { - realm::parser::analyzeGrammer(); - XCTAssertTrue(realm::parser::testGrammer()); + realm::parser::analyzeGrammar(); + XCTAssertTrue(realm::parser::testGrammar()); } @end diff --git a/tests/RealmJSTests.mm b/tests/RealmJSTests.mm index 061ea3e0..7fea54d3 100644 --- a/tests/RealmJSTests.mm +++ b/tests/RealmJSTests.mm @@ -62,5 +62,3 @@ } @end - - From e3b638edf3ace3bd99767a1f8eb085a81ca20c69 Mon Sep 17 00:00:00 2001 From: Ari Lazier Date: Mon, 23 Nov 2015 08:56:36 -0800 Subject: [PATCH 47/47] pr fixes --- RealmJS.xcodeproj/project.pbxproj | 22 ++++++++++++---------- src/object-store/parser/parser.hpp | 2 +- src/object-store/parser/test.cpp | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/RealmJS.xcodeproj/project.pbxproj b/RealmJS.xcodeproj/project.pbxproj index 9b60174e..2d6a57fa 100644 --- a/RealmJS.xcodeproj/project.pbxproj +++ b/RealmJS.xcodeproj/project.pbxproj @@ -60,6 +60,7 @@ 02C0864E1BCDB27000942F9C /* list.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02C0864C1BCDB27000942F9C /* list.cpp */; }; 02C0864F1BCDB27000942F9C /* list.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 02C0864D1BCDB27000942F9C /* list.hpp */; }; 02D0F23B1BF6C95200B4FC45 /* binding_context.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 02D0F23A1BF6C95200B4FC45 /* binding_context.hpp */; }; + 02D0F2D61C037C3400B4FC45 /* GCDWebServers.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 021CEA3C1BFD1BA000D69390 /* GCDWebServers.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 02D456DA1B7E59A500EE1299 /* ArrayTests.js in Resources */ = {isa = PBXBuildFile; fileRef = 02D456D91B7E59A500EE1299 /* ArrayTests.js */; }; 02D8D1F71B601984006DB49D /* JavaScriptCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */; }; 02E9A9F11BFA84F100939F86 /* QueryTests.js in Resources */ = {isa = PBXBuildFile; fileRef = 02E9A9F01BFA84F100939F86 /* QueryTests.js */; }; @@ -140,6 +141,13 @@ remoteGlobalIDString = E2DDD18B1BE69404002CE867; remoteInfo = "GCDWebServers (tvOS)"; }; + 02D0F2D41C037C1700B4FC45 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = CEE28CEE1AE0051F00F4023C; + remoteInfo = "GCDWebServers (iOS)"; + }; 02EE6D891BD87E310016A82E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 02EE6D781BD87E310016A82E /* ReactTests.xcodeproj */; @@ -154,13 +162,6 @@ remoteGlobalIDString = 00E356EE1AD99517003FC87E; remoteInfo = RealmReactTests; }; - 02EF76771BFFD506000D5BAD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 02A3C7841BC4317A00B1A7BE /* GCDWebServer.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = CEE28CEE1AE0051F00F4023C; - remoteInfo = "GCDWebServers (iOS)"; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -170,6 +171,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + 02D0F2D61C037C3400B4FC45 /* GCDWebServers.framework in Embed Frameworks */, F64426C51BCDB1E200A81210 /* RealmJS.framework in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -490,7 +492,7 @@ buildRules = ( ); dependencies = ( - 02EF76781BFFD506000D5BAD /* PBXTargetDependency */, + 02D0F2D51C037C1700B4FC45 /* PBXTargetDependency */, 02B29A301B7CF7ED008A7E6B /* PBXTargetDependency */, ); name = RealmReact; @@ -756,10 +758,10 @@ target = 02B58CB01AE99CEC009B348C /* RealmJS */; targetProxy = 02B58CBE1AE99CEC009B348C /* PBXContainerItemProxy */; }; - 02EF76781BFFD506000D5BAD /* PBXTargetDependency */ = { + 02D0F2D51C037C1700B4FC45 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = "GCDWebServers (iOS)"; - targetProxy = 02EF76771BFFD506000D5BAD /* PBXContainerItemProxy */; + targetProxy = 02D0F2D41C037C1700B4FC45 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ diff --git a/src/object-store/parser/parser.hpp b/src/object-store/parser/parser.hpp index f2119c66..f443c5f1 100644 --- a/src/object-store/parser/parser.hpp +++ b/src/object-store/parser/parser.hpp @@ -82,7 +82,7 @@ namespace realm { Predicate parse(const std::string &query); void analyzeGrammar(); - bool testGrammer(); + bool testGrammar(); } } diff --git a/src/object-store/parser/test.cpp b/src/object-store/parser/test.cpp index 973cf672..ce290474 100644 --- a/src/object-store/parser/test.cpp +++ b/src/object-store/parser/test.cpp @@ -134,7 +134,7 @@ static std::vector invalid_queries = { namespace realm { namespace parser { -bool testGrammer() +bool testGrammar() { bool success = true; for (auto &query : valid_queries) {