diff options
-rw-r--r-- | db/db.cpp | 10 | ||||
-rw-r--r-- | db/db.sln | 6 | ||||
-rw-r--r-- | db/db.vcproj | 24 | ||||
-rw-r--r-- | db/jsobj.cpp | 458 | ||||
-rw-r--r-- | db/makefile | 10 | ||||
-rw-r--r-- | db/matcher.cpp | 484 | ||||
-rw-r--r-- | db/query.cpp | 10 | ||||
-rw-r--r-- | stdafx.cpp | 5 | ||||
-rw-r--r-- | util/mmap.cpp | 2 | ||||
-rw-r--r-- | util/unittest.h | 40 | ||||
-rw-r--r-- | util/util.cpp | 7 |
11 files changed, 575 insertions, 481 deletions
diff --git a/db/db.cpp b/db/db.cpp index f4a0d069e6e..7c3a8e44522 100644 --- a/db/db.cpp +++ b/db/db.cpp @@ -29,6 +29,7 @@ #include "query.h" #include "introspect.h" #include "repl.h" +#include "../util/unittest.h" bool slave = false; bool master = false; // true means keep an op log @@ -740,8 +741,7 @@ void segvhandler(int x) { void mysighandler(int x) { signal(x, SIG_IGN); - cout << "got kill or ctrl c signal " << x << ", will terminate after current cmd ends" << endl; - problem() << "got kill or ctrl c signal " << x << ", will terminate after current cmd ends" << endl; + log() << "got kill or ctrl c signal " << x << ", will terminate after current cmd ends" << endl; { dblock lk; problem() << " now exiting" << endl; @@ -824,6 +824,8 @@ int main(int argc, char* argv[], char *envp[] ) #endif srand(curTimeMillis()); + UnitTest::runTests(); + if( argc >= 2 ) { if( strcmp(argv[1], "quicktest") == 0 ) { quicktest(); @@ -948,13 +950,13 @@ int main(int argc, char* argv[], char *envp[] ) #undef exit void dbexit(int rc, const char *why) { - cout << " dbexit: " << why << "; flushing op log and files" << endl; + log() << " dbexit: " << why << "; flushing op log and files" << endl; flushOpLog(); /* must do this before unmapping mem or you may get a seg fault */ closeAllSockets(); MemoryMappedFile::closeAllFiles(); - cout << " dbexit: really exiting now" << endl; + log() << " dbexit: really exiting now" << endl; exit(rc); } diff --git a/db/db.sln b/db/db.sln index 9ebc444204b..43585735798 100644 --- a/db/db.sln +++ b/db/db.sln @@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "db", "db.vcproj", "{215B2D68-0A70-4D10-8E75-B31010C62A91}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dbgrid", "..\dbgrid\dbgrid.vcproj", "{E03717ED-69B4-4D21-BC55-DF6690B585C6}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -13,6 +15,10 @@ Global {215B2D68-0A70-4D10-8E75-B31010C62A91}.Debug|Win32.Build.0 = Debug|Win32
{215B2D68-0A70-4D10-8E75-B31010C62A91}.Release|Win32.ActiveCfg = Release|Win32
{215B2D68-0A70-4D10-8E75-B31010C62A91}.Release|Win32.Build.0 = Release|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Debug|Win32.Build.0 = Debug|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Release|Win32.ActiveCfg = Release|Win32
+ {E03717ED-69B4-4D21-BC55-DF6690B585C6}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/db/db.vcproj b/db/db.vcproj index 6ab824bf3c7..4fdae169214 100644 --- a/db/db.vcproj +++ b/db/db.vcproj @@ -191,6 +191,10 @@ >
</File>
<File
+ RelativePath=".\commands.cpp"
+ >
+ </File>
+ <File
RelativePath=".\db.cpp"
>
</File>
@@ -215,6 +219,10 @@ >
</File>
<File
+ RelativePath=".\matcher.cpp"
+ >
+ </File>
+ <File
RelativePath="..\grid\message.cpp"
>
</File>
@@ -384,6 +392,10 @@ RelativePath=".\storage.h"
>
</File>
+ <File
+ RelativePath="..\util\unittest.h"
+ >
+ </File>
</Filter>
<Filter
Name="Resource Files"
@@ -403,10 +415,6 @@ Name="misc"
>
<File
- RelativePath=".\how_to_build.txt"
- >
- </File>
- <File
RelativePath=".\makefile"
>
</File>
@@ -426,14 +434,6 @@ RelativePath="..\pcre-7.4\pcrecpp.h"
>
</File>
- <File
- RelativePath=".\testdb.js"
- >
- </File>
- <File
- RelativePath=".\todo.txt"
- >
- </File>
</Filter>
<Filter
Name="libs_etc"
diff --git a/db/jsobj.cpp b/db/jsobj.cpp index 51c8df5f1a3..8687321f3e4 100644 --- a/db/jsobj.cpp +++ b/db/jsobj.cpp @@ -19,101 +19,6 @@ #include "stdafx.h" #include "jsobj.h" #include "../util/goodies.h" -#include "javajs.h" - -#if defined(_WIN32) - -#include <hash_map> -using namespace stdext; - -typedef const char * MyStr; -struct less_str { - bool operator()(const MyStr & x, const MyStr & y) const { - if ( strcmp(x, y) > 0) - return true; - - return false; - } -}; - -typedef hash_map<const char*, int, hash_compare<const char *, less_str> > strhashmap; - -#else - -#include <ext/hash_map> -using namespace __gnu_cxx; - -typedef const char * MyStr; -struct eq_str { - bool operator()(const MyStr & x, const MyStr & y) const { - if ( strcmp(x, y) == 0) - return true; - - return false; - } -}; - -typedef hash_map<const char*, int, hash<const char *>, eq_str > strhashmap; - -#endif - -#include "minilex.h" - -MiniLex minilex; - -class Where { -public: - Where() { codeCopy = 0; } - ~Where() { - JavaJS->scopeFree(scope); - delete codeCopy; - scope = 0; func = 0; codeCopy = 0; - } - jlong scope, func; - strhashmap fields; -// map<string,int> fields; - bool fullObject; - int nFields; - char *codeCopy; - - void setFunc(const char *code) { - codeCopy = new char[strlen(code)+1]; - strcpy(codeCopy,code); - func = JavaJS->functionCreate( code ); - minilex.grabVariables(codeCopy, fields); - // if user references db, eg db.foo.save(obj), - // we make sure we have the whole thing. - fullObject = fields.count("fullObject") + - fields.count("db") > 0; - nFields = fields.size(); - } - - void buildSubset(JSObj& src, JSObjBuilder& dst) { - JSElemIter it(src); - int n = 0; - if( !it.more() ) return; - while( 1 ) { - Element e = it.next(); - if( e.eoo() ) - break; - if( //n == 0 && - fields.find(e.fieldName()) != fields.end() - //fields.count(e.fieldName()) - ) { - dst.append(e); - if( ++n >= nFields ) - break; - } - } - } -}; - -JSMatcher::~JSMatcher() { - for( int i = 0; i < nBuilders; i++ ) - delete builders[i]; - delete in; - delete where; -} Element nullElement; @@ -334,304 +239,6 @@ int getGtLtOp(Element& e) { return op; } -#include "pdfile.h" - -JSMatcher::JSMatcher(JSObj &_jsobj) : - in(0), where(0), jsobj(_jsobj), nRegex(0) -{ - nBuilders = 0; - - JSElemIter i(jsobj); - n = 0; - while( i.more() ) { - Element e = i.next(); - if( e.eoo() ) - break; - - if( e.type() == Code && strcmp(e.fieldName(), "$where")==0 ) { - // $where: function()... - assert( where == 0 ); - where = new Where(); - const char *code = e.valuestr(); - massert( "$where query, but jni is disabled", JavaJS ); - where->scope = JavaJS->scopeCreate(); - JavaJS->scopeSetString(where->scope, "$client", client->name.c_str()); - where->setFunc(code); - continue; - } - - if( e.type() == RegEx ) { - if( nRegex >= 4 ) { - cout << "ERROR: too many regexes in query" << endl; - } - else { - pcrecpp::RE_Options options; - options.set_utf8(true); - const char *flags = e.regexFlags(); - while( flags && *flags ) { - if( *flags == 'i' ) - options.set_caseless(true); - else if( *flags == 'm' ) - options.set_multiline(true); - else if( *flags == 'x' ) - options.set_extended(true); - flags++; - } - regexs[nRegex].re = new pcrecpp::RE(e.regex(), options); - regexs[nRegex].fieldName = e.fieldName(); - nRegex++; - } - continue; - } - - // greater than / less than... - // e.g., e == { a : { $gt : 3 } } - // or - // { a : { $in : [1,2,3] } } - if( e.type() == Object ) { - // e.g., fe == { $gt : 3 } - JSElemIter j(e.embeddedObject()); - bool ok = false; - while( j.more() ) { - Element fe = j.next(); - if( fe.eoo() ) - break; - // Element fe = e.embeddedObject().firstElement(); - const char *fn = fe.fieldName(); - if( fn[0] == '$' && fn[1] ) { - if( fn[2] == 't' ) { - int op = Equality; - if( fn[1] == 'g' ) { - if( fn[3] == 0 ) op = GT; - else if( fn[3] == 'e' && fn[4] == 0 ) op = GTE; - } - else if( fn[1] == 'l' ) { - if( fn[3] == 0 ) op = LT; - else if( fn[3] == 'e' && fn[4] == 0 ) op = LTE; - } - if( op && nBuilders < 8) { - JSObjBuilder *b = new JSObjBuilder(); - builders[nBuilders++] = b; - b->appendAs(fe, e.fieldName()); - toMatch.push_back( b->done().firstElement() ); - compareOp.push_back(op); - n++; - ok = true; - } - } - else if( fn[1] == 'i' && fn[2] == 'n' && fn[3] == 0 && fe.type() == Array ) { - // $in - assert( in == 0 ); // only one per query supported so far. finish... - in = new set<Element,element_lt>(); - JSElemIter i(fe.embeddedObject()); - if( i.more() ) { - while( 1 ) { - Element ie = i.next(); - if( ie.eoo() ) - break; - in->insert(ie); - } - } - toMatch.push_back(e); // not actually used at the moment - compareOp.push_back(opIN); - n++; - ok = true; - } - } - else { - ok = false; - break; - } - } - if( ok ) - continue; - } - - { - // normal, simple case e.g. { a : "foo" } - toMatch.push_back(e); - compareOp.push_back(Equality); - n++; - } - } -} - -inline int JSMatcher::valuesMatch(Element& l, Element& r, int op) { - if( op == 0 ) - return l.valuesEqual(r); - - if( op == opIN ) { - // { $in : [1,2,3] } - int c = in->count(l); - return c; - } - - /* check LT, GTE, ... */ - if( l.type() != r.type() ) - return false; - int c = compareElementValues(l, r); - int z = 1 << (c+1); - return (op & z); -} - -/* Check if a particular field matches. - - fieldName - field to match "a.b" if we are reaching into an embedded object. - toMatch - element we want to match. - obj - database object to check against - compareOp - Equality, LT, GT, etc. - deep - out param. set to true/false if we scanned an array - isArr - - - Special forms: - - { "a.b" : 3 } means obj.a.b == 3 - { a : { $lt : 3 } } means obj.a < 3 - { a : { $in : [1,2] } } means [1,2].contains(obj.a) - - return value - -1 mismatch - 0 missing element - 1 match -*/ -int JSMatcher::matchesDotted(const char *fieldName, Element& toMatch, JSObj& obj, int compareOp, bool *deep, bool isArr) { - { - const char *p = strchr(fieldName, '.'); - if( p ) { - string left(fieldName, p-fieldName); - - Element e = obj.getField(left.c_str()); - if( e.eoo() ) - return 0; - if( e.type() != Object && e.type() != Array ) - return -1; - - JSObj eo = e.embeddedObject(); - return matchesDotted(p+1, toMatch, eo, compareOp, deep, e.type() == Array); - } - } - - Element e = obj.getField(fieldName); - - if( valuesMatch(e, toMatch, compareOp) ) { - return 1; - } - else if( e.type() == Array ) { - JSElemIter ai(e.embeddedObject()); - while( ai.more() ) { - Element z = ai.next(); - if( valuesMatch( z, toMatch, compareOp) ) { - if( deep ) - *deep = true; - return 1; - } - } - } - else if( isArr ) { - JSElemIter ai(obj); - while( ai.more() ) { - Element z = ai.next(); - if( z.type() == Object ) { - JSObj eo = z.embeddedObject(); - int cmp = matchesDotted(fieldName, toMatch, eo, compareOp, deep); - if( cmp > 0 ) { - if( deep ) *deep = true; - return 1; - } - } - } - } - else if( e.eoo() ) { - return 0; - } - return -1; -} - -extern int dump; - -inline bool _regexMatches(RegexMatcher& rm, Element& e) { - char buf[64]; - const char *p = buf; - if( e.type() == String || e.type() == Symbol ) - p = e.valuestr(); - else if( e.type() == Number ) { - sprintf(buf, "%f", e.number()); - } - else if( e.type() == Date ) { - unsigned long long d = e.date(); - time_t t = (d/1000); - time_t_to_String(t, buf); - } - else - return false; - return rm.re->PartialMatch(p); -} -/* todo: internal dotted notation scans -- not done yet here. */ -inline bool regexMatches(RegexMatcher& rm, Element& e, bool *deep) { - if( e.type() != Array ) - return _regexMatches(rm, e); - - JSElemIter ai(e.embeddedObject()); - while( ai.more() ) { - Element z = ai.next(); - if( _regexMatches(rm, z) ) { - if( deep ) - *deep = true; - return true; - } - } - return false; -} - -/* See if an object matches the query. - deep - return true when meanswe looked into arrays for a match -*/ -bool JSMatcher::matches(JSObj& jsobj, bool *deep) { - if( deep ) - *deep = false; - - /* assuming there is usually only one thing to match. if more this - could be slow sometimes. */ - - // check normal non-regex cases: - for( int i = 0; i < n; i++ ) { - Element& m = toMatch[i]; - int cmp = matchesDotted(toMatch[i].fieldName(), toMatch[i], jsobj, compareOp[i], deep); - - /* missing is ok iff we were looking for null */ - if( cmp < 0 ) - return false; - if( cmp == 0 && (m.type() != jstNULL && m.type() != Undefined ) ) - return false; - } - - for( int r = 0; r < nRegex; r++ ) { - RegexMatcher& rm = regexs[r]; - Element e = jsobj.getFieldDotted(rm.fieldName); - if( e.eoo() ) - return false; - if( !regexMatches(rm, e, deep) ) - return false; - } - - if( where ) { - if( where->func == 0 ) - return false; // didn't compile - if( jsobj.objsize() < 200 || where->fullObject ) { - JavaJS->scopeSetObject(where->scope, "obj", &jsobj); - } else { - JSObjBuilder b; - where->buildSubset(jsobj, b); - JSObj temp = b.done(); - JavaJS->scopeSetObject(where->scope, "obj", &temp); - } - if( JavaJS->invoke(where->scope, where->func) ) - return false; - return JavaJS->scopeGetBoolean(where->scope, "return") != 0; - } - - return true; -} /* JSObj ------------------------------------------------------------*/ @@ -829,7 +436,6 @@ int JSObj::addFields(JSObj& from, set<string>& fields) { /*-- test things ----------------------------------------------------*/ #pragma pack(push,1) - struct MaxKeyData { MaxKeyData() { totsize=7; maxkey=MaxKey; name=0; eoo=EOO; } int totsize; @@ -844,6 +450,7 @@ struct JSObj0 { int totsize; char eoo; } js0; +#pragma pack(pop) Element::Element() { data = &js0.eoo; @@ -851,60 +458,13 @@ Element::Element() { totalSize = -1; } -struct JSObj1 js1; - -struct JSObj2 { - JSObj2() { - totsize=sizeof(JSObj2); - s = String; strcpy_s(sname, 7, "abcdef"); slen = 10; - strcpy_s(sval, 10, "123456789"); eoo = EOO; - } - unsigned totsize; - char s; - char sname[7]; - unsigned slen; - char sval[10]; - char eoo; -} js2; - -struct JSUnitTest { - JSUnitTest() { - JSObj j1((const char *) &js1); - JSObj j2((const char *) &js2); - JSMatcher m(j2); - assert( m.matches(j1) ); - js2.sval[0] = 'z'; - assert( !m.matches(j1) ); - JSMatcher n(j1); - assert( n.matches(j1) ); - assert( !n.matches(j2) ); - - JSObj j0((const char *) &js0); - JSMatcher p(j0); - assert( p.matches(j1) ); - assert( p.matches(j2) ); - } -} jsunittest; - +#pragma pack(push,1) +struct EmptyObject { + EmptyObject() { len = 5; jstype = EOO; } + int len; + char jstype; +} emptyObject; #pragma pack(pop) -struct RXTest { - RXTest() { - /* - static const boost::regex e("(\\d{4}[- ]){3}\\d{4}"); - static const boost::regex b("....."); - cout << "regex result: " << regex_match("hello", e) << endl; - cout << "regex result: " << regex_match("abcoo", b) << endl; - */ - pcrecpp::RE re1(")({a}h.*o"); - pcrecpp::RE re("h.llo"); - assert( re.FullMatch("hello") ); - assert( !re1.FullMatch("hello") ); - - - pcrecpp::RE_Options options; - options.set_utf8(true); - pcrecpp::RE part("dwi", options); - assert( part.PartialMatch("dwight") ); - } -} rxtest; +JSObj emptyObj((char *) &emptyObject); + diff --git a/db/makefile b/db/makefile index b301dd3bd13..89c6a590197 100644 --- a/db/makefile +++ b/db/makefile @@ -5,11 +5,14 @@ FLAGS= ${CFLAGS} -fPIC -ggdb -pthread -O3 -I .. -Isrc/p -I/src/p/db -L/usr/loca LIB_DEPS = -lpcrecpp -lpcre LIB_BOOST = -lboost_thread -lboost_filesystem -LIBS= $(LIB_DEPS) $(LIB_BOOST) -lstdc++ +LIBS= $(LIB_DEPS) $(LIB_BOOST) -lstdc++ +DBGRID_LIBS = $(LIB_BOOST) -lstdc++ JVM_LIBS = -L/opt/java/lib/ -OBJS=../stdafx.o ../util/sock.o ../grid/message.o ../util/mmap.o pdfile.o query.o jsobj.o introspect.o btree.o clientcursor.o ../util/util.o javajs.o tests.o json.o repl.o dbclient.o btreecursor.o cloner.o namespace.o commands.o +OBJS=../stdafx.o ../util/sock.o ../grid/message.o ../util/mmap.o pdfile.o query.o jsobj.o introspect.o btree.o clientcursor.o ../util/util.o javajs.o tests.o json.o repl.o dbclient.o btreecursor.o cloner.o namespace.o commands.o matcher.o + +DBGRID_OBJS=../stdafx.o ../util/sock.o ../grid/message.o ../util/util.o jsobj.o dbclient.o ../dbgrid/dbgrid.o GPP = g++ @@ -39,6 +42,9 @@ _REQ_GPP_VER = 4.2 db: $(OBJS) db.o $(GPP) $(FLAGS) -o $@ $(OBJS) db.o $(LIBS) $(JVM_LIBS) # -./db quicktest + +dbgrid: $(DBGRID_OBJS) ../dbgrid/dbgrid.o + $(GPP) $(FLAGS) -o $@ $(DBGRID_OBJS) $(DBGRID_LIBS) javatest: javatest.o $(OBJS) $(GPP) $(FLAGS) -o $@ javatest.o $(OBJS) $(LIBS) $(JVM_LIBS) diff --git a/db/matcher.cpp b/db/matcher.cpp new file mode 100644 index 00000000000..24c5fdf8ab8 --- /dev/null +++ b/db/matcher.cpp @@ -0,0 +1,484 @@ +// matcher.cpp
+
+/* JSMatcher is our boolean expression evaluator for "where" clauses */
+
+/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" +#include "jsobj.h" +#include "../util/goodies.h" +#include "javajs.h" +#include "../util/unittest.h" + +#if defined(_WIN32) + +#include <hash_map> +using namespace stdext; + +typedef const char * MyStr; +struct less_str { + bool operator()(const MyStr & x, const MyStr & y) const { + if ( strcmp(x, y) > 0) + return true; + + return false; + } +}; + +typedef hash_map<const char*, int, hash_compare<const char *, less_str> > strhashmap; + +#else + +#include <ext/hash_map> +using namespace __gnu_cxx; + +typedef const char * MyStr; +struct eq_str { + bool operator()(const MyStr & x, const MyStr & y) const { + if ( strcmp(x, y) == 0) + return true; + + return false; + } +}; + +typedef hash_map<const char*, int, hash<const char *>, eq_str > strhashmap; + +#endif + +#include "minilex.h" + +MiniLex minilex; + +class Where { +public: + Where() { codeCopy = 0; } + ~Where() { + JavaJS->scopeFree(scope); + delete codeCopy; + scope = 0; func = 0; codeCopy = 0; + } + jlong scope, func; + strhashmap fields; +// map<string,int> fields; + bool fullObject; + int nFields; + char *codeCopy; + + void setFunc(const char *code) { + codeCopy = new char[strlen(code)+1]; + strcpy(codeCopy,code); + func = JavaJS->functionCreate( code ); + minilex.grabVariables(codeCopy, fields); + // if user references db, eg db.foo.save(obj), + // we make sure we have the whole thing. + fullObject = fields.count("fullObject") + + fields.count("db") > 0; + nFields = fields.size(); + } + + void buildSubset(JSObj& src, JSObjBuilder& dst) { + JSElemIter it(src); + int n = 0; + if( !it.more() ) return; + while( 1 ) { + Element e = it.next(); + if( e.eoo() ) + break; + if( //n == 0 && + fields.find(e.fieldName()) != fields.end() + //fields.count(e.fieldName()) + ) { + dst.append(e); + if( ++n >= nFields ) + break; + } + } + } +}; + +JSMatcher::~JSMatcher() { + for( int i = 0; i < nBuilders; i++ ) + delete builders[i]; + delete in; + delete where; +} + +#include "pdfile.h" + +JSMatcher::JSMatcher(JSObj &_jsobj) : + in(0), where(0), jsobj(_jsobj), nRegex(0) +{ + nBuilders = 0; + + JSElemIter i(jsobj); + n = 0; + while( i.more() ) { + Element e = i.next(); + if( e.eoo() ) + break; + + if( e.type() == Code && strcmp(e.fieldName(), "$where")==0 ) { + // $where: function()... + assert( where == 0 ); + where = new Where(); + const char *code = e.valuestr(); + massert( "$where query, but jni is disabled", JavaJS ); + where->scope = JavaJS->scopeCreate(); + JavaJS->scopeSetString(where->scope, "$client", client->name.c_str()); + where->setFunc(code); + continue; + } + + if( e.type() == RegEx ) { + if( nRegex >= 4 ) { + cout << "ERROR: too many regexes in query" << endl; + } + else { + pcrecpp::RE_Options options; + options.set_utf8(true); + const char *flags = e.regexFlags(); + while( flags && *flags ) { + if( *flags == 'i' ) + options.set_caseless(true); + else if( *flags == 'm' ) + options.set_multiline(true); + else if( *flags == 'x' ) + options.set_extended(true); + flags++; + } + regexs[nRegex].re = new pcrecpp::RE(e.regex(), options); + regexs[nRegex].fieldName = e.fieldName(); + nRegex++; + } + continue; + } + + // greater than / less than... + // e.g., e == { a : { $gt : 3 } } + // or + // { a : { $in : [1,2,3] } } + if( e.type() == Object ) { + // e.g., fe == { $gt : 3 } + JSElemIter j(e.embeddedObject()); + bool ok = false; + while( j.more() ) { + Element fe = j.next(); + if( fe.eoo() ) + break; + // Element fe = e.embeddedObject().firstElement(); + const char *fn = fe.fieldName(); + if( fn[0] == '$' && fn[1] ) { + if( fn[2] == 't' ) { + int op = Equality; + if( fn[1] == 'g' ) { + if( fn[3] == 0 ) op = GT; + else if( fn[3] == 'e' && fn[4] == 0 ) op = GTE; + } + else if( fn[1] == 'l' ) { + if( fn[3] == 0 ) op = LT; + else if( fn[3] == 'e' && fn[4] == 0 ) op = LTE; + } + if( op && nBuilders < 8) { + JSObjBuilder *b = new JSObjBuilder(); + builders[nBuilders++] = b; + b->appendAs(fe, e.fieldName()); + toMatch.push_back( b->done().firstElement() ); + compareOp.push_back(op); + n++; + ok = true; + } + } + else if( fn[1] == 'i' && fn[2] == 'n' && fn[3] == 0 && fe.type() == Array ) { + // $in + assert( in == 0 ); // only one per query supported so far. finish... + in = new set<Element,element_lt>(); + JSElemIter i(fe.embeddedObject()); + if( i.more() ) { + while( 1 ) { + Element ie = i.next(); + if( ie.eoo() ) + break; + in->insert(ie); + } + } + toMatch.push_back(e); // not actually used at the moment + compareOp.push_back(opIN); + n++; + ok = true; + } + } + else { + ok = false; + break; + } + } + if( ok ) + continue; + } + + { + // normal, simple case e.g. { a : "foo" } + toMatch.push_back(e); + compareOp.push_back(Equality); + n++; + } + } +} + +inline int JSMatcher::valuesMatch(Element& l, Element& r, int op) { + if( op == 0 ) + return l.valuesEqual(r); + + if( op == opIN ) { + // { $in : [1,2,3] } + int c = in->count(l); + return c; + } + + /* check LT, GTE, ... */ + if( l.type() != r.type() ) + return false; + int c = compareElementValues(l, r); + int z = 1 << (c+1); + return (op & z); +} + +/* Check if a particular field matches. + + fieldName - field to match "a.b" if we are reaching into an embedded object. + toMatch - element we want to match. + obj - database object to check against + compareOp - Equality, LT, GT, etc. + deep - out param. set to true/false if we scanned an array + isArr - + + Special forms: + + { "a.b" : 3 } means obj.a.b == 3 + { a : { $lt : 3 } } means obj.a < 3 + { a : { $in : [1,2] } } means [1,2].contains(obj.a) + + return value + -1 mismatch + 0 missing element + 1 match +*/ +int JSMatcher::matchesDotted(const char *fieldName, Element& toMatch, JSObj& obj, int compareOp, bool *deep, bool isArr) { + { + const char *p = strchr(fieldName, '.'); + if( p ) { + string left(fieldName, p-fieldName); + + Element e = obj.getField(left.c_str()); + if( e.eoo() ) + return 0; + if( e.type() != Object && e.type() != Array ) + return -1; + + JSObj eo = e.embeddedObject(); + return matchesDotted(p+1, toMatch, eo, compareOp, deep, e.type() == Array); + } + } + + Element e = obj.getField(fieldName); + + if( valuesMatch(e, toMatch, compareOp) ) { + return 1; + } + else if( e.type() == Array ) { + JSElemIter ai(e.embeddedObject()); + while( ai.more() ) { + Element z = ai.next(); + if( valuesMatch( z, toMatch, compareOp) ) { + if( deep ) + *deep = true; + return 1; + } + } + } + else if( isArr ) { + JSElemIter ai(obj); + while( ai.more() ) { + Element z = ai.next(); + if( z.type() == Object ) { + JSObj eo = z.embeddedObject(); + int cmp = matchesDotted(fieldName, toMatch, eo, compareOp, deep); + if( cmp > 0 ) { + if( deep ) *deep = true; + return 1; + } + } + } + } + else if( e.eoo() ) { + return 0; + } + return -1; +} + +extern int dump; + +inline bool _regexMatches(RegexMatcher& rm, Element& e) { + char buf[64]; + const char *p = buf; + if( e.type() == String || e.type() == Symbol ) + p = e.valuestr(); + else if( e.type() == Number ) { + sprintf(buf, "%f", e.number()); + } + else if( e.type() == Date ) { + unsigned long long d = e.date(); + time_t t = (d/1000); + time_t_to_String(t, buf); + } + else + return false; + return rm.re->PartialMatch(p); +} +/* todo: internal dotted notation scans -- not done yet here. */ +inline bool regexMatches(RegexMatcher& rm, Element& e, bool *deep) { + if( e.type() != Array ) + return _regexMatches(rm, e); + + JSElemIter ai(e.embeddedObject()); + while( ai.more() ) { + Element z = ai.next(); + if( _regexMatches(rm, z) ) { + if( deep ) + *deep = true; + return true; + } + } + return false; +} + +/* See if an object matches the query. + deep - return true when meanswe looked into arrays for a match +*/ +bool JSMatcher::matches(JSObj& jsobj, bool *deep) { + if( deep ) + *deep = false; + + /* assuming there is usually only one thing to match. if more this + could be slow sometimes. */ + + // check normal non-regex cases: + for( int i = 0; i < n; i++ ) { + Element& m = toMatch[i]; + int cmp = matchesDotted(toMatch[i].fieldName(), toMatch[i], jsobj, compareOp[i], deep); + + /* missing is ok iff we were looking for null */ + if( cmp < 0 ) + return false; + if( cmp == 0 && (m.type() != jstNULL && m.type() != Undefined ) ) + return false; + } + + for( int r = 0; r < nRegex; r++ ) { + RegexMatcher& rm = regexs[r]; + Element e = jsobj.getFieldDotted(rm.fieldName); + if( e.eoo() ) + return false; + if( !regexMatches(rm, e, deep) ) + return false; + } + + if( where ) { + if( where->func == 0 ) + return false; // didn't compile + if( jsobj.objsize() < 200 || where->fullObject ) { + JavaJS->scopeSetObject(where->scope, "obj", &jsobj); + } else { + JSObjBuilder b; + where->buildSubset(jsobj, b); + JSObj temp = b.done(); + JavaJS->scopeSetObject(where->scope, "obj", &temp); + } + if( JavaJS->invoke(where->scope, where->func) ) + return false; + return JavaJS->scopeGetBoolean(where->scope, "return") != 0; + } + + return true; +} + +struct JSObj1 js1; + +#pragma pack(push,1) +struct JSObj2 { + JSObj2() { + totsize=sizeof(JSObj2); + s = String; strcpy_s(sname, 7, "abcdef"); slen = 10; + strcpy_s(sval, 10, "123456789"); eoo = EOO; + } + unsigned totsize; + char s; + char sname[7]; + unsigned slen; + char sval[10]; + char eoo; +} js2; + +struct JSUnitTest : public UnitTest { + void run() { + + JSObj j1((const char *) &js1); + JSObj j2((const char *) &js2); + cout << "j1:" << j1.toString() << endl; + cout << "j2:" << j2.toString() << endl; + JSMatcher m(j2); + assert( m.matches(j1) ); + js2.sval[0] = 'z'; + assert( !m.matches(j1) ); + JSMatcher n(j1); + assert( n.matches(j1) ); + cout << "j2:" << j2.toString() << endl; + assert( !n.matches(j2) ); + +cout << "temp1" << endl; + JSObj j0 = emptyObj; +// JSObj j0((const char *) &js0); + JSMatcher p(j0); + assert( p.matches(j1) ); + assert( p.matches(j2) ); + } +} jsunittest; + +#pragma pack(pop) + +struct RXTest : public UnitTest { + void run() { + /* + static const boost::regex e("(\\d{4}[- ]){3}\\d{4}"); + static const boost::regex b("....."); + cout << "regex result: " << regex_match("hello", e) << endl; + cout << "regex result: " << regex_match("abcoo", b) << endl; + */ + pcrecpp::RE re1(")({a}h.*o"); + pcrecpp::RE re("h.llo"); + assert( re.FullMatch("hello") ); + assert( !re1.FullMatch("hello") ); + + + pcrecpp::RE_Options options; + options.set_utf8(true); + pcrecpp::RE part("dwi", options); + assert( part.PartialMatch("dwight") ); + } +} rxtest; + diff --git a/db/query.cpp b/db/query.cpp index 286142e9ded..7f5a575dc3c 100644 --- a/db/query.cpp +++ b/db/query.cpp @@ -41,16 +41,6 @@ LRUishMap<JSObj,DiskLoc,5> lrutest(123); int nextCursorId = 1; extern bool useCursors; -#pragma pack(push,1) -struct EmptyObject { - EmptyObject() { len = 5; jstype = EOO; } - int len; - char jstype; -} emptyObject; -#pragma pack(pop) - -JSObj emptyObj((char *) &emptyObject); - int getGtLtOp(Element& e); void appendElementHandlingGtLt(JSObjBuilder& b, Element& e); diff --git a/stdafx.cpp b/stdafx.cpp index 56ea439d52e..b050390966f 100644 --- a/stdafx.cpp +++ b/stdafx.cpp @@ -23,18 +23,20 @@ // TODO: reference any additional headers you need in STDAFX.H // and not in this file +/* struct MyAsserts { MyAsserts() { } } myassertsstdafx; +*/ #undef assert #undef yassert #include "assert.h" -void sayDbContext(const char *p = 0); +void sayDbContext(const char *errmsg = 0); void wasserted(const char *msg, const char *file, unsigned line) { problem() << "Assertion failure " << msg << ' ' << file << ' ' << line << endl; @@ -55,3 +57,4 @@ void msgasserted(const char *msg) { cout << "Assertion: " << msg << '\n'; throw AssertionException(); } + diff --git a/util/mmap.cpp b/util/mmap.cpp index a3296cd2412..e67fe4560b0 100644 --- a/util/mmap.cpp +++ b/util/mmap.cpp @@ -36,7 +36,7 @@ void MemoryMappedFile::closeAllFiles() { ++closingAllFiles; for( set<MemoryMappedFile*>::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ) (*i)->close(); - cout << " closeAllFiles() finished" << endl; + log() << " closeAllFiles() finished" << endl; --closingAllFiles; } diff --git a/util/unittest.h b/util/unittest.h new file mode 100644 index 00000000000..db80517c463 --- /dev/null +++ b/util/unittest.h @@ -0,0 +1,40 @@ +// unittest.h + +/** +* Copyright (C) 2008 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +/* the idea here is to let all initialization of global variables (classes inheriting from UnitTest) + complete before we run the tests -- otherwise order of initilization being arbitrary may mess + us up. The app's main() function should call runTests. +*/ +struct UnitTest { + UnitTest() { registerTest(this); } + + // assert if fails + virtual void run() = 0; + + static vector<UnitTest*> tests; + + static void registerTest(UnitTest *t) { tests.push_back(t); } + + static void runTests() { + for( vector<UnitTest*>::iterator i = tests.begin(); i != tests.end(); i++ ) { + } + } +}; + diff --git a/util/util.cpp b/util/util.cpp index c78767f3a69..0879a27b50b 100644 --- a/util/util.cpp +++ b/util/util.cpp @@ -16,6 +16,9 @@ #include "stdafx.h" #include "goodies.h" +#include "unittest.h" + +vector<UnitTest*> UnitTest::tests; Nullstream nullstream; Logstream logstream; @@ -43,8 +46,8 @@ int nextPrime(int n) { return n; } -struct UtilTest { - UtilTest() { +struct UtilTest : public UnitTest { + void run() { assert( WrappingInt(0) <= WrappingInt(0) ); assert( WrappingInt(0) <= WrappingInt(1) ); assert( !(WrappingInt(1) <= WrappingInt(0)) ); |