├── .gitignore ├── LICENSE ├── README.md ├── build.sh ├── config.cmake ├── interactive_mode.sh ├── phpunit.xml ├── src ├── MongoClient.cpp ├── MongoClient.php ├── MongoCollection.cpp ├── MongoCollection.php ├── MongoCursor.cpp ├── MongoCursor.php ├── MongoDB.php ├── bson.cpp ├── bson.php ├── bson_decode.cpp ├── bson_decode.h ├── bson_test ├── bson_test.cpp ├── contrib │ ├── classes.h │ ├── encode.cpp │ └── encode.h ├── encode_draft.cpp ├── exceptions │ ├── MongoConnectionException.php │ ├── MongoCursorException.php │ ├── MongoCursorTimeoutException.php │ ├── MongoDuplicateKeyException.php │ ├── MongoException.php │ ├── MongoExecutionTimeoutException.php │ ├── MongoGridFSException.php │ ├── MongoProtocolException.php │ ├── MongoResultException.php │ └── MongoWriteConcernException.php ├── ext_mongo.cpp ├── ext_mongo.h ├── mongo_common.cpp ├── mongo_common.h └── types │ ├── MongoBinData.php │ ├── MongoCode.php │ ├── MongoDBRef.php │ ├── MongoDate.php │ ├── MongoId.php │ ├── MongoInt32.php │ ├── MongoInt64.php │ ├── MongoMaxKey.php │ ├── MongoMinKey.php │ ├── MongoRegex.php │ └── MongoTimestamp.php ├── test.sh └── test ├── AutoLoader.php ├── MongoTestCase.php ├── basicTest.php ├── bootstrap.php ├── bson_encode_decode_Test.php ├── mongoClientTest.php ├── mongoCollectionTest.php ├── mongoCursorTest.php ├── mongoDBTest.php ├── mongoDateTest.php ├── mongoclient-getserverversion.php ├── students.json └── test_collection.json /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles 3 | CMakeLists.txt 4 | cmake_install.cmake 5 | Makefile 6 | *.so 7 | 8 | # This file is concatenated from other PHP source files at build time 9 | src/ext_mongo.php 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Note**: This repository is unsupported and no longer under active development. Please see [mongodb/mongo-hhvm-driver](https://github.com/mongodb/mongo-hhvm-driver) for our official HHVM driver. 2 | 3 | ---- 4 | 5 | DISCLAIMER 6 | ---------- 7 | Please note: all tools/ scripts in this repo are released for use "AS IS" without any warranties of any kind, including, but not limited to their installation, use, or performance. We disclaim any and all warranties, either express or implied, including but not limited to any warranty of noninfringement, merchantability, and/ or fitness for a particular purpose. We do not warrant that the technology will meet your requirements, that the operation thereof will be uninterrupted or error-free, or that any errors will be corrected. 8 | Any use of these scripts and tools is at your own risk. There is no guarantee that they have been through thorough testing in a comparable environment and we are not responsible for any damage or data loss incurred with their use. 9 | You are responsible for reviewing and testing any scripts you run thoroughly before use in any non-testing environment. 10 | 11 | # MongoDB driver for HHVM 12 | 13 | This is an implementation of the 14 | [MongoDB PHP driver](https://github.com/mongodb/mongo-php-driver) for 15 | [HHVM](https://github.com/facebook/hhvm). It is not feature-complete and should 16 | be considered experimental. 17 | 18 | This project is not officially supported and GitHub issues have been disabled. 19 | 20 | ## Dependencies 21 | 22 | Compiling this extension requires the following libraries: 23 | 24 | * HHVM (>=3.1.0) must be compiled from source, since the binary distributions 25 | of HHVM do not include necessary development headers. Instructions for 26 | compiling HHVM may be found 27 | [here](https://github.com/facebook/hhvm/wiki#building-hhvm). 28 | 29 | * libmongoc (>=0.94.0) and its corresponding libbson dependency must be 30 | installed as a system library. Instructions for installing libmongoc may be 31 | found 32 | [here](https://github.com/mongodb/mongo-c-driver#fetch-sources-and-build). 33 | 34 | ## Building and installation 35 | 36 | Ensure that the `HPHP_HOME` environment variable is set to the HHVM project 37 | directory. This should be the path to the cloned HHVM git repository where you 38 | compiled the project. 39 | 40 | ```bash 41 | $ export HPHP_HOME=/path/to/hhvm 42 | ``` 43 | 44 | Execute this project's `build.sh` script: 45 | 46 | ```bash 47 | $ ./build.sh 48 | ``` 49 | 50 | This script checks for the HHVM path, executes `hphpize` to prepare the build 51 | process, and finally executes `cmake` and `make` to compile the extension. 52 | 53 | The build process will produce a `mongo.so` file, which can then be dynamically 54 | loaded by HHVM by adding the following to HHVM's `config.hdf` file: 55 | 56 | ``` 57 | DynamicExtensions { 58 | mongo = /path/to/mongo.so 59 | } 60 | ``` 61 | 62 | This example is taken from the 63 | [Extension API](https://github.com/facebook/hhvm/wiki/Extension-API) 64 | documentation. 65 | 66 | Note that the `mongo` key in this example is a placeholder; HHVM only cares that 67 | the path to the `mongo.so` file is correct. You may notice that in our test 68 | script, we use `0` as a key when specifying our extension via the command line. 69 | 70 | ## Tests 71 | 72 | The test suite is implemented with [PHPUnit](http://phpunit.de) and may be 73 | executed via the `test.sh` script: 74 | 75 | ``` 76 | $ ./test.sh 77 | ``` 78 | 79 | The test script depends on the `HPHP_HOME` environment variable and will attempt 80 | to locate PHPUnit via the `which` command, so ensure that the `phpunit` binary 81 | is installed in an executable path. 82 | 83 | ## Interactive Mode 84 | 85 | To try out this work in progress for yourself, you can run the extension in interactive mode on HipHop VM via the `interactive_mode.sh` script: 86 | 87 | ``` 88 | $ ./interactive_mode.sh 89 | ``` 90 | 91 | Once in interactive mode, you can execute queries and all implemented methods on your existing local databases. For example, if a `test` database exists with a `students` collection, I can access one document in that collection by running the following commands: 92 | 93 | ``` 94 | hphpd> $cli = new MongoClient(); 95 | hphpd> $db = $cli->selectDB('test'); 96 | hphpd> $coll = $db->selectCollection('students'); 97 | hphpd> $cur = $coll->find()->limit(1); 98 | hphpd> $cur->rewind(); 99 | hphpd> var_dump($cur->current()); 100 | ``` 101 | 102 | ## Credits 103 | 104 | Máximo Cuadros created the src/contrib/encode.h, src/contrib/encode.cpp and src/contrib/classes.h files. 105 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$HPHP_HOME" = "" ]; then 4 | echo "HPHP_HOME environment variable must be set!" 5 | exit 1 6 | fi 7 | 8 | printf " src/ext_mongo.php 9 | 10 | # Base classes must be concatenated/declared first 11 | tail -q -n +2 src/exceptions/MongoException.php >> src/ext_mongo.php 12 | tail -q -n +2 src/exceptions/MongoConnectionException.php >> src/ext_mongo.php 13 | tail -q -n +2 src/exceptions/MongoCursorException.php >> src/ext_mongo.php 14 | tail -q -n +2 src/exceptions/MongoDuplicateKeyException.php >> src/ext_mongo.php 15 | tail -q -n +2 src/exceptions/MongoExecutionTimeoutException.php >> src/ext_mongo.php 16 | tail -q -n +2 src/exceptions/MongoGridFSException.php >> src/ext_mongo.php 17 | tail -q -n +2 src/exceptions/MongoProtocolException.php >> src/ext_mongo.php 18 | tail -q -n +2 src/exceptions/MongoResultException.php >> src/ext_mongo.php 19 | tail -q -n +2 src/exceptions/MongoCursorTimeoutException.php >> src/ext_mongo.php 20 | tail -q -n +2 src/exceptions/MongoWriteConcernException.php >> src/ext_mongo.php 21 | 22 | # Type and base classes have no inheritance hierarchy 23 | tail -q -n +2 src/types/*.php >> src/ext_mongo.php 24 | find src/ -maxdepth 1 -name "*.php" \! -name ext_mongo.php | xargs tail -q -n +2 >> src/ext_mongo.php 25 | 26 | $HPHP_HOME/hphp/tools/hphpize/hphpize 27 | cmake . 28 | make -j5 29 | -------------------------------------------------------------------------------- /config.cmake: -------------------------------------------------------------------------------- 1 | FIND_PATH(MONGOC_INCLUDE_DIR NAMES mongoc.h 2 | PATHS /usr/include /usr/include/libmongoc-1.0 /usr/local/include /usr/local/include/libmongoc-1.0) 3 | 4 | FIND_LIBRARY(MONGOC_LIBRARY NAMES mongoc-1.0 PATHS /lib /usr/lib /usr/local/lib) 5 | 6 | IF (MONGOC_INCLUDE_DIR AND MONGOC_LIBRARY) 7 | MESSAGE(STATUS "mongoc Include dir: ${MONGOC_INCLUDE_DIR}") 8 | MESSAGE(STATUS "libmongoc library: ${MONGOC_LIBRARY}") 9 | ELSE() 10 | MESSAGE(FATAL_ERROR "Cannot find libmongoc library") 11 | ENDIF() 12 | 13 | FIND_PATH(BSON_INCLUDE_DIR NAMES bson.h 14 | PATHS /usr/include /usr/include/libbson-1.0 /usr/local/include /usr/local/include/libbson-1.0) 15 | 16 | FIND_LIBRARY(BSON_LIBRARY NAMES bson-1.0 PATHS /lib /usr/lib /usr/local/lib) 17 | 18 | IF (BSON_INCLUDE_DIR AND BSON_LIBRARY) 19 | MESSAGE(STATUS "bson Include dir: ${BSON_INCLUDE_DIR}") 20 | MESSAGE(STATUS "libbson library: ${BSON_LIBRARY}") 21 | ELSE() 22 | MESSAGE(FATAL_ERROR "Cannot find libbson library") 23 | ENDIF() 24 | 25 | include_directories(${MONGOC_INCLUDE_DIR}) 26 | include_directories(${BSON_INCLUDE_DIR}) 27 | 28 | HHVM_EXTENSION(mongo src/ext_mongo.cpp src/mongo_common.cpp src/MongoClient.cpp src/MongoCursor.cpp src/MongoCollection.cpp src/bson.cpp src/bson_decode.cpp src/contrib/encode.cpp) 29 | HHVM_SYSTEMLIB(mongo src/ext_mongo.php) 30 | 31 | target_link_libraries(mongo ${MONGOC_LIBRARY}) 32 | -------------------------------------------------------------------------------- /interactive_mode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DIRNAME=`dirname $0` 4 | REALPATH=`which realpath` 5 | if [ ! -z "${REALPATH}" ]; then 6 | DIRNAME=`realpath ${DIRNAME}` 7 | fi 8 | 9 | ${HPHP_HOME}/hphp/hhvm/hhvm -a \ 10 | -vDynamicExtensions.0=${DIRNAME}/mongo.so 11 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | test 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/MongoClient.cpp: -------------------------------------------------------------------------------- 1 | #include "ext_mongo.h" 2 | 3 | #if HHVM_API_VERSION < 20140702L 4 | #define throw_not_implemented(msg) \ 5 | throw NotImplementedException(msg); 6 | #endif 7 | 8 | namespace HPHP { 9 | 10 | //////////////////////////////////////////////////////////////////////////////// 11 | // class MongoClient 12 | 13 | static void HHVM_METHOD(MongoClient, __construct, const String& uri, Array options) { 14 | MongocClient *client = MongocClient::GetPersistent(uri); 15 | 16 | if (client == nullptr) { 17 | client = new MongocClient(uri); 18 | } 19 | 20 | if (client->isInvalid()) { 21 | mongoThrow(strcat("Unable to connect: ", uri.c_str())); 22 | } 23 | 24 | MongocClient::SetPersistent(uri, client); 25 | this_->o_set(s_mongoc_client, client, s_mongoclient); 26 | } 27 | 28 | static bool HHVM_METHOD(MongoClient, close, Variant connection) { 29 | throw_not_implemented(__func__); 30 | } 31 | 32 | static bool HHVM_METHOD(MongoClient, connect) { 33 | throw_not_implemented(__func__); 34 | } 35 | 36 | static Array HHVM_METHOD(MongoClient, dropDB, Variant db) { 37 | throw_not_implemented(__func__); 38 | } 39 | 40 | static Object HHVM_METHOD(MongoClient, __get, const String& dbname) { 41 | throw_not_implemented(__func__); 42 | } 43 | 44 | static Array HHVM_STATIC_METHOD(MongoClient, getConnections) { 45 | throw_not_implemented(__func__); 46 | } 47 | 48 | static Array HHVM_METHOD(MongoClient, getHosts) { 49 | throw_not_implemented(__func__); 50 | } 51 | 52 | static Array HHVM_METHOD(MongoClient, getReadPreference) { 53 | throw_not_implemented(__func__); 54 | } 55 | 56 | static bool HHVM_METHOD(MongoClient, killCursor, const String& server_hash, Variant id) { 57 | throw_not_implemented(__func__); 58 | } 59 | 60 | static Array HHVM_METHOD(MongoClient, listDBs) { 61 | throw_not_implemented(__func__); 62 | } 63 | 64 | static Object HHVM_METHOD(MongoClient, selectCollection, const String& db, const String& collection) { 65 | throw_not_implemented(__func__); 66 | } 67 | 68 | static Object HHVM_METHOD(MongoClient, selectDB, const String& name) { 69 | throw_not_implemented(__func__); 70 | } 71 | 72 | static bool HHVM_METHOD(MongoClient, setReadPreference, const String& read_preference, Array tags) { 73 | throw_not_implemented(__func__); 74 | } 75 | 76 | static String HHVM_METHOD(MongoClient, __toString) { 77 | String s = "dummy toString"; 78 | return s; 79 | //throw_not_implemented(__func__); 80 | } 81 | 82 | /* Test method that returns the server's version string */ 83 | static String HHVM_METHOD(MongoClient, getServerVersion) { 84 | bool result; 85 | bson_t buildInfo, reply; 86 | bson_error_t error; 87 | bson_iter_t iter; 88 | String retval; 89 | 90 | auto client = get_client(this_); 91 | 92 | bson_init(&buildInfo); 93 | bson_append_int32(&buildInfo, "buildInfo", 9, 1); 94 | 95 | result = mongoc_client_command_simple(client->get(), "test", &buildInfo, nullptr, &reply, &error); 96 | 97 | bson_destroy(&buildInfo); 98 | 99 | if ( ! result) { 100 | mongoThrow(strcat("Command error: ", error.message)); 101 | } 102 | 103 | if (bson_iter_init_find(&iter, &reply, "version")) { 104 | retval = String(bson_iter_utf8(&iter, nullptr), CopyString); 105 | } 106 | 107 | bson_destroy(&reply); 108 | 109 | return retval; 110 | } 111 | 112 | //////////////////////////////////////////////////////////////////////////////// 113 | 114 | void MongoExtension::_initMongoClientClass() { 115 | HHVM_ME(MongoClient, __construct); 116 | HHVM_ME(MongoClient, close); 117 | HHVM_ME(MongoClient, connect); 118 | HHVM_ME(MongoClient, dropDB); 119 | HHVM_ME(MongoClient, __get); 120 | HHVM_STATIC_ME(MongoClient, getConnections); 121 | HHVM_ME(MongoClient, getHosts); 122 | HHVM_ME(MongoClient, getReadPreference); 123 | HHVM_ME(MongoClient, killCursor); 124 | HHVM_ME(MongoClient, listDBs); 125 | HHVM_ME(MongoClient, selectCollection); 126 | HHVM_ME(MongoClient, selectDB); 127 | HHVM_ME(MongoClient, setReadPreference); 128 | HHVM_ME(MongoClient, __toString); 129 | HHVM_ME(MongoClient, getServerVersion); 130 | } 131 | 132 | } //namespace HPHP 133 | -------------------------------------------------------------------------------- /src/MongoClient.php: -------------------------------------------------------------------------------- 1 | > 18 | public function __construct (string $server = "mongodb://localhost:27017", 19 | array $options = array('connect' => true)): void; 20 | 21 | /** 22 | * Closes this connection 23 | * 24 | * @param boolean|string $connection - connection If connection is 25 | * not given, or FALSE then connection that would be selected for 26 | * writes would be closed. If 27 | * connection is TRUE then all connections as known by the connection 28 | * manager will be closed. If connection is a string argument, then 29 | * it will only close the connection identified by this hash. 30 | * 31 | * @return bool - Returns if the connection was successfully closed. 32 | */ 33 | <<__Native>> 34 | public function close(?mixed $connection): bool; 35 | 36 | /** 37 | * Connects to a database server 38 | * 39 | * @return bool - If the connection was successful. 40 | */ 41 | <<__Native>> 42 | public function connect(): bool; 43 | 44 | /** 45 | * Drops a database [deprecated] 46 | * 47 | * @param mixed $db - db The database to drop. Can be a MongoDB 48 | * object or the name of the database. 49 | * 50 | * @return array - Returns the database response. 51 | */ 52 | public function dropDB(mixed $db): array { 53 | if(is_object($db)) { 54 | return $db->drop(); 55 | } 56 | 57 | if(is_string($db)) { 58 | return $this->selectDB($db)->drop(); 59 | } 60 | throw new MongoConnectionException(); 61 | } 62 | 63 | /** 64 | * Gets a database 65 | * 66 | * @param string $dbname - dbname The database name. 67 | * 68 | * @return MongoDB - Returns a new db object. 69 | */ 70 | public function __get(string $dbname): MongoDB { 71 | return $this->selectDB($dbname); 72 | } 73 | 74 | /** 75 | * Return info about all open connections 76 | * 77 | * @return array - An array of open connections. 78 | */ 79 | <<__Native>> 80 | public static function getConnections(): array; 81 | 82 | /** 83 | * Updates status for all associated hosts 84 | * 85 | * @return array - Returns an array of information about the hosts in 86 | * the set. 87 | */ 88 | <<__Native>> 89 | public function getHosts(): array; 90 | 91 | /** 92 | * Get the read preference for this connection 93 | * 94 | * @return array - 95 | */ 96 | public function getReadPreference(): array { 97 | return $this->read_preference; 98 | } 99 | 100 | /** 101 | * Kills a specific cursor on the server 102 | * 103 | * @param string $server_hash - server_hash The server hash that has 104 | * the cursor. This can be obtained through MongoCursor::info. 105 | * @param int|mongoint64 $id - id The ID of the cursor to kill. 106 | * 107 | * @return bool - Returns TRUE if the method attempted to kill a 108 | * cursor, and FALSE if there was something wrong with the arguments 109 | * (such as a wrong server_hash). 110 | */ 111 | <<__Native>> 112 | public function killCursor(string $server_hash, 113 | mixed $id): bool; 114 | 115 | /** 116 | * Lists all of the databases available. 117 | * 118 | * @return array - Returns an associative array containing three 119 | * fields. The first field is databases, which in turn contains an 120 | * array. Each element of the array is an associative array 121 | * corresponding to a database, giving th database's name, size, and if 122 | * it's empty. The other two fields are totalSize (in bytes) and ok, 123 | * which is 1 if this method ran successfully. 124 | */ 125 | public function listDBs(): array { 126 | return $this->selectDB('admin')->command(array("listDatabases" => 1)); 127 | } 128 | 129 | /** 130 | * Gets a database collection 131 | * 132 | * @param string $db - db The database name. 133 | * @param string $collection - collection The collection name. 134 | * 135 | * @return MongoCollection - Returns a new collection object. 136 | */ 137 | public function selectCollection(string $db, 138 | string $collection) { 139 | return $this->selectDB($db)->selectCollection($collection); 140 | } 141 | 142 | /** 143 | * Gets a database 144 | * 145 | * @param string $name - name The database name. 146 | * 147 | * @return MongoDB - Returns a new database object. 148 | */ 149 | public function selectDB(string $name): MongoDB { 150 | if (!isset($this->databases[$name])) { 151 | $this->databases[$name] = new MongoDB($this, $name); 152 | } 153 | return $this->databases[$name]; 154 | } 155 | 156 | /** 157 | * Set the read preference for this connection 158 | * 159 | * @param string $read_preference - 160 | * @param array $tags - 161 | * 162 | * @return bool - 163 | */ 164 | public function setReadPreference(string $read_preference, 165 | array $tags): bool { 166 | $this->read_preference['type'] = $read_preference; 167 | $this->read_preference['tagsets'] = $tags; 168 | return true; 169 | } 170 | 171 | /** 172 | * String representation of this connection 173 | * 174 | * @return string - Returns hostname and port for this connection. 175 | */ 176 | <<__Native>> 177 | public function __toString(): string; 178 | 179 | /** 180 | * Test method that returns the server's version string 181 | * 182 | * @return string 183 | */ 184 | <<__Native>> 185 | public function getServerVersion(): string; 186 | } -------------------------------------------------------------------------------- /src/MongoCollection.cpp: -------------------------------------------------------------------------------- 1 | #include "ext_mongo.h" 2 | #include "contrib/encode.h" 3 | 4 | namespace HPHP { 5 | 6 | static mongoc_collection_t *get_collection(Object obj) { 7 | mongoc_collection_t *collection; 8 | 9 | auto db = obj->o_realProp("db", ObjectData::RealPropUnchecked, "MongoCollection")->toObject(); 10 | auto client = db->o_realProp("client", ObjectData::RealPropUnchecked, "MongoDB")->toObject(); 11 | String db_name = db->o_realProp("db_name", ObjectData::RealPropUnchecked, "MongoDB")->toString(); 12 | String collection_name = obj->o_realProp("name", ObjectData::RealPropUnchecked, "MongoCollection")->toString(); 13 | 14 | collection = mongoc_client_get_collection (get_client(client)->get(), db_name.c_str(), collection_name.c_str()); 15 | return collection; 16 | } 17 | 18 | //////////////////////////////////////////////////////////////////////////////// 19 | // class MongoCollection 20 | 21 | /** 22 | * Inserts a document into the collection 23 | * 24 | * @param array|object $a - a An array or object. If an object is 25 | * used, it may not have protected or private properties. If the 26 | * parameter does not have an _id key or property, a new MongoId 27 | * instance will be created and assigned to it. 28 | * @param array $options - options Options for the insert. 29 | * 30 | * @return bool|array - Returns an array containing the status of the 31 | * insertion if the "w" option is set. Otherwise, returns TRUE if the 32 | * inserted array is not empty (a MongoException will be thrown if the 33 | * inserted array is empty). 34 | */ 35 | //public function insert(mixed $a, array $options = array()): mixed; 36 | static Variant HHVM_METHOD(MongoCollection, insert, Variant a, Array options) { 37 | mongoc_collection_t *collection; 38 | bson_t doc; 39 | bson_error_t error; 40 | 41 | collection = get_collection(this_); 42 | 43 | Array doc_array = a.toArray(); 44 | doc = encodeToBSON(doc_array); 45 | // bson_init(&doc); 46 | // bson_oid_init_from_string(&oid, doc_array[String("_id")].toString().c_str()); 47 | // bson_append_oid(&doc, "_id", 3, &oid); 48 | // //Supporting only "name" key 49 | // bson_append_utf8(&doc, "name", 4, doc_array[String("name")].toString().c_str(), doc_array[String("name")].toString().length()); 50 | 51 | bool ret = mongoc_collection_insert(collection, MONGOC_INSERT_NONE, &doc, NULL, &error); 52 | 53 | mongoc_collection_destroy (collection); 54 | bson_destroy(&doc); 55 | 56 | return ret; 57 | /* 58 | bool mongoc_collection_insert (mongoc_collection_t *collection, 59 | mongoc_insert_flags_t flags, 60 | const bson_t *document, 61 | const mongoc_write_concern_t *write_concern, 62 | bson_error_t *error); 63 | */ 64 | 65 | } 66 | 67 | 68 | /** 69 | * Remove records from this collection 70 | * 71 | * @param array $criteria - criteria Description of records to 72 | * remove. 73 | * @param array $options - options Options for remove. "justOne" 74 | * Remove at most one record matching this criteria. 75 | * 76 | * @return bool|array - Returns an array containing the status of the 77 | * removal if the "w" option is set. Otherwise, returns TRUE. 78 | */ 79 | //public function remove(array $criteria = array(), array $options = array()): mixed; 80 | static Variant HHVM_METHOD(MongoCollection, remove, Array criteria, Array options) { 81 | mongoc_collection_t *collection; 82 | bson_t criteria_b; 83 | bson_error_t error; 84 | 85 | collection = get_collection(this_); 86 | 87 | criteria_b = encodeToBSON(criteria); 88 | 89 | //Supporting only "name" key 90 | //bson_append_utf8(&criteria_b, "name", 4, criteria[String("name")].toString().c_str(), criteria[String("name")].toString().length()); 91 | 92 | bool ret = mongoc_collection_delete(collection, MONGOC_DELETE_NONE, &criteria_b, NULL, &error); 93 | 94 | mongoc_collection_destroy (collection); 95 | bson_destroy(&criteria_b); 96 | 97 | return ret; 98 | /* 99 | bool mongoc_collection_delete (mongoc_collection_t *collection, 100 | mongoc_delete_flags_t flags, 101 | const bson_t *selector, 102 | const mongoc_write_concern_t *write_concern, 103 | bson_error_t *error); 104 | */ 105 | } 106 | 107 | 108 | /* 109 | public function update(array $criteria, 110 | array $new_object, 111 | array $options = array()): mixed; 112 | */ 113 | static Variant HHVM_METHOD(MongoCollection, update, Array criteria, Array new_object, Array options) { 114 | mongoc_collection_t *collection; 115 | bson_t selector; //selector is the criteria (which document to update) 116 | bson_t update; //update is the new_object containing the new data 117 | bson_error_t error; 118 | 119 | collection = get_collection(this_); 120 | 121 | selector = encodeToBSON(criteria); 122 | update = encodeToBSON(new_object); 123 | 124 | //Read oid and name from criteria array 125 | // bson_init(&selector); 126 | // bson_oid_init_from_string(&oid, criteria[String("_id")].toString().c_str()); 127 | // bson_append_oid(&selector, "_id", 3, &oid); 128 | // bson_append_utf8(&selector, "name", 4, criteria[String("name")].toString().c_str(), criteria[String("name")].toString().length()); 129 | 130 | //Convert new_object to bson 131 | //Hard coded test for now 132 | // bson_init(&update); 133 | // BSON_APPEND_INT32 (&update, "abcd", 1); 134 | // BSON_APPEND_INT32 (&update, "$hi", 1); 135 | 136 | bool ret = mongoc_collection_update(collection, MONGOC_UPDATE_NONE, &selector, &update, NULL, &error); 137 | 138 | mongoc_collection_destroy (collection); 139 | 140 | bson_destroy(&update); 141 | bson_destroy(&selector); 142 | 143 | return ret; 144 | 145 | /* 146 | bool 147 | mongoc_collection_update (mongoc_collection_t *collection, 148 | mongoc_update_flags_t flags, 149 | const bson_t *selector, 150 | const bson_t *update, 151 | const mongoc_write_concern_t *write_concern, 152 | bson_error_t *error) 153 | */ 154 | } 155 | 156 | 157 | //////////////////////////////////////////////////////////////////////////////// 158 | 159 | void MongoExtension::_initMongoCollectionClass() { 160 | HHVM_ME(MongoCollection, insert); 161 | HHVM_ME(MongoCollection, remove); 162 | HHVM_ME(MongoCollection, update); 163 | } 164 | 165 | } // namespace HPHP 166 | -------------------------------------------------------------------------------- /src/MongoCollection.php: -------------------------------------------------------------------------------- 1 | > 39 | public function insert(mixed $a, 40 | array $options = array()): mixed; 41 | 42 | /** 43 | * Remove records from this collection 44 | * 45 | * @param array $criteria - criteria Description of records to 46 | * remove. 47 | * @param array $options - options Options for remove. "justOne" 48 | * Remove at most one record matching this criteria. 49 | * 50 | * @return bool|array - Returns an array containing the status of the 51 | * removal if the "w" option is set. Otherwise, returns TRUE. 52 | */ 53 | <<__Native>> 54 | public function remove(array $criteria = array(), 55 | array $options = array()): mixed; 56 | 57 | /** 58 | * Update records based on a given criteria 59 | * 60 | * @param array $criteria - criteria Description of the objects to 61 | * update. 62 | * @param array $new_object - new_object The object with which to 63 | * update the matching records. 64 | * @param array $options - options 65 | * 66 | * @return bool|array - Returns an array containing the status of the 67 | * update if the "w" option is set. Otherwise, returns TRUE. 68 | */ 69 | <<__Native>> 70 | public function update(array $criteria, 71 | array $new_object, 72 | array $options = array()): mixed; 73 | 74 | 75 | private function getFullName(): string { 76 | return $this->db . "." . $this->name; 77 | } 78 | /** 79 | * Perform an aggregation using the aggregation framework 80 | * 81 | * @param array $pipeline - 82 | * @param array $op - 83 | * @param array $... - 84 | * 85 | * @return array - The result of the aggregation as an array. The ok 86 | * will be set to 1 on success, 0 on failure. 87 | */ 88 | public function aggregate(array $pipeline): array { 89 | $cmd = [ 90 | 'aggregate' => $this->name, 91 | 'pipeline' => $pipeline 92 | ]; 93 | return $this->db->command($cmd); 94 | } 95 | 96 | /** 97 | * Inserts multiple documents into this collection 98 | * 99 | * @param array $a - a An array of arrays or objects. If any objects 100 | * are used, they may not have protected or private properties. If 101 | * the documents to insert do not have an _id key or property, a new 102 | * MongoId instance will be created and assigned to it. 103 | * @param array $options - options Options for the inserts. 104 | * 105 | * @return mixed - If the w parameter is set to acknowledge the write, 106 | * returns an associative array with the status of the inserts ("ok") 107 | * and any error that may have occurred ("err"). Otherwise, returns 108 | * TRUE if the batch insert was successfully sent, FALSE otherwise. 109 | */ 110 | public function batchInsert(array $a, 111 | array $options = array()): mixed { 112 | $results = array(); 113 | 114 | foreach ($a as $doc) { 115 | $results[] = $this->insert($doc, $options); 116 | } 117 | 118 | return $results; 119 | } 120 | 121 | 122 | public function __construct(MongoDB $db, string $name) { 123 | $this->db = $db; 124 | $this->name = $name; 125 | } 126 | 127 | /** 128 | * Counts the number of documents in this collection 129 | * 130 | * @param array $query - query Associative array or object with 131 | * fields to match. 132 | * @param int $limit - limit Specifies an upper limit to the number 133 | * returned. 134 | * @param int $skip - skip Specifies a number of results to skip 135 | * before starting the count. 136 | * 137 | * @return int - Returns the number of documents matching the query. 138 | */ 139 | public function count(array $query = array(), 140 | int $limit, 141 | int $skip): int { 142 | $cmd = [ 143 | 'count' => $this->name, 144 | 'query' => $query, 145 | 'limit' => $limit, 146 | 'skip' => $skip 147 | ]; 148 | $cmd_result = $this->db->command($cmd); 149 | if (!$cmd_result["ok"]) { 150 | throw new MongoCursorException(); 151 | } 152 | return $cmd_result["n"]; 153 | } 154 | 155 | /** 156 | * Creates a database reference 157 | * 158 | * @param mixed $document_or_id - document_or_id If an array or 159 | * object is given, its _id field will be used as the reference ID. If 160 | * a MongoId or scalar is given, it will be used as the reference ID. 161 | * 162 | * @return array - Returns a database reference array. If an array 163 | * without an _id field was provided as the document_or_id parameter, 164 | * NULL will be returned. 165 | */ 166 | public function createDBRef(string $collection, 167 | mixed $document_or_id): array { 168 | if (is_array($document_or_id)) { 169 | $id = $document_or_id['_id']; 170 | } else { 171 | $id = $document_or_id; 172 | } 173 | return MongoDBRef::create($this->name, $id, $this->db->__getDBName()); 174 | } 175 | 176 | /** 177 | * Deletes an index from this collection 178 | * 179 | * @param string|array $keys - keys Field or fields from which to 180 | * delete the index. 181 | * 182 | * @return array - Returns the database response. 183 | */ 184 | public function deleteIndex(mixed $keys): array { 185 | $index = $this->toIndexString($keys); 186 | return $this->db->command(array("deleteIndexes" => $this->getName(), "index" => $index)); 187 | } 188 | 189 | /** 190 | * Delete all indices for this collection 191 | * 192 | * @return array - Returns the database response. 193 | */ 194 | public function deleteIndexes(): array { 195 | return $this->deleteIndex("*"); 196 | } 197 | 198 | /** 199 | * Retrieve a list of distinct values for the given key across a collection. 200 | * 201 | * @param string $key - 202 | * @param array $query - 203 | * 204 | * @return array - Returns an array of distinct values, 205 | */ 206 | public function distinct(string $key, 207 | array $query): array { 208 | return $this->db->command(array("distinct" => $this->name, "key" => $key, "query" => $query)); 209 | } 210 | 211 | /** 212 | * Drops this collection 213 | * 214 | * @return array - Returns the database response. 215 | */ 216 | public function drop(): array { 217 | return $this->db->command(array("drop" => $this->name)); 218 | } 219 | 220 | /** 221 | * Creates an index on the given field(s), or does nothing if the index 222 | * already exists 223 | * 224 | * 225 | * @param string|array $key|keys - 226 | * @param array $options - options This parameter is an associative 227 | * array of the form array("optionname" => boolean, ...). 228 | * 229 | * @return bool - Returns an array containing the status of the index 230 | * creation if the "w" option is set. Otherwise, returns TRUE. 231 | */ 232 | public function ensureIndex(mixed $key, 233 | array $options = array()): bool { 234 | $indexName = $this->toIndexString($key); 235 | $client = $this->db->__getClient(); 236 | 237 | // check client server version, set newer to true if 2.6+ 238 | $version = $client->getServerVersion(); 239 | $newer = false; 240 | if (intval($version[0]) > 2) { 241 | $newer = true; 242 | } else if (intval($version[0]) == 2) { 243 | if (intval($version[2]) >= 6) { 244 | $newer = true; 245 | } 246 | } 247 | 248 | // indexOptions Object 249 | $indexOptions = array("key" => $key, 250 | "name" => $indexName, 251 | "ns" => $this->name); 252 | 253 | $option_names = ["background", "unique", "dropDups", "sparse", 254 | "expireAfterSeconds", "v", "weights", 255 | "default_language", "language_override"]; 256 | foreach ($option_names as $opt) { 257 | if(isset($options[$opt])) { 258 | $indexOptions[$opt] = $options[$opt]; 259 | } 260 | } 261 | 262 | 263 | // if server version >= 2.6, can run database command 264 | if ($newer) { 265 | $out = $this->db->command(array("createIndexes" => $this->name, 266 | "indexes" => $indexOptions)); 267 | } else { 268 | $out = $this->db->selectCollection("system.indexes")->insert($indexOptions, 0, true); 269 | } 270 | return $out; 271 | } 272 | 273 | /** 274 | * Queries this collection, returning a 275 | * for the result set 276 | * 277 | * @param array $query - query The fields for which to search. 278 | * @param array $fields - fields Fields of the results to return. 279 | * The array is in the format array('fieldname' => true, 'fieldname2' 280 | * => true). The _id field is always returned. 281 | * 282 | * @return MongoCursor - Returns a cursor for the search results. 283 | */ 284 | public function find(array $query = array(), 285 | array $fields = array()): MongoCursor { 286 | $ns = $this->getFullName(); 287 | return new MongoCursor($this->db->__getClient(), $ns, $query, $fields); 288 | } 289 | 290 | /** 291 | * Update a document and return it 292 | * 293 | * @param array $query - 294 | * @param array $update - 295 | * @param array $fields - 296 | * @param array $options - 297 | * 298 | * @return array - Returns the original document, or the modified 299 | * document when new is set. 300 | */ 301 | public function findAndModify(array $query, 302 | array $update, 303 | array $fields, 304 | array $options): array { 305 | $cmd = array( "findAndModify" => $this->name, 306 | "query" => $query, 307 | "update" => $update, 308 | "fields" => $fields); 309 | $opts = ["new", "upsert", "sort"]; 310 | foreach ($opts as $option) { 311 | if(isset($options[$option])) { 312 | $cmd[$option] = $options[$option]; 313 | } 314 | } 315 | $out = $this->db->command($cmd); 316 | return $out["value"]; 317 | } 318 | 319 | /** 320 | * Queries this collection, returning a single element 321 | * 322 | * @param array $query - query The fields for which to search. 323 | * @param array $fields - fields Fields of the results to return. 324 | * The array is in the format array('fieldname' => true, 'fieldname2' 325 | * => true). The _id field is always returned. 326 | * 327 | * @return array - Returns record matching the search or NULL. 328 | */ 329 | public function findOne(array $query = array(), 330 | array $fields = array()): array { 331 | $cursor = $this->find($query, $fields); 332 | $cursor = $cursor->limit(-1); 333 | $cursor->rewind(); // TODO: Need to remove later 334 | //var_dump($cursor->current()); 335 | return $cursor->current(); 336 | } 337 | 338 | /** 339 | * Gets a collection 340 | * 341 | * @param string $name - name The next string in the collection 342 | * name. 343 | * 344 | * @return MongoCollection - Returns the collection. 345 | */ 346 | public function __get(string $name): MongoCollection { 347 | return $this->db->selectCollection($name); 348 | } 349 | 350 | /** 351 | * Fetches the document pointed to by a database reference 352 | * 353 | * @param array $ref - ref A database reference. 354 | * 355 | * @return array - Returns the database document pointed to by the 356 | * reference. 357 | */ 358 | public function getDBRef(array $ref): array { 359 | return MongoDBRef::get($this->db, $ref); 360 | } 361 | 362 | /** 363 | * Returns information about indexes on this collection 364 | * 365 | * @return array - This function returns an array in which each element 366 | * describes an index. Elements will contain the values name for the 367 | * name of the index, ns for the namespace (a combination of the 368 | * database and collection name), and key for a list of all fields in 369 | * the index and their ordering. 370 | */ 371 | public function getIndexInfo(): array { 372 | return $this->db->selectCollection("system.indexes")->findOne(array("ns" => $this->getFullName())); 373 | } 374 | 375 | /** 376 | * Returns this collections name 377 | * 378 | * @return string - Returns the name of this collection. 379 | */ 380 | public function getName(): string { 381 | return $this->name; 382 | } 383 | 384 | /** 385 | * Get the read preference for this collection 386 | * 387 | * @return array - 388 | */ 389 | public function getReadPreference(): array { 390 | return $this->read_preference; 391 | } 392 | 393 | /** 394 | * Get slaveOkay setting for this collection 395 | * 396 | * @return bool - Returns the value of slaveOkay for this instance. 397 | */ 398 | public function getSlaveOkay(): bool { 399 | return $this->slaveOkay; 400 | } 401 | 402 | /** TODO: Make sure MongoCode works? 403 | * Performs an operation similar to SQL's GROUP BY command 404 | * 405 | * @param mixed $keys - keys Fields to group by. If an array or 406 | * non-code object is passed, it will be the key used to group results. 407 | * @param array $initial - initial Initial value of the aggregation 408 | * counter object. 409 | * @param mongocode $reduce - reduce A function that takes two 410 | * arguments (the current document and the aggregation to this point) 411 | * and does the aggregation. 412 | * @param array $options - options Optional parameters to the group 413 | * command. 414 | * 415 | * @return array - Returns an array containing the result. 416 | */ 417 | public function group(mixed $keys, 418 | array $initial, 419 | MongoCode $reduce, 420 | array $options = array()): array { 421 | $group = array( '$reduce' => $reduce, 422 | 'initial' => $initial); 423 | if(get_class($keys) == "MongoCode") { 424 | $group['$keyf'] = $keys; 425 | } 426 | else { 427 | $group['key'] = $keys; 428 | } 429 | 430 | $cmd = array('group' => $group); 431 | $ret = $this->db->runCommand($cmd); 432 | if (!$ret["ok"]) { 433 | throw MongoResultException("Group command failed"); 434 | } 435 | 436 | return $ret; 437 | } 438 | 439 | 440 | 441 | /** 442 | * Saves a document to this collection 443 | * 444 | * @param array|object $a - a Array or object to save. If an object 445 | * is used, it may not have protected or private properties. If the 446 | * parameter does not have an _id key or property, a new MongoId 447 | * instance will be created and assigned to it. 448 | * @param array $options - options Options for the save. 449 | * 450 | * @return mixed - If w was set, returns an array containing the status 451 | * of the save. Otherwise, returns a boolean representing if the array 452 | * was not empty (an empty array will not be inserted). 453 | */ 454 | public function save(mixed $a, 455 | array $options = array()): mixed { 456 | if (is_array($a)) { 457 | if (!isset($a["_id"])) { 458 | $a["_id"] = new MongoId(); 459 | return $this->insert($a); 460 | } 461 | return $this->update(array("_id" => $a["_id"]), $a); 462 | } 463 | throw new Exception("Saving objects not implemented"); 464 | } 465 | 466 | /** 467 | * Set the read preference for this collection 468 | * 469 | * @param string $read_preference - 470 | * @param array $tags - 471 | * 472 | * @return bool - 473 | */ 474 | public function setReadPreference(string $read_preference, 475 | array $tags): bool { 476 | $this->read_preference['type'] = $read_preference; 477 | $this->read_preference['tagsets'] = $tags; 478 | return true; 479 | } 480 | 481 | /** 482 | * Change slaveOkay setting for this collection 483 | * 484 | * @param bool $ok - ok If reads should be sent to secondary members 485 | * of a replica set for all possible queries using this MongoCollection 486 | * instance. 487 | * 488 | * @return bool - Returns the former value of slaveOkay for this 489 | * instance. 490 | */ 491 | public function setSlaveOkay(bool $ok = true): bool { 492 | $former = $this->read_preference["type"]; 493 | if ($ok) { 494 | $this->read_preference["type"] = MongoClient::RP_PRIMARY; 495 | } else { 496 | $this->read_preference["type"] = MongoClient::RP_SECONDARY_PREFERRED; 497 | } 498 | $this->read_preference["type"] = $ok; 499 | return ($former != MongoClient::RP_PRIMARY); 500 | } 501 | 502 | /**DEPRECATED 503 | * TODO: Enforce only scalar value types 504 | * Converts keys specifying an index to its identifying string 505 | * 506 | * @param mixed $keys - keys Field or fields to convert to the 507 | * identifying string 508 | * 509 | * @return string - Returns a string that describes the index. 510 | */ 511 | static protected function toIndexString(mixed $keys): string { 512 | $str = ""; 513 | 514 | if (gettype($keys) == "array") { 515 | 516 | foreach ($keys as $key => $val) { 517 | // order must be ascending (1) for boolean field 518 | if (gettype($val) == "boolean") 519 | $val = 1; 520 | 521 | if ($key === key($keys)) { 522 | $str .= $key . "_" . $val; 523 | } 524 | else { 525 | $str .= "_" . $key . "_" . $val; 526 | } 527 | } 528 | } else { 529 | // if $keys is just a string, append _1 as value 530 | $str .= $keys . "_1"; 531 | } 532 | return $str; 533 | } 534 | 535 | /** 536 | * String representation of this collection 537 | * 538 | * @return string - Returns the full name of this collection. 539 | */ 540 | public function __toString(): string { 541 | return $this->getFullName(); 542 | } 543 | 544 | /** 545 | * Validates this collection 546 | * 547 | * @param bool $scan_data - scan_data Only validate indices, not the 548 | * base collection. 549 | * 550 | * @return array - Returns the databases evaluation of this object. 551 | */ 552 | public function validate(bool $scan_data = false): array { 553 | $cmd = array("validate" => $this->name); 554 | if ($scan_data) { 555 | $cmd["full"] = true; 556 | } 557 | 558 | return $this->db->command($cmd); 559 | } 560 | } -------------------------------------------------------------------------------- /src/MongoCursor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ext_mongo.h" 3 | #include "bson_decode.h" 4 | #include "contrib/encode.h" 5 | 6 | namespace HPHP { 7 | 8 | //////////////////////////////////////////////////////////////////////////////// 9 | // class MongoCursor 10 | 11 | static void HHVM_METHOD(MongoCursor, rewind); 12 | 13 | static Variant HHVM_METHOD(MongoCursor, current) { 14 | bool started = this_->o_realProp("started_iterating", ObjectData::RealPropUnchecked, "MongoCursor")->toBoolean(); 15 | if (!started) 16 | { 17 | return init_null_variant; 18 | } 19 | 20 | mongoc_cursor_t *cursor = get_cursor(this_)->get(); 21 | const bson_t *doc; 22 | 23 | doc = mongoc_cursor_current(cursor); 24 | if (doc) { 25 | auto ret = cbson_loads(doc); 26 | return ret; 27 | } else { 28 | return init_null_variant; 29 | } 30 | } 31 | 32 | static bool HHVM_METHOD(MongoCursor, hasNext) { 33 | bson_error_t error; 34 | mongoc_cursor_t *cursor = get_cursor(this_)->get(); 35 | 36 | bool ret = mongoc_cursor_more(cursor); 37 | if (mongoc_cursor_error (cursor, &error)) { 38 | mongoThrow((const char *)error.message); 39 | } 40 | return ret; 41 | } 42 | 43 | static void HHVM_METHOD(MongoCursor, next) { 44 | const bson_t *doc; 45 | 46 | bool started = this_->o_realProp("started_iterating", ObjectData::RealPropUnchecked, "MongoCursor")->toBoolean(); 47 | if (!started) 48 | { 49 | HHVM_MN(MongoCursor, rewind)(this_); 50 | } 51 | 52 | mongoc_cursor_t *cursor = get_cursor(this_)->get(); 53 | // if (!mongoc_cursor_next (cursor, &doc)) { 54 | // if (mongoc_cursor_error (cursor, &error)) { 55 | // throw FatalErrorException(error.message); 56 | // } 57 | // } 58 | mongoc_cursor_next (cursor, &doc); //Note: error would be catched by valid() 59 | 60 | auto at = this_->o_realProp("at", ObjectData::RealPropUnchecked, "MongoCursor")->toInt64(); 61 | this_->o_set("at", at + 1, "MongoCursor"); 62 | } 63 | 64 | static void HHVM_METHOD(MongoCursor, reset) { 65 | if (get_cursor(this_)) { 66 | get_cursor(this_)->~MongocCursor(); 67 | this_->o_set(s_mongoc_cursor, init_null_variant, "MongoCursor"); 68 | } 69 | 70 | this_->o_set("at", 0, "MongoCursor"); 71 | this_->o_set("started_iterating", false_varNR, "MongoCursor"); 72 | } 73 | 74 | static void HHVM_METHOD(MongoCursor, rewind) { 75 | HHVM_MN(MongoCursor, reset)(this_); 76 | 77 | //TODO: need to test with null value 78 | auto connection = this_->o_realProp("connection", ObjectData::RealPropUnchecked, "MongoCursor")->toObject(); 79 | auto ns = this_->o_realProp("ns", ObjectData::RealPropUnchecked, "MongoCursor")->toString(); 80 | auto query = this_->o_realProp("query", ObjectData::RealPropUnchecked, "MongoCursor")->toArray(); 81 | bson_t query_bs; 82 | query_bs = encodeToBSON(query); 83 | 84 | /* 85 | bson_init(&query_bs); 86 | if (!query->empty()) { 87 | //Currently only supports "name" query 88 | bson_append_utf8(&query_bs, "name", 4, query[String("name")].toString().c_str(), query[String("name")].toString().length()); 89 | 90 | 91 | } 92 | */ 93 | 94 | //Parameters and their types: 95 | //static void HHVM_METHOD(MongoCursor, __construct, const Object& connection, const String& ns, const Array& query, const Array& fields) 96 | 97 | /* 98 | MongocCursor(mongoc_client_t *client, 99 | const char *db_and_collection, 100 | mongoc_query_flags_t flags, 101 | uint32_t skip, 102 | uint32_t limit, 103 | uint32_t batch_size, 104 | bool is_command, 105 | const bson_t *query, 106 | const bson_t *fields, 107 | const mongoc_read_prefs_t *read_prefs); 108 | 109 | */ 110 | 111 | //MongocCursor *cursor = new MongocCursor(get_client(connection)->get(), ns.c_str(), MONGOC_QUERY_NONE, 0, 0, 0, false, &query_bs, NULL, NULL); 112 | //std::cout << "Got past cursor construction with" << ns.c_str() << std::endl; 113 | 114 | /* fields needed: 115 | 116 | private $flags = []; //TODO: implement this 117 | private $skip = 0; 118 | private $limit = 0; 119 | private $batchSize = 100; 120 | private $fields = []; 121 | private $read_preference = []; 122 | 123 | */ 124 | 125 | bson_t fields_bs; 126 | mongoc_read_prefs_t *read_prefs; 127 | bson_t read_prefs_tags_bs; 128 | 129 | auto flags_array = this_->o_realProp("flags", ObjectData::RealPropUnchecked, "MongoCursor")->toArray(); 130 | int flags = MONGOC_QUERY_NONE; 131 | 132 | if (flags_array->exists((int64_t)0)) { flags |= MONGOC_QUERY_NONE;} 133 | if (flags_array->exists((int64_t)1)) { flags = (flags | MONGOC_QUERY_TAILABLE_CURSOR);} 134 | if (flags_array->exists((int64_t)2)) { flags = (flags | MONGOC_QUERY_SLAVE_OK);} 135 | if (flags_array->exists((int64_t)3)) { flags = (flags | MONGOC_QUERY_OPLOG_REPLAY);} 136 | if (flags_array->exists((int64_t)4)) { flags = (flags | MONGOC_QUERY_NO_CURSOR_TIMEOUT);} 137 | if (flags_array->exists((int64_t)5)) { flags = (flags | MONGOC_QUERY_AWAIT_DATA);} 138 | if (flags_array->exists((int64_t)6)) { flags = (flags | MONGOC_QUERY_EXHAUST);} 139 | if (flags_array->exists((int64_t)7)) { flags = (flags | MONGOC_QUERY_PARTIAL);} 140 | 141 | uint32_t skip = this_->o_realProp("skip", ObjectData::RealPropUnchecked, "MongoCursor")->toInt32(); 142 | uint32_t limit = this_->o_realProp("limit", ObjectData::RealPropUnchecked, "MongoCursor")->toInt32(); 143 | uint32_t batchSize = this_->o_realProp("batchSize", ObjectData::RealPropUnchecked, "MongoCursor")->toInt32(); 144 | auto fields = this_->o_realProp("fields", ObjectData::RealPropUnchecked, "MongoCursor")->toArray(); 145 | auto read_prefs_array = this_->o_realProp("read_preference", ObjectData::RealPropUnchecked, "MongoCursor")->toArray(); 146 | String read_pref_type = read_prefs_array[String("type")].toString(); 147 | Array read_pref_tagsets = read_prefs_array[String("tagsets")].toArray(); 148 | read_prefs_tags_bs = encodeToBSON(read_pref_tagsets); 149 | /* 150 | MongoClient::RP_PRIMARY, 151 | MongoClient::RP_PRIMARY_PREFERRED, 152 | MongoClient::RP_SECONDARY, 153 | MongoClient::RP_SECONDARY_PREFERRED, 154 | MongoClient::RP_NEAREST 155 | */ 156 | read_prefs = mongoc_read_prefs_new(MONGOC_READ_PRIMARY); 157 | 158 | if (read_pref_type.equal(String("RP_PRIMARY"))) { 159 | mongoc_read_prefs_set_mode(read_prefs, MONGOC_READ_PRIMARY); 160 | } else if (read_pref_type.equal(String("RP_PRIMARY_PREFERRED"))) { 161 | mongoc_read_prefs_set_mode(read_prefs, MONGOC_READ_PRIMARY_PREFERRED); 162 | } else if (read_pref_type.equal(String("RP_SECONDARY"))) { 163 | mongoc_read_prefs_set_mode(read_prefs, MONGOC_READ_SECONDARY); 164 | } else if (read_pref_type.equal(String("RP_SECONDARY_PREFERRED"))) { 165 | mongoc_read_prefs_set_mode(read_prefs, MONGOC_READ_SECONDARY_PREFERRED); 166 | } else if (read_pref_type.equal(String("RP_NEAREST"))) { 167 | mongoc_read_prefs_set_mode(read_prefs, MONGOC_READ_NEAREST); 168 | } 169 | mongoc_read_prefs_set_tags(read_prefs, &read_prefs_tags_bs); 170 | 171 | fields_bs = encodeToBSON(fields); 172 | 173 | MongocCursor *cursor= new MongocCursor( get_client(connection)->get(), 174 | ns.c_str(), 175 | (mongoc_query_flags_t)flags, 176 | skip, 177 | limit, 178 | batchSize, 179 | &query_bs, 180 | &fields_bs, 181 | read_prefs); 182 | 183 | this_->o_set(s_mongoc_cursor, cursor, s_mongocursor); 184 | bson_destroy(&query_bs); 185 | bson_destroy(&fields_bs); 186 | bson_destroy(&read_prefs_tags_bs); 187 | 188 | this_->o_set("started_iterating", true_varNR, "MongoCursor"); 189 | 190 | HHVM_MN(MongoCursor, next)(this_); 191 | } 192 | 193 | static bool HHVM_METHOD(MongoCursor, valid) { 194 | auto cur = HHVM_MN(MongoCursor, current)(this_); 195 | return ! cur.isNull(); 196 | } 197 | 198 | //////////////////////////////////////////////////////////////////////////////// 199 | 200 | void MongoExtension::_initMongoCursorClass() { 201 | HHVM_ME(MongoCursor, current); 202 | HHVM_ME(MongoCursor, hasNext); 203 | HHVM_ME(MongoCursor, next); 204 | HHVM_ME(MongoCursor, reset); 205 | HHVM_ME(MongoCursor, rewind); 206 | HHVM_ME(MongoCursor, valid); 207 | } 208 | 209 | } // namespace HPHP 210 | -------------------------------------------------------------------------------- /src/MongoCursor.php: -------------------------------------------------------------------------------- 1 | > 37 | public function current(): ?array; 38 | 39 | /** 40 | * Checks if there are any more elements in this cursor. 41 | * May be hard to do in both php and c++ 42 | * 43 | * @return bool - Returns if there is another element. 44 | */ 45 | <<__Native>> 46 | public function hasNext(): bool; 47 | 48 | /** 49 | * Advances the cursor to the next result 50 | * 51 | * @return void - NULL. 52 | */ 53 | <<__Native>> 54 | public function next(): void; 55 | 56 | /** 57 | * Clears the cursor 58 | * 59 | * @return void - NULL. 60 | */ 61 | <<__Native>> 62 | public function reset(): void; 63 | 64 | /** 65 | * Returns the cursor to the beginning of the result set 66 | * 67 | * @return void - NULL. 68 | */ 69 | <<__Native>> 70 | public function rewind(): void; 71 | 72 | /** 73 | * Checks if the cursor is reading a valid result. 74 | * 75 | * @return bool - If the current result is not null. 76 | */ 77 | <<__Native>> 78 | public function valid(): bool; 79 | 80 | 81 | 82 | //NON-NATIVE FUNCTIONS 83 | 84 | /** 85 | * Adds a top-level key/value pair to a query 86 | * 87 | * @param string $key - key Fieldname to add. 88 | * @param mixed $value - value Value to add. 89 | * 90 | * @return MongoCursor - Returns this cursor. 91 | */ 92 | public function addOption(string $key, 93 | mixed $value): MongoCursor { 94 | if ($this->started_iterating) { 95 | throw new MongoCursorException("Tried to add an option after started iterating"); 96 | } 97 | 98 | // Make the query object special (i.e. wrap in $query) if necessary 99 | if ( ! $this->isSpecial) { 100 | $this->query['$query'] = $this->query; 101 | $this->isSpecial = true; 102 | } 103 | 104 | $this->query[$key] = $value; 105 | return $this; 106 | } 107 | 108 | /** 109 | * Sets whether this cursor will wait for a while for a tailable cursor to 110 | * return more data 111 | * 112 | * @param bool $wait - wait If the cursor should wait for more data 113 | * to become available. 114 | * 115 | * @return MongoCursor - Returns this cursor. 116 | */ 117 | public function awaitData(bool $wait = true): MongoCursor { 118 | $this->wait = $wait; 119 | return $this; 120 | } 121 | 122 | /** 123 | * Limits the number of elements returned in one batch. 124 | * 125 | * @param int $batchSize - batchSize The number of results to return 126 | * per batch. If batchSize is 2 or more, it represents the size of each batch of 127 | * objects retrieved. If batchSize is 1 or negative, it will limit 128 | * of number returned documents to the absolute value of batchSize, and 129 | * the cursor will be closed. The batch size can be changed even 130 | * after a cursor is iterated, in which case the setting will apply on 131 | * the next batch retrieval. 132 | * 133 | * @return MongoCursor - Returns this cursor. 134 | */ 135 | public function batchSize(int $batchSize): MongoCursor { 136 | //TODO: Handle non-positive batch size 137 | $this->batchSize = $batchSize; 138 | return $this; 139 | } 140 | 141 | /** 142 | * Create a new cursor 143 | * 144 | * @param mongoclient $connection - connection Database connection. 145 | * @param string $ns - ns Full name of database and collection. 146 | * @param array $query - query Database query. 147 | * @param array $fields - fields Fields to return. 148 | * 149 | * @return - Returns the new cursor. 150 | */ 151 | 152 | public function __construct(MongoClient $connection, 153 | string $ns, 154 | array $query = array(), 155 | array $fields = array()) { 156 | $this->connection = $connection; 157 | $this->ns = $ns; 158 | $this->query = $query; 159 | $this->fields = $fields; 160 | 161 | } 162 | 163 | /** 164 | * Counts the number of results for this query 165 | * 166 | * @param bool $foundOnly - 167 | * 168 | * @return int - The number of documents returned by this cursor's 169 | * query. 170 | */ 171 | public function count(bool $foundOnly = false): int { 172 | $pieces = explode($this->ns); 173 | $db_name = $pieces[0]; 174 | $collection_name = $pieces[1]; 175 | 176 | $db = $this->connection->selectDB($db_name); 177 | $query = ["count" => $collection_name]; 178 | if ($foundOnly) { 179 | if ($this->limit > 0) { 180 | $query["limit"] = $this->limit; 181 | } 182 | if ($this->skip > 0) { 183 | $query["skip"] = $this->skip; 184 | } 185 | } 186 | 187 | $command_result = $db->command($query); 188 | if (!$command_result["ok"]) { 189 | throw new MongoCursorException(); 190 | } 191 | return $command_result["n"]; 192 | } 193 | 194 | /** 195 | * Checks if there are documents that have not been sent yet from the 196 | * database for this cursor 197 | * 198 | * @return bool - Returns if there are more results that have not been 199 | * sent to the client, yet. 200 | */ 201 | public function dead(): bool { 202 | return $this->dead; 203 | } 204 | 205 | /** 206 | * Return an explanation of the query, often useful for optimization and 207 | * debugging 208 | * 209 | * @return array - Returns an explanation of the query. 210 | */ 211 | public function explain(): array { 212 | $this->reset(); 213 | 214 | $originalLimit = $this->limit; 215 | $this->limit = abs($this->limit) * -1; 216 | $this->addOption('$explain', true); 217 | 218 | /* TODO: rewinding should not be necessary. Since we previously called 219 | * reset(), we should just have to call next() and have it initialize the 220 | * cursor resource automatically. Since we need to recall rewind() here, we 221 | * have to avoid calling next(), lest we advance past the single result. 222 | */ 223 | $this->rewind(); 224 | 225 | $retval = $this->current(); 226 | 227 | $this->limit = $originalLimit; 228 | unset($this->query['$explain']); 229 | $this->reset(); 230 | 231 | return $retval; 232 | } 233 | 234 | /** 235 | * Sets the fields for a query 236 | * 237 | * @param array $f - f Fields to return (or not return). 238 | * 239 | * @return MongoCursor - Returns this cursor. 240 | */ 241 | public function fields(array $fields) { 242 | if ($this->started_iterating) { 243 | throw new MongoCursorException("Tried to change fields after started iterating"); 244 | } 245 | $this->fields = $fields; 246 | return $this; 247 | } 248 | 249 | /** 250 | * Return the next object to which this cursor points, and advance the 251 | * cursor 252 | * 253 | * @return array - Returns the next object. 254 | */ 255 | public function getNext(): ?array { 256 | $this->next(); 257 | return $this->current(); 258 | } 259 | 260 | /** 261 | * Get the read preference for this query 262 | * 263 | * @return array - 264 | */ 265 | public function getReadPreference(): array { 266 | return $this->read_preference; 267 | } 268 | 269 | /** 270 | * Gives the database a hint about the query 271 | * 272 | * @param mixed $index - index Index to use for the query. If a 273 | * string is passed, it should correspond to an index name. If an array 274 | * or object is passed, it should correspond to the specification used 275 | * to create the index (i.e. the first argument to 276 | * MongoCollection::ensureIndex()). 277 | * 278 | * @return MongoCursor - Returns this cursor. 279 | */ 280 | public function hint(mixed $index): MongoCursor { 281 | 282 | if (is_object($index)) { 283 | $index = get_object_vars($index); 284 | } 285 | 286 | if (is_array($index)) { 287 | $index = MongoCollection::_toIndexString($index); 288 | } 289 | 290 | $this->addOption('$hint', $index); 291 | return $this; 292 | } 293 | 294 | /** 295 | * Sets whether this cursor will timeout 296 | * 297 | * @param bool $liveForever - liveForever If the cursor should be 298 | * immortal. 299 | * 300 | * @return MongoCursor - Returns this cursor. 301 | */ 302 | public function immortal(bool $liveForever = true): MongoCursor { 303 | if ($this->started_iterating) { 304 | throw new MongoCursorException("Tried to add an option after started iterating"); 305 | } 306 | 307 | $this->immortal = $liveForever; 308 | return $this; 309 | } 310 | 311 | /** 312 | * Gets the query, fields, limit, and skip for this cursor 313 | * 314 | * @return array - Returns the namespace, limit, skip, query, and 315 | * fields for this cursor. 316 | */ 317 | public function info(): array { 318 | $info = [ 319 | "ns" => $this->ns, 320 | "limit" => $this->limit, 321 | "batchSize" => $this->batchSize, 322 | "skip" => $this->skip, 323 | "flags" => $this->flags, 324 | "query" => $this->query, 325 | "fields" => $this->fields 326 | ]; 327 | return $info; 328 | } 329 | 330 | /** 331 | * Returns the current results _id 332 | * 333 | * @return string - The current results _id as a string. 334 | */ 335 | public function key(): mixed { 336 | $current = $this->current(); 337 | 338 | if ($current === null) { 339 | return null; 340 | } 341 | 342 | if (is_array($current) && array_key_exists('_id', $current)) { 343 | return (string) $current['_id']; 344 | } 345 | 346 | if (is_object($current) && property_exists($current, '_id')) { 347 | return (string) $current->_id; 348 | } 349 | 350 | return $this->at - 1; 351 | } 352 | /** 353 | * Limits the number of results returned 354 | * 355 | * @param int $num - num The number of results to return. 356 | * 357 | * @return MongoCursor - Returns this cursor. 358 | */ 359 | public function limit(int $num) { 360 | if ($this->started_iterating) { 361 | throw new MongoCursorException("Tried to add an option after started iterating"); 362 | } 363 | $this->limit = $num; 364 | return $this; 365 | } 366 | 367 | /* 368 | * If this query should fetch partial results from if a shard is down 369 | * 370 | * @param bool $okay - okay If receiving partial results is okay. 371 | * 372 | * @return MongoCursor - Returns this cursor. 373 | */ 374 | public function partial(bool $okay = true): MongoCursor { 375 | if ($this->started_iterating) { 376 | throw new MongoCursorException("Tried to add an option after started iterating"); 377 | } 378 | $this->partialResultsOK = $okay; 379 | return $this; 380 | } 381 | 382 | /** 383 | * Sets arbitrary flags in case there is no method available the specific 384 | * flag 385 | * 386 | * @param int $flag - flag Which flag to set. You can not set flag 6 387 | * (EXHAUST) as the driver does not know how to handle them. You will 388 | * get a warning if you try to use this flag. For available flags, 389 | * please refer to the wire protocol documentation. 390 | * @param bool $set - set Whether the flag should be set (TRUE) or 391 | * unset (FALSE). 392 | * 393 | * @return MongoCursor - Returns this cursor. 394 | */ 395 | public function setFlag(int $flag, 396 | bool $set = true): MongoCursor { 397 | if ($this->started_iterating) { 398 | throw new MongoCursorException("Tried to add an option after started iterating"); 399 | } 400 | $this->flags[$flag] = $set; 401 | return $this; 402 | } 403 | 404 | /** 405 | * Set the read preference for this query 406 | * 407 | * @param string $read_preference - 408 | * @param array $tags - 409 | * 410 | * @return MongoCursor - Returns this cursor. 411 | */ 412 | public function setReadPreference(string $read_preference, 413 | array $tags): MongoCursor { 414 | if ($this->started_iterating) { 415 | throw new MongoCursorException("Tried to add an option after started iterating"); 416 | } 417 | $this->read_preference['type'] = $read_preference; 418 | $this->read_preference['tagsets'] = $tags; 419 | return $this; 420 | } 421 | 422 | /** 423 | * Skips a number of results 424 | * 425 | * @param int $num - num The number of results to skip. 426 | * 427 | * @return MongoCursor - Returns this cursor. 428 | */ 429 | public function skip(int $num): MongoCursor { 430 | if ($this->started_iterating) { 431 | throw new MongoCursorException("Tried to add an option after started iterating"); 432 | } 433 | $this->skip = $num; 434 | return $this; 435 | } 436 | 437 | /** DEPRECATED 438 | * Sets whether this query can be done on a secondary 439 | * 440 | * @param bool $okay - okay If it is okay to query the secondary. 441 | * 442 | * @return MongoCursor - Returns this cursor. 443 | */ 444 | public function slaveOkay(bool $okay = true): MongoCursor { 445 | if ($this->started_iterating) { 446 | throw new MongoCursorException("Tried to add an option after started iterating"); 447 | } 448 | $this->slaveOkay = $okay; 449 | return $this; 450 | } 451 | 452 | /** 453 | * Use snapshot mode for the query 454 | * 455 | * @return MongoCursor - Returns this cursor. 456 | */ 457 | public function snapshot() { 458 | $this->addOption('$snapshot', true); 459 | return $this; 460 | } 461 | 462 | /** 463 | * Sorts the results by given fields 464 | * 465 | * @param array $fields - fields An array of fields by which to 466 | * sort. Each element in the array has as key the field name, and as 467 | * value either 1 for ascending sort, or -1 for descending sort. Each 468 | * result is first sorted on the first field in the array, then (if it 469 | * exists) on the second field in the array, etc. 470 | * 471 | * @return MongoCursor - Returns the same cursor that this method was 472 | * called on. 473 | */ 474 | public function sort(array $fields) { 475 | $this->addOption('$orderby', $fields); 476 | return $this; 477 | } 478 | 479 | /** TODO: Make sure C++ code closes cursor appropriately 480 | * Sets whether this cursor will be left open after fetching the last 481 | * results 482 | * 483 | * @param bool $tail - tail If the cursor should be tailable. 484 | * 485 | * @return MongoCursor - Returns this cursor. 486 | */ 487 | public function tailable(bool $tail = true): MongoCursor { 488 | if ($this->started_iterating) { 489 | throw new MongoCursorException("Tried to add an option after started iterating"); 490 | } 491 | $this->tailable = $tail; 492 | return $this; 493 | } 494 | 495 | /** TODO: How does query-side timeout work? 496 | * Sets a client-side timeout for this query 497 | * 498 | * @param int $ms - 499 | * 500 | * @return MongoCursor - This cursor. 501 | */ 502 | public function timeout(int $ms): MongoCursor { 503 | if ($this->started_iterating) { 504 | throw new MongoCursorException("Tried to add an option after started iterating"); 505 | } 506 | $this->timeout = $ms; 507 | return $this; 508 | } 509 | 510 | } 511 | -------------------------------------------------------------------------------- /src/MongoDB.php: -------------------------------------------------------------------------------- 1 | command(array("getnonce" => 1)); 33 | 34 | $saltedHash = md5($nonce["nonce"]."${username}${hash}"); 35 | 36 | $result = $this->command(array("authenticate" => 1, 37 | "user" => $username, 38 | "nonce" => $nonce["nonce"], 39 | "key" => $saltedHash)); 40 | 41 | return $result; 42 | } 43 | 44 | /** 45 | * Execute a database command 46 | * 47 | * @param array $command - command The query to send. 48 | * @param array $options - options 49 | * 50 | * @return array - Returns database response. Every database response 51 | * is always maximum one document, which means that the result of a 52 | * database command can never exceed 16MB. 53 | */ 54 | public function command(array $command, 55 | array $options = array()): array { 56 | //echo "Running command "; 57 | //var_dump($command); 58 | //$coll = $this->selectCollection('$cmd'); 59 | //echo "Finished selecting collection "; 60 | return $this->selectCollection('$cmd')->findOne($command); 61 | } 62 | 63 | /** 64 | * Creates a new database 65 | * 66 | * @param mongoclient $conn - MongoClient conn Database connection. 67 | * @param string $name - name Database name. 68 | * 69 | * @return - Returns the database. 70 | */ 71 | 72 | public function __construct(MongoClient $conn, string $name) { 73 | $this->client = $conn; 74 | $this->db_name = $name; 75 | } 76 | 77 | /** 78 | * Creates a collection 79 | * 80 | * @param string $name - name The name of the collection. 81 | * @param array $options - options An array containing options for 82 | * the collections. Each option is its own element in the options 83 | * array, with the option name listed below being the key of the 84 | * element. 85 | * 86 | * @return MongoCollection - Returns a collection object representing 87 | * the new collection. 88 | */ 89 | public function createCollection(string $name, 90 | array $options = array()): MongoCollection { 91 | $cmd = array("create" => $name); 92 | $option_choices = array("capped", "size", "max", "autoIndexId"); 93 | foreach ($option_choices as $op) { 94 | if(isset($options[$op])) { 95 | $cmd[$op] = $options[$op]; 96 | } 97 | } 98 | $result = $this->command($cmd); 99 | 100 | if (!$result["ok"]) { 101 | throw new MongoException("Unable to create collection"); 102 | } 103 | return new MongoCollection($this, $name); 104 | } 105 | 106 | 107 | /** TODO 108 | * Creates a database reference 109 | * 110 | * @param string $collection - collection The collection to which 111 | * the database reference will point. 112 | * @param mixed $document_or_id - document_or_id If an array or 113 | * object is given, its _id field will be used as the reference ID. If 114 | * a MongoId or scalar is given, it will be used as the reference ID. 115 | * 116 | * @return array - Returns a database reference array. If an array 117 | * without an _id field was provided as the document_or_id parameter, 118 | * NULL will be returned. 119 | */ 120 | public function createDBRef(string $collection, 121 | mixed $document_or_id): array { 122 | if (is_array($document_or_id)) { 123 | $id = $document_or_id['_id']; 124 | } else { 125 | $id = $document_or_id; 126 | } 127 | return MongoDBRef::create($collection, $id, $this->db_name); 128 | } 129 | 130 | /** 131 | * Drops this database 132 | * 133 | * @return array - Returns the database response. 134 | */ 135 | public function drop(): array { 136 | return $this->command(array("dropDatabase" => 1)); 137 | } 138 | 139 | /** 140 | * Drops a collection [deprecated]. 141 | * 142 | * @param mixed $coll - coll MongoCollection or name of collection 143 | * to drop. 144 | * 145 | * @return array - Returns the database response. 146 | */ 147 | public function dropCollection(mixed $coll): array { 148 | if (is_object($coll)) { 149 | $coll = $coll->getName(); 150 | } 151 | return $this->command(array('drop' => $coll)); 152 | } 153 | 154 | /** 155 | * Runs JavaScript code on the database server. 156 | * 157 | * @param mixed $code - code MongoCode or string to execute. 158 | * @param array $args - args Arguments to be passed to code. 159 | * 160 | * @return array - Returns the result of the evaluation. 161 | */ 162 | public function execute(mixed $code, array $args = array()): array { 163 | return $this->command(array('eval' => $code, 'args' => $args)); 164 | } 165 | 166 | 167 | /** 168 | * Creates a database error 169 | * 170 | * @return bool - Returns the database response. 171 | */ 172 | public function forceError(): bool { 173 | return $this->command(array('forceerror' => 1)); 174 | } 175 | 176 | /** 177 | * Gets a collection 178 | * 179 | * @param string $name - name The name of the collection. 180 | * 181 | * @return MongoCollection - Returns the collection. 182 | */ 183 | public function __get(string $name): MongoCollection { 184 | return $this->selectCollection($name); 185 | } 186 | 187 | public function __getDBName(): string { 188 | return $this->db_name; 189 | } 190 | 191 | public function __getClient(): MongoClient { 192 | return $this->client; 193 | } 194 | 195 | /** TODO: Handle system collections 196 | * Get all collections from this database 197 | * 198 | * @param bool $includeSystemCollections - 199 | * 200 | * @return array - Returns the names of the all the collections in the 201 | * database as an array. 202 | */ 203 | public function getCollectionNames(bool $includeSystemCollections = false): array { 204 | $allCollections = []; 205 | $db_name_length = strlen($this->db_name) + 1; 206 | $c = $this->selectCollection("system.namespaces")->find(); 207 | while ($c->hasNext()) { 208 | $c->next(); 209 | $curr_coll = $c->current(); 210 | $name = $curr_coll["name"]; 211 | 212 | if (strpos("$", $name) >= 0 && strpos(".oplog.$") === false) { 213 | continue; 214 | } 215 | 216 | $allCollections[] = substr($name, $db_name_length); 217 | } 218 | sort($allCollections); 219 | return $allCollections; 220 | } 221 | 222 | /** 223 | * Fetches the document pointed to by a database reference 224 | * 225 | * @param array $ref - ref A database reference. 226 | * 227 | * @return array - Returns the document pointed to by the reference. 228 | */ 229 | public function getDBRef(array $ref): array { 230 | return MongoDBRef::get($this, $ref); 231 | } 232 | 233 | /** 234 | * TODO: Make MongoGridFS class 235 | * Fetches toolkit for dealing with files stored in this database 236 | * 237 | * @param string $prefix - prefix The prefix for the files and 238 | * chunks collections. 239 | * 240 | * @return MongoGridFS - Returns a new gridfs object for this database. 241 | */ 242 | // <<__Native>> 243 | // public function getGridFS(string $prefix = fs): MongoGridFS; 244 | 245 | /** 246 | * Gets this databases profiling level 247 | * 248 | * @return int - Returns the profiling level. 249 | */ 250 | public function getProfilingLevel(): int { 251 | return $this->command(array('profile' => -1)); 252 | } 253 | 254 | /** 255 | * Get the read preference for this database 256 | * 257 | * @return array - 258 | */ 259 | public function getReadPreference(): array { 260 | return $this->read_preference; 261 | } 262 | 263 | /** 264 | * Get slaveOkay setting for this database 265 | * 266 | * @return bool - Returns the value of slaveOkay for this instance. 267 | */ 268 | public function getSlaveOkay(): bool{ 269 | return $this->slaveOkay; 270 | } 271 | 272 | /** 273 | * Check if there was an error on the most recent db operation performed 274 | * 275 | * @return array - Returns the error, if there was one. 276 | */ 277 | public function lastError(): array { 278 | return $this->command(array('getLastError' => 1)); 279 | } 280 | 281 | /** TODO: handle system collections 282 | * Gets an array of all MongoCollections for this database 283 | * 284 | * @param bool $includeSystemCollections - 285 | * 286 | * @return array - Returns an array of MongoCollection objects. 287 | */ 288 | public function listCollections(bool $includeSystemCollections = false): array { 289 | $collection_names = $this->getCollectionNames(); 290 | foreach ($collection_names as $name) { 291 | if (!isset($this->collections[$name])) { 292 | $this->collections[$name] = new MongoCollection($this, $name); 293 | } 294 | } 295 | return $this->collections; 296 | } 297 | 298 | /** TODO: Will be deprecated soon 299 | * Checks for the last error thrown during a database operation 300 | * 301 | * @return array - Returns the error and the number of operations ago 302 | * it occurred. 303 | */ 304 | public function prevError(): array { 305 | return $this->command(array('getPrevError' => 1)); 306 | } 307 | 308 | /** 309 | * Repairs and compacts this database 310 | * 311 | * @param bool $preserve_cloned_files - preserve_cloned_files If 312 | * cloned files should be kept if the repair fails. 313 | * @param bool $backup_original_files - backup_original_files If 314 | * original files should be backed up. 315 | * 316 | * @return array - Returns db response. 317 | */ 318 | public function repair(bool $preserve_cloned_files = false, 319 | bool $backup_original_files = false): array { 320 | return $this->command(array('repairDatabase' => 1)); 321 | } 322 | 323 | /** 324 | * Clears any flagged errors on the database 325 | * 326 | * @return array - Returns the database response. 327 | */ 328 | public function resetError(): array { 329 | return $this->command(array('reseterror' => 1)); 330 | } 331 | 332 | /** 333 | * Gets a collection 334 | * 335 | * @param string $name - name The collection name. 336 | * 337 | * @return MongoCollection - Returns a new collection object. 338 | */ 339 | public function selectCollection(string $name): MongoCollection { 340 | if (!isset($this->collections[$name])) { 341 | $this->collections[$name] = new MongoCollection($this, $name); 342 | } 343 | return $this->collections[$name]; 344 | } 345 | 346 | /** 347 | * Sets this databases profiling level 348 | * 349 | * @param int $level - level Profiling level. 350 | * 351 | * @return int - Returns the previous profiling level. 352 | */ 353 | public function setProfilingLevel(int $level): int { 354 | return $this->command(array('profile' => $level)); 355 | } 356 | 357 | /** TODO: When to return false? 358 | * Set the read preference for this database 359 | * 360 | * @param string $read_preference - 361 | * @param array $tags - 362 | * 363 | * @return bool - 364 | */ 365 | public function setReadPreference(string $read_preference, 366 | array $tags): bool { 367 | $this->read_preference['type'] = $read_preference; 368 | $this->read_preference['tagsets'] = $tags; 369 | return true; 370 | } 371 | 372 | /** 373 | * Change slaveOkay setting for this database 374 | * 375 | * @param bool $ok - ok If reads should be sent to secondary members 376 | * of a replica set for all possible queries using this MongoDB 377 | * instance. 378 | * 379 | * @return bool - Returns the former value of slaveOkay for this 380 | * instance. 381 | */ 382 | public function setSlaveOkay(bool $ok = true): bool { 383 | $former = $this->slaveOkay; 384 | $this->slaveOkay = $ok; 385 | return $former; 386 | } 387 | 388 | public function setWriteConcern(mixed $w, int $timeout = 10000) { 389 | if( !is_integer($w) || !is_string($w)) { 390 | throw Exception("Invalid argument to set write concern"); 391 | } 392 | $this->writeConcern["w"] = $w; 393 | if (!isset($this->writeConcern["timeout"])) { 394 | $this->writeConcern["timeout"] = $timeout; 395 | } 396 | } 397 | 398 | /** 399 | * The name of this database 400 | * 401 | * @return string - Returns this databases name. 402 | */ 403 | public function __toString(): string { 404 | return $this->db_name; 405 | } 406 | 407 | } 408 | -------------------------------------------------------------------------------- /src/bson.cpp: -------------------------------------------------------------------------------- 1 | #include "bson_decode.h" 2 | #include "contrib/encode.h" 3 | #include "ext_mongo.h" 4 | #include 5 | namespace HPHP { 6 | 7 | static String encode(const Variant& mixture) { 8 | bson_t bson = encodeToBSON(mixture); 9 | 10 | const char* output = (const char*) bson_get_data(&bson); 11 | String s = String(output, bson.len, CopyString); 12 | return s; 13 | } 14 | 15 | static Array HHVM_FUNCTION(bson_decode, const String& bson) { 16 | return cbson_loads_from_string(bson); 17 | } 18 | 19 | static String HHVM_FUNCTION(bson_encode, const Variant& value) { 20 | return encode(value); 21 | } 22 | 23 | void MongoExtension::_initBSON() { 24 | HHVM_FE(bson_decode); 25 | HHVM_FE(bson_encode); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/bson.php: -------------------------------------------------------------------------------- 1 | > 4 | function bson_decode(string $bson): array; 5 | 6 | <<__Native>> 7 | function bson_encode(mixed $value): string; 8 | -------------------------------------------------------------------------------- /src/bson_decode.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "./contrib/classes.h" 3 | #include "hphp/runtime/base/base-includes.h" 4 | #include "bson_decode.h" 5 | #include "ext_mongo.h" 6 | 7 | namespace HPHP { 8 | 9 | // Helper Function to Instantiate Defined Mongo Classes 10 | // Adapted from HNI example 11 | static ObjectData * 12 | create_object(const StaticString * className, Array params) 13 | { 14 | TypedValue ret; 15 | Class * cls = Unit::loadClass(className -> get()); 16 | ObjectData * obj = ObjectData::newInstance(cls); 17 | obj->incRefCount(); 18 | 19 | g_context->invokeFunc( 20 | &ret, 21 | cls->getCtor(), 22 | params, 23 | obj 24 | ); 25 | return obj; 26 | } 27 | 28 | static bool 29 | cbson_loads_visit_double (const bson_iter_t *iter, 30 | const char *key, 31 | double v_double, 32 | void *output) 33 | { 34 | ((Array *) output)->add(String(key),v_double); 35 | return false; 36 | } 37 | 38 | static bool 39 | cbson_loads_visit_utf8 (const bson_iter_t *iter, 40 | const char *key, 41 | size_t v_utf8_len, 42 | const char *v_utf8, 43 | void *output) 44 | { 45 | ((Array *) output)->add(String(key),v_utf8); 46 | return false; 47 | } 48 | 49 | // Pre-declaration so compiler doesn't complain about 50 | // undefined methods 51 | static bool 52 | cbson_loads_visit_document (const bson_iter_t *iter, 53 | const char *key, 54 | const bson_t *v_document, 55 | void *data); 56 | 57 | 58 | static bool 59 | cbson_loads_visit_array (const bson_iter_t *iter, 60 | const char *key, 61 | const bson_t *v_array, 62 | void *data); 63 | 64 | static bool 65 | cbson_loads_visit_binary (const bson_iter_t *iter, 66 | const char *key, 67 | bson_subtype_t v_subtype, 68 | size_t v_binary_len, 69 | const uint8_t *v_binary, 70 | void *output) 71 | { 72 | ObjectData * data = create_object(&s_MongoBinData, 73 | make_packed_array( 74 | String((const char*) v_binary, v_binary_len, CopyString), 75 | (int) v_subtype)); 76 | ((Array *) output) -> add(String(key), data); 77 | return false; 78 | 79 | } 80 | 81 | static bool 82 | cbson_loads_visit_oid (const bson_iter_t *iter, 83 | const char *key, 84 | const bson_oid_t *oid, 85 | void *output) 86 | { 87 | char id[25]; 88 | bson_oid_to_string(oid, id); 89 | ObjectData * data = create_object(&s_MongoId, 90 | make_packed_array(String(id))); 91 | ((Array *) output)->add(String(key), data); 92 | return false; 93 | } 94 | 95 | static bool 96 | cbson_loads_visit_bool (const bson_iter_t *iter, 97 | const char *key, 98 | bool v_bool, 99 | void *output) 100 | { 101 | ((Array *) output)->add(String(key), v_bool); 102 | return false; 103 | } 104 | 105 | static bool 106 | cbson_loads_visit_date_time (const bson_iter_t *iter, 107 | const char *key, 108 | int64_t msec_since_epoch, 109 | void *output) 110 | { 111 | // Renaming for convenience 112 | int64_t msec = msec_since_epoch; 113 | 114 | ObjectData * data = create_object(&s_MongoDate, 115 | make_packed_array(msec / 1000, (msec % 1000) * 1000)); 116 | 117 | ((Array *) output)->add(String(key), data); 118 | 119 | return false; 120 | } 121 | 122 | static bool 123 | cbson_loads_visit_null (const bson_iter_t *iter, 124 | const char *key, 125 | void *output) 126 | { 127 | ((Array *) output)->add(String(key), Variant()); 128 | return false; 129 | } 130 | 131 | static bool 132 | cbson_loads_visit_regex (const bson_iter_t *iter, 133 | const char *key, 134 | const char *regex, 135 | const char *options, 136 | void *output) 137 | { 138 | String regex_string = "/" + String(regex) + "/" + String(options); 139 | ObjectData * data = create_object(&s_MongoRegex, 140 | make_packed_array(regex_string)); 141 | 142 | ((Array *) output)->add(String(key), data); 143 | 144 | return false; 145 | } 146 | 147 | static bool 148 | cbson_loads_visit_dbpointer (const bson_iter_t *iter, 149 | const char *key, 150 | size_t v_collection_len, 151 | const char *v_collection, 152 | const bson_oid_t *v_oid, 153 | void *output) 154 | { 155 | char id[25]; 156 | bson_oid_to_string(v_oid, id); 157 | ObjectData * data = create_object(&s_MongoDBRef, 158 | make_packed_array( 159 | String(v_collection, v_collection_len, CopyString), 160 | String(id))); 161 | ((Array *)output)->add(String(key), data); 162 | return false; 163 | //TODO: Finish this 164 | } 165 | 166 | static bool 167 | cbson_loads_visit_code (const bson_iter_t *iter, 168 | const char *key, 169 | size_t v_code_len, 170 | const char *v_code, 171 | void *output) 172 | { 173 | ObjectData * data = create_object(&s_MongoCode, 174 | make_packed_array(String(v_code, v_code_len, CopyString))); 175 | ((Array *)output)->add(String(key), data); 176 | return false; 177 | } 178 | 179 | static bool 180 | cbson_loads_visit_int32 (const bson_iter_t *iter, 181 | const char *key, 182 | int32_t v_int32, 183 | void *output) 184 | { 185 | ((Array *) output)->add(String(key), v_int32); 186 | return false; 187 | } 188 | 189 | // Not implemented in cbson.c 190 | static bool 191 | cbson_loads_visit_timestamp (const bson_iter_t *iter, 192 | const char *key, 193 | uint32_t timestamp, 194 | uint32_t increment, 195 | void *output) 196 | { 197 | ObjectData * data = create_object(&s_MongoTimestamp, 198 | make_packed_array((int64_t)timestamp, (int64_t)increment)); 199 | 200 | ((Array *) output)->add(String(key), data); 201 | 202 | return false; 203 | } 204 | 205 | static bool 206 | cbson_loads_visit_int64 (const bson_iter_t *iter, 207 | const char* key, 208 | int64_t v_int64, 209 | void *output) 210 | { 211 | ((Array *) output)->add(String(key),v_int64); 212 | return false; 213 | } 214 | 215 | static bool 216 | cbson_loads_visit_maxkey (const bson_iter_t *iter, 217 | const char* key, 218 | void *output) 219 | { 220 | ObjectData * data = create_object(&s_MongoMaxKey, Array()); 221 | ((Array *)output)->add(String(key), data); 222 | return false; 223 | } 224 | 225 | static bool 226 | cbson_loads_visit_minkey (const bson_iter_t *iter, 227 | const char* key, 228 | void *output) 229 | { 230 | ObjectData * data = create_object(&s_MongoMinKey, Array()); 231 | ((Array *)output)->add(String(key), data); 232 | return false; 233 | } 234 | 235 | static const 236 | bson_visitor_t gLoadsVisitors = { 237 | NULL, //TODO: visit before 238 | NULL, //TODO: visit after 239 | NULL, //TODO: visit corrupt 240 | cbson_loads_visit_double, 241 | cbson_loads_visit_utf8, 242 | cbson_loads_visit_document, 243 | cbson_loads_visit_array, 244 | cbson_loads_visit_binary, //TODO: visit binary 245 | NULL, //TODO: visit undefined 246 | cbson_loads_visit_oid, 247 | cbson_loads_visit_bool, 248 | cbson_loads_visit_date_time, 249 | cbson_loads_visit_null, 250 | cbson_loads_visit_regex, 251 | cbson_loads_visit_dbpointer, //TODO: visit dbpointer 252 | cbson_loads_visit_code, //TODO: visit code 253 | NULL, //TODO: visit symbol 254 | NULL, //TODO: visit code with scope 255 | cbson_loads_visit_int32, 256 | cbson_loads_visit_timestamp, 257 | cbson_loads_visit_int64, 258 | cbson_loads_visit_maxkey, 259 | cbson_loads_visit_minkey, 260 | }; 261 | 262 | static bool 263 | cbson_loads_visit_document (const bson_iter_t *iter, 264 | const char *key, 265 | const bson_t *v_document, 266 | void *data) 267 | { 268 | bson_iter_t child; 269 | Array * ret = (Array *) data; 270 | Array obj; 271 | 272 | bson_return_val_if_fail(iter, true); 273 | bson_return_val_if_fail(key, true); 274 | bson_return_val_if_fail(v_document, true); 275 | 276 | if (bson_iter_init(&child, v_document)) 277 | { 278 | obj = Array(); 279 | if (!bson_iter_visit_all(&child, &gLoadsVisitors, &obj)) 280 | { 281 | ret->add(String(key), obj); 282 | } 283 | } 284 | return false; 285 | } 286 | 287 | static bool 288 | cbson_loads_visit_array (const bson_iter_t *iter, 289 | const char *key, 290 | const bson_t *v_array, 291 | void *data) 292 | { 293 | bson_iter_t child; 294 | Array * ret = (Array *) data; 295 | Array obj; 296 | 297 | bson_return_val_if_fail(iter, true); 298 | bson_return_val_if_fail(key, true); 299 | bson_return_val_if_fail(v_array, true); 300 | 301 | if (bson_iter_init(&child, v_array)) 302 | { 303 | obj = Array(); 304 | if (!bson_iter_visit_all(&child, &gLoadsVisitors, &obj)) 305 | { 306 | ret->add(String(key), obj); 307 | } 308 | } 309 | return false; 310 | } 311 | 312 | Array 313 | cbson_loads (const bson_t * bson) 314 | { 315 | bson_iter_t iter; 316 | 317 | Array ret = Array(); 318 | 319 | if (!bson_iter_init(&iter, bson)) 320 | { 321 | mongoThrow("Failed to initialize BSON iterator"); 322 | } 323 | bson_iter_visit_all(&iter, &gLoadsVisitors, &ret); 324 | 325 | return ret; 326 | } 327 | 328 | 329 | 330 | Array 331 | cbson_loads_from_string (const String& bson) 332 | { 333 | bson_reader_t * reader; 334 | const bson_t * obj; 335 | bool reached_eof; 336 | 337 | Array output = Array(); 338 | 339 | reader = bson_reader_new_from_data((uint8_t *)bson.c_str(), bson.size()); 340 | 341 | if (!(obj = bson_reader_read(reader, &reached_eof))) { 342 | mongoThrow("Unexpected end of BSON. Input document is likely corrupted!"); 343 | } 344 | 345 | output = cbson_loads(obj); 346 | bson_reader_destroy(reader); 347 | 348 | return output; 349 | } 350 | // Namespace 351 | } -------------------------------------------------------------------------------- /src/bson_decode.h: -------------------------------------------------------------------------------- 1 | #include "hphp/runtime/base/base-includes.h" 2 | #include 3 | 4 | namespace HPHP { 5 | 6 | Array cbson_loads_from_string(const String& bson); 7 | Array cbson_loads (const bson_t * bson); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/bson_test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mongodb-labs/mongo-hhvm-driver-unsupported/f274cf2c789856bf122ff34e563f416b832bd815/src/bson_test -------------------------------------------------------------------------------- /src/bson_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() 5 | { 6 | bson_t b[1]; 7 | bson_init( b ); 8 | bson_append_int32( b, "int32", 5, 1001); 9 | bson_append_int64( b, "int64", 5, 999999); 10 | bson_append_utf8( b, "string", 6, "test string", 11); 11 | bson_append_bool(b, "boolean", 7, true); 12 | printf("number of keys is %d\n", bson_count_keys(b)); 13 | // Array arr = HPHP::cbson_loads(b); 14 | bson_destroy( b ); 15 | } 16 | -------------------------------------------------------------------------------- /src/contrib/classes.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Di Máximo Cuadros 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | * 5 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | */ 9 | #include "hphp/runtime/base/base-includes.h" 10 | #include 11 | 12 | // Adapted from HNI Source 13 | namespace HPHP { 14 | const StaticString s_MongoDate("MongoDate"); 15 | const StaticString s_MongoId("MongoId"); 16 | const StaticString s_MongoRegex("MongoRegex"); 17 | const StaticString s_MongoTimestamp("MongoTimestamp"); 18 | const StaticString s_MongoCode("MongoCode"); 19 | const StaticString s_MongoBinData("MongoBinData"); 20 | const StaticString s_MongoInt32("MongoInt32"); 21 | const StaticString s_MongoInt64("MongoInt64"); 22 | const StaticString s_MongoDBRef("MongoDBRef"); 23 | const StaticString s_MongoMaxKey("MongoMaxKey"); 24 | const StaticString s_MongoMinKey("MongoMinKey"); 25 | } 26 | -------------------------------------------------------------------------------- /src/contrib/encode.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Di Máximo Cuadros 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | * 5 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | */ 9 | #include "hphp/runtime/base/base-includes.h" 10 | #include 11 | #include "encode.h" 12 | #include "classes.h" 13 | 14 | namespace HPHP { 15 | void fillBSONWithArray(const Array& value, bson_t* bson) { 16 | for (ArrayIter iter(value); iter; ++iter) { 17 | Variant key(iter.first()); 18 | const Variant& data(iter.secondRef()); 19 | 20 | variantToBSON(data, key.toString().c_str(), bson); 21 | } 22 | } 23 | 24 | void variantToBSON(const Variant& value, const char* key, bson_t* bson) { 25 | switch(value.getType()) { 26 | case KindOfUninit: 27 | case KindOfNull: 28 | nullToBSON(key, bson); 29 | break; 30 | case KindOfBoolean: 31 | boolToBSON(value.toBoolean(), key, bson); 32 | break; 33 | case KindOfInt64: 34 | int64ToBSON(value.toInt64(), key, bson); 35 | break; 36 | case KindOfDouble: 37 | doubleToBSON(value.toDouble(), key, bson); 38 | break; 39 | case KindOfStaticString: 40 | case KindOfString: 41 | stringToBSON(value.toString(), key, bson); 42 | break; 43 | case KindOfArray: 44 | arrayToBSON(value.toArray(), key, bson); 45 | break; 46 | case KindOfObject: 47 | objectToBSON(value.toObject(), key, bson); 48 | break; 49 | default: 50 | break; 51 | } 52 | } 53 | 54 | void arrayToBSON(const Array& value, const char* key, bson_t* bson) { 55 | bson_t child; 56 | bool isDocument = arrayIsDocument(value); 57 | if (isDocument) { 58 | bson_append_document_begin(bson, key, -1, &child); 59 | } else { 60 | bson_append_array_begin(bson, key, -1, &child); 61 | } 62 | 63 | fillBSONWithArray(value, &child); 64 | 65 | if (isDocument) { 66 | bson_append_document_end(bson, &child); 67 | } else { 68 | bson_append_array_end(bson, &child); 69 | } 70 | } 71 | 72 | void doubleToBSON(const double value,const char* key, bson_t* bson) { 73 | bson_append_double(bson, key, -1, value); 74 | } 75 | 76 | void nullToBSON(const char* key, bson_t* bson) { 77 | bson_append_null(bson, key, -1); 78 | } 79 | 80 | void boolToBSON(const bool value, const char* key, bson_t* bson) { 81 | bson_append_bool(bson, key, -1, value); 82 | } 83 | 84 | void int64ToBSON(const int64_t value, const char* key, bson_t* bson) { 85 | bson_append_int64(bson, key, -1, value); 86 | } 87 | 88 | void stringToBSON(const String& value, const char* key, bson_t* bson) { 89 | bson_append_utf8(bson, key, strlen(key), value.c_str(), -1); 90 | } 91 | 92 | 93 | void objectToBSON(const Object& value, const char* key, bson_t* bson) { 94 | const String& className = value->o_getClassName(); 95 | 96 | if (className == s_MongoId) { 97 | mongoIdToBSON(value, key, bson); 98 | } else if (className == s_MongoDate) { 99 | mongoDateToBSON(value, key, bson); 100 | } else if (className == s_MongoRegex) { 101 | mongoRegexToBSON(value, key, bson); 102 | } else if (className == s_MongoTimestamp) { 103 | mongoTimestampToBSON(value, key, bson); 104 | } else if (className == s_MongoCode) { 105 | mongoCodeToBSON(value, key, bson); 106 | } else if (className == s_MongoBinData) { 107 | mongoBinDataToBSON(value, key, bson); 108 | } else if (className == s_MongoInt32) { 109 | mongoInt32ToBSON(value, key, bson); 110 | } else if (className == s_MongoInt64) { 111 | mongoInt64ToBSON(value, key, bson); 112 | } else if (className == s_MongoMaxKey) { 113 | mongoMaxKeyToBSON(key, bson); 114 | } else if (className == s_MongoMinKey) { 115 | mongoMinKeyToBSON(key, bson); 116 | } else { 117 | arrayToBSON(value.toArray(), key, bson); 118 | } 119 | } 120 | 121 | ////////////////////////////////////////////////////////////////////////////// 122 | 123 | void mongoTimestampToBSON(const Object& value, const char* key, bson_t* bson) { 124 | bson_append_timestamp(bson, key, -1, 125 | value->o_get("sec").toInt64(), 126 | value->o_get("inc").toInt64() 127 | ); 128 | } 129 | 130 | void mongoRegexToBSON(const Object& value, const char* key, bson_t* bson) { 131 | bson_append_regex(bson, key, -1, 132 | value->o_get("regex").toString().c_str(), 133 | value->o_get("flags").toString().c_str() 134 | ); 135 | } 136 | 137 | void mongoIdToBSON(const Object& value, const char* key, bson_t* bson) { 138 | bson_oid_t oid; 139 | bson_oid_init_from_string(&oid, value->o_get("$id").toString().c_str()); 140 | bson_append_oid(bson, key, -1, &oid); 141 | } 142 | 143 | void mongoDateToBSON(const Object& value, const char* key, bson_t* bson) { 144 | int64_t mili = 145 | (value->o_get("sec").toInt64() * 1000) + 146 | (value->o_get("usec").toInt64() / 1000); 147 | 148 | bson_append_date_time(bson, key, -1, mili); 149 | } 150 | 151 | void mongoCodeToBSON(const Object& value, const char* key, bson_t* bson) { 152 | bson_t child; 153 | bson_init(&child); 154 | fillBSONWithArray( 155 | value->o_get("scope", true, s_MongoCode.get()).toArray(), 156 | &child 157 | ); 158 | 159 | bson_append_code_with_scope(bson, key, -1, 160 | value->o_get("code", true, s_MongoCode.get()).toString().c_str(), 161 | &child 162 | ); 163 | } 164 | 165 | void mongoBinDataToBSON(const Object& value, const char* key, bson_t* bson) { 166 | const String& binary = value->o_get("bin").toString(); 167 | 168 | bson_append_binary(bson, key, -1, 169 | (bson_subtype_t) value->o_get("type").toInt32(), 170 | (const uint8_t*) binary.c_str(), 171 | binary.size() 172 | ); 173 | } 174 | 175 | void mongoInt32ToBSON(const Object& value, const char* key, bson_t* bson) { 176 | bson_append_int32(bson, key, -1, value->o_get("value").toInt32()); 177 | } 178 | 179 | void mongoInt64ToBSON(const Object& value, const char* key, bson_t* bson) { 180 | bson_append_int64(bson, key, -1, value->o_get("value").toInt64()); 181 | } 182 | 183 | void mongoMinKeyToBSON(const char* key, bson_t* bson) { 184 | bson_append_minkey(bson, key, -1); 185 | } 186 | 187 | void mongoMaxKeyToBSON(const char* key, bson_t* bson) { 188 | bson_append_maxkey(bson, key, -1); 189 | } 190 | 191 | ////////////////////////////////////////////////////////////////////////////// 192 | //* Objects *// 193 | bool arrayIsDocument(const Array& arr) { 194 | int64_t max_index = 0; 195 | 196 | for (ArrayIter it(arr); it; ++it) { 197 | Variant key = it.first(); 198 | if (!key.isNumeric()) { 199 | return true; 200 | } 201 | int64_t index = key.toInt64(); 202 | if (index < 0) { 203 | return true; 204 | } 205 | if (index > max_index) { 206 | max_index = index; 207 | } 208 | } 209 | 210 | if (max_index >= arr.size() * 2) { 211 | // Might as well store it as a map 212 | return true; 213 | } 214 | 215 | return false; 216 | } 217 | 218 | bson_t encodeToBSON(const Variant& mixture) { 219 | bson_t bson; 220 | bson_init(&bson); 221 | fillBSONWithArray(mixture.toArray(), &bson); 222 | return bson; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/contrib/encode.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2014 Di Máximo Cuadros 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | * 5 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | */ 9 | #include "hphp/runtime/base/base-includes.h" 10 | #include 11 | 12 | namespace HPHP { 13 | bool arrayIsDocument(const Array& arr); 14 | void fillBSONWithArray(const Array& value, bson_t* bson); 15 | void stringToBSON(const String& value, const char* key, bson_t* bson); 16 | void arrayToBSON(const Array& value, const char* key, bson_t* bson); 17 | void objectToBSON(const Object& value, const char* key, bson_t* bson); 18 | void variantToBSON(const Variant& value, const char* key, bson_t* bson); 19 | void int64ToBSON(const int64_t value, const char* key, bson_t* bson); 20 | void boolToBSON(const bool value, const char* key, bson_t* bson); 21 | void nullToBSON(const char* key, bson_t* bson); 22 | void doubleToBSON(const double value,const char* key, bson_t* bson); 23 | void mongoDateToBSON(const Object& value, const char* key, bson_t* bson); 24 | void mongoIdToBSON(const Object& value, const char* key, bson_t* bson); 25 | void mongoRegexToBSON(const Object& value, const char* key, bson_t* bson); 26 | void mongoTimestampToBSON(const Object& value, const char* key, bson_t* bson); 27 | void mongoCodeToBSON(const Object& value, const char* key, bson_t* bson); 28 | void mongoBinDataToBSON(const Object& value, const char* key, bson_t* bson); 29 | void mongoInt32ToBSON(const Object& value, const char* key, bson_t* bson); 30 | void mongoInt64ToBSON(const Object& value, const char* key, bson_t* bson); 31 | void mongoMinKeyToBSON(const char* key, bson_t* bson); 32 | void mongoMaxKeyToBSON(const char* key, bson_t* bson); 33 | bson_t encodeToBSON(const Variant& mixture); 34 | } 35 | -------------------------------------------------------------------------------- /src/encode_draft.cpp: -------------------------------------------------------------------------------- 1 | #include "hphp/runtime/base/base-includes.h" 2 | #include 3 | 4 | namespace HPHP { 5 | void fillBSONWithArray(const Array& value, bson_t* bson) { 6 | for (ArrayIter iter(value); iter; ++iter) { 7 | Variant key(iter.first()); 8 | const Variant& data(iter.secondRef()); 9 | 10 | variantToBSON(data, key.toString().c_str(), bson); 11 | } 12 | } 13 | 14 | void variantToBSON(const Variant& value, const char* key, bson_t* bson) { 15 | switch(value.getType()) { 16 | case KindOfUninit: 17 | case KindOfNull: 18 | nullToBSON(key, bson); 19 | break; 20 | case KindOfBoolean: 21 | boolToBSON(value.toBoolean(), key, bson); 22 | break; 23 | case KindOfInt64: 24 | int64ToBSON(value.toInt64(), key, bson); 25 | break; 26 | case KindOfDouble: 27 | doubleToBSON(value.toDouble(), key, bson); 28 | break; 29 | case KindOfStaticString: 30 | case KindOfString: 31 | stringToBSON(value.toString(), key, bson); 32 | break; 33 | case KindOfArray: 34 | arrayToBSON(value.toArray(), key, bson); 35 | break; 36 | case KindOfObject: 37 | objectToBSON(value.toObject(), key, bson); 38 | break; 39 | default: 40 | break; 41 | } 42 | 43 | 44 | void int32ToBSON(const int32_t value, const char* key, bson_t* bson) { 45 | bson_append_int32(bson, key, -1, value); 46 | } 47 | 48 | void int64TOBSON(const int64_t value, const char* key, bson_t) { 49 | bson_append_int64(bson, key, -1, value); 50 | } 51 | 52 | void boolTOBSON(const bool value, const char* key, bson_t bson) { 53 | bson_append_bool(bson, key, -1, value); 54 | } 55 | 56 | void doubleTOBSON(const double value, const char* key, bson_t bson) { 57 | bson_append_double(bson, key, -1, value); 58 | } 59 | 60 | void stringTOBSON(const String& value, const char* key, bson_t bson) { 61 | bson_append_utf8(bson, key, strlen(key), value.c_str(), -1); 62 | } 63 | 64 | void nullTOBSON(const char* value, const char* key, bson_t bson) { 65 | bson_append_null(bson, key, -1, value); 66 | } 67 | 68 | void documentTOBSON(const int64_t value, const char* key, bson_t bson) { 69 | bson_t child; 70 | bson_append_document_begin(bson, key, -1, &child); 71 | fillBSONWithArray(value, &child); 72 | bson_append_document_end(bson, &child); 73 | } 74 | 75 | void arrayTOBSON(const Array& value, const char* key, bson_t bson) { 76 | bson_t child; 77 | bson_append_array_begin(bson, key, -1, &child); 78 | fillBSONWithArray(value, &child); 79 | bson_append_array_end(bson, &child); 80 | } 81 | 82 | void oidTOBSON(const Object& value, const char* key, bson_t bson) { 83 | bson_oid_t oid; 84 | bson_oid_init_from_string(&oid, value->o_get("$id").toString().c_str()); 85 | bson_append_oid(bson, key, -1, &oid); 86 | } 87 | 88 | void timestampToBSON(const Object& value, const char* key, bson_t* bson) { 89 | bson_append_timestamp(bson, key, -1, value->o_get("sec").toInt64(), value->o_get("inc").toInt64()); 90 | } 91 | 92 | void regexToBSON(const Object& value, const char* key, bson_t* bson) { 93 | bson_append_regex(bson, key, -1, value->o_get("regex").toString().c_str(), value->o_get("flags").toString().c_str()); 94 | } 95 | 96 | void dateToBSON(const Object& value, const char* key, bson_t* bson) { 97 | int64_t mili = 98 | (value->o_get("sec").toInt64() * 1000) + 99 | (value->o_get("usec").toInt64() / 1000); 100 | 101 | bson_append_date_time(bson, key, -1, mili); 102 | } 103 | 104 | void codeToBSON(const Object& value, const char* key, bson_t* bson) { 105 | bson_t child; 106 | bson_init(&child); 107 | fillBSONWithArray( 108 | value->o_get("scope", true, s_MongoCode.get()).toArray(), 109 | &child 110 | ); 111 | 112 | bson_append_code_with_scope(bson, key, -1, 113 | value->o_get("code", true, s_MongoCode.get()).toString().c_str(), 114 | &child 115 | ); 116 | } 117 | 118 | void binDataToBSON(const Object& value, const char* key, bson_t* bson) { 119 | const String& binary = value->o_get("bin").toString(); 120 | 121 | bson_append_binary(bson, key, -1, 122 | (bson_subtype_t) value->o_get("type").toInt32(), 123 | (const uint8_t*) binary.c_str(), 124 | binary.size() 125 | ); 126 | } 127 | 128 | } // end of HPHP namespace 129 | -------------------------------------------------------------------------------- /src/exceptions/MongoConnectionException.php: -------------------------------------------------------------------------------- 1 | invokeFunc(&dummy, \ 23 | cls->getCtor(), \ 24 | make_packed_array(arg), \ 25 | ret.get()); \ 26 | return ret; \ 27 | } \ 28 | \ 29 | private: \ 30 | static void initClass() { \ 31 | cls = Unit::lookupClass(StringData::Make(#CLS)); \ 32 | } \ 33 | \ 34 | static HPHP::Class* cls; \ 35 | }; 36 | 37 | MONGO_DEFINE_CLASS(MongoException) 38 | MONGO_DEFINE_CLASS(MongoConnectionException) 39 | MONGO_DEFINE_CLASS(MongoCursorException) 40 | MONGO_DEFINE_CLASS(MongoCursorTimeoutException) 41 | MONGO_DEFINE_CLASS(MongoDuplicateKeyException) 42 | MONGO_DEFINE_CLASS(MongoExecutionTimeoutException) 43 | MONGO_DEFINE_CLASS(MongoGridFSException) 44 | MONGO_DEFINE_CLASS(MongoProtocolException) 45 | MONGO_DEFINE_CLASS(MongoResultException) 46 | MONGO_DEFINE_CLASS(MongoWriteConcernException) 47 | 48 | MONGO_DEFINE_CLASS(MongoClient) 49 | MONGO_DEFINE_CLASS(MongoCursor) 50 | MONGO_DEFINE_CLASS(MongoCollection) 51 | 52 | #undef MONGO_DEFINE_CLASS 53 | 54 | template 55 | void mongoThrow(const char* message); 56 | 57 | template 58 | void mongoThrow(const char* message) { 59 | std::string msg(message); 60 | throw T::allocObject(msg); 61 | } 62 | 63 | ////////////////////////////////////////////////////////////////////////////// 64 | 65 | class MongoExtension : public Extension { 66 | public: 67 | MongoExtension(); 68 | virtual void moduleInit(); 69 | 70 | private: 71 | void _initMongoClientClass(); 72 | void _initMongoCursorClass(); 73 | void _initMongoCollectionClass(); 74 | void _initBSON(); 75 | }; 76 | 77 | } // namespace HPHP 78 | 79 | #endif // incl_HPHP_EXT_MONGO_H_ 80 | -------------------------------------------------------------------------------- /src/mongo_common.cpp: -------------------------------------------------------------------------------- 1 | #include "mongo_common.h" 2 | #include 3 | 4 | namespace HPHP { 5 | 6 | ////////MongocClient 7 | 8 | //////////////////////////////////////////////////////////////////////////////// 9 | 10 | Resource get_client_resource(Object obj) { 11 | auto res = obj->o_realProp(s_mongoc_client, ObjectData::RealPropUnchecked, s_mongoclient); 12 | 13 | if (!res || !res->isResource()) { 14 | return null_resource; 15 | } 16 | 17 | return res->toResource(); 18 | } 19 | 20 | MongocClient *get_client(Object obj) { 21 | auto res = get_client_resource(obj); 22 | 23 | return res.getTyped(true, false); 24 | } 25 | 26 | MongocClient *MongocClient::GetPersistent(const String& uri) { 27 | return GetCachedImpl("mongo::persistent_clients", uri); 28 | } 29 | 30 | void MongocClient::SetPersistent(const String& uri, MongocClient *client) { 31 | SetCachedImpl("mongo::persistent_clients", uri, client); 32 | } 33 | 34 | MongocClient *MongocClient::GetCachedImpl(const char *name, const String& uri) { 35 | return dynamic_cast(g_persistentResources->get(name, uri.data())); 36 | } 37 | 38 | void MongocClient::SetCachedImpl(const char *name, const String& uri, MongocClient *client) { 39 | g_persistentResources->set(name, uri.data(), client); 40 | } 41 | 42 | MongocClient::MongocClient(const String &uri) { 43 | m_client = mongoc_client_new(uri.c_str()); 44 | } 45 | 46 | MongocClient::~MongocClient() { 47 | if (m_client != nullptr) { 48 | mongoc_client_destroy(m_client); 49 | } 50 | } 51 | 52 | ////////MongocCursor 53 | 54 | //////////////////////////////////////////////////////////////////////////////// 55 | 56 | Resource get_cursor_resource(Object obj) { 57 | auto res = obj->o_realProp(s_mongoc_cursor, ObjectData::RealPropUnchecked, s_mongocursor); 58 | 59 | if (!res || !res->isResource()) { 60 | return null_resource; 61 | } 62 | 63 | return res->toResource(); 64 | } 65 | 66 | MongocCursor *get_cursor(Object obj) { 67 | auto res = get_cursor_resource(obj); 68 | 69 | return res.getTyped(true, false); 70 | } 71 | 72 | MongocCursor::MongocCursor(mongoc_client_t *client, 73 | const char *db_and_collection, 74 | mongoc_query_flags_t flags, 75 | uint32_t skip, 76 | uint32_t limit, 77 | uint32_t batch_size, 78 | const bson_t *query, 79 | const bson_t *fields, 80 | const mongoc_read_prefs_t *read_prefs) { 81 | std::string db_name; 82 | std::string collection_name; 83 | 84 | std::string *db_and_collection_str = new std::string(db_and_collection); 85 | 86 | //namespace format: db.collection 87 | size_t dot_pos; 88 | dot_pos = db_and_collection_str->find_first_of( ".", 0 ); 89 | db_name = db_and_collection_str->substr( 0, dot_pos ); 90 | collection_name = db_and_collection_str->substr( dot_pos+1, std::string::npos ); 91 | 92 | mongoc_collection_t *collection; 93 | 94 | collection = mongoc_client_get_collection (client, db_name.c_str(), collection_name.c_str()); 95 | m_cursor = mongoc_collection_find (collection, 96 | flags, 97 | skip, 98 | limit, 99 | batch_size, 100 | query, 101 | fields, 102 | read_prefs); 103 | } 104 | 105 | MongocCursor::~MongocCursor() { 106 | if (m_cursor != nullptr) { 107 | mongoc_cursor_destroy (m_cursor); 108 | } 109 | } 110 | 111 | } // namespace HPHP 112 | -------------------------------------------------------------------------------- /src/mongo_common.h: -------------------------------------------------------------------------------- 1 | #ifndef incl_HPHP_EXT_MONGO_COMMON_H_ 2 | #define incl_HPHP_EXT_MONGO_COMMON_H_ 3 | 4 | #include "hphp/runtime/base/base-includes.h" 5 | #include "hphp/runtime/base/persistent-resource-store.h" 6 | #include "mongoc.h" 7 | #include "string.h" 8 | 9 | namespace HPHP { 10 | 11 | const StaticString 12 | s_mongoclient("MongoClient"), 13 | s_mongoc_client("__mongoc_client"); 14 | 15 | //////////////////////////////////////////////////////////////////////////////// 16 | 17 | class MongocClient : public SweepableResourceData { 18 | public: 19 | static MongocClient *GetPersistent(const String& uri); 20 | static void SetPersistent(const String& uri, MongocClient *client); 21 | 22 | private: 23 | static MongocClient *GetCachedImpl(const char *name, const String& uri); 24 | static void SetCachedImpl(const char *name, const String& uri, MongocClient *client); 25 | 26 | public: 27 | MongocClient(const String& uri); 28 | ~MongocClient(); 29 | 30 | CLASSNAME_IS("mongoc client") 31 | 32 | // overriding ResourceData 33 | virtual const String& o_getClassNameHook() const { return classnameof(); } 34 | virtual bool isInvalid() const { return m_client == nullptr; } 35 | 36 | mongoc_client_t *get() { return m_client;} 37 | 38 | private: 39 | mongoc_client_t *m_client; 40 | 41 | }; 42 | 43 | MongocClient *get_client(Object obj); 44 | 45 | 46 | 47 | 48 | 49 | const StaticString 50 | s_mongocursor("MongoCursor"), 51 | s_mongoc_cursor("__mongoc_cursor"); 52 | 53 | //////////////////////////////////////////////////////////////////////////////// 54 | 55 | class MongocCursor : public SweepableResourceData { 56 | public: 57 | //Reference: https://github.com/mongodb/mongo-c-driver/blob/e6038636bcee5264a264b54afce0b93c39884d97/src/mongoc/mongoc-cursor.c 58 | MongocCursor(mongoc_client_t *client, 59 | const char *db_and_collection, 60 | mongoc_query_flags_t flags, 61 | uint32_t skip, 62 | uint32_t limit, 63 | uint32_t batch_size, 64 | const bson_t *query, 65 | const bson_t *fields, 66 | const mongoc_read_prefs_t *read_prefs); 67 | ~MongocCursor(); 68 | 69 | CLASSNAME_IS("mongoc cursor") 70 | 71 | // overriding ResourceData 72 | virtual const String& o_getClassNameHook() const { return classnameof(); } 73 | virtual bool isInvalid() const { return m_cursor == nullptr; } 74 | 75 | mongoc_cursor_t *get() { return m_cursor;} 76 | 77 | void set(mongoc_cursor_t *cursor) { 78 | if (cursor != m_cursor) { 79 | mongoc_cursor_destroy(m_cursor); 80 | m_cursor = cursor; 81 | } 82 | } 83 | 84 | private: 85 | mongoc_cursor_t *m_cursor; 86 | 87 | }; 88 | 89 | MongocCursor *get_cursor(Object obj); 90 | 91 | } // namespace HPHP 92 | 93 | #endif // incl_HPHP_EXT_MONGO_COMMON_H_ 94 | -------------------------------------------------------------------------------- /src/types/MongoBinData.php: -------------------------------------------------------------------------------- 1 | bin = $data; 31 | $this->type = $type; 32 | } 33 | 34 | /** 35 | * The string representation of this binary data object. 36 | * 37 | * @return string - Returns the string "Mongo Binary Data". To access 38 | * the contents of a MongoBinData, use the bin field. 39 | */ 40 | public function __toString() 41 | { 42 | return ""; 43 | } 44 | } -------------------------------------------------------------------------------- /src/types/MongoCode.php: -------------------------------------------------------------------------------- 1 | code = $code; 22 | $this->scope = $scope; 23 | } 24 | 25 | public function getScope() 26 | { 27 | return $this->scope; 28 | } 29 | 30 | /** 31 | * Returns this code as a string 32 | * 33 | * @return string - This code, the scope is not returned. 34 | */ 35 | public function __toString() 36 | { 37 | return $this->code; 38 | } 39 | } -------------------------------------------------------------------------------- /src/types/MongoDBRef.php: -------------------------------------------------------------------------------- 1 | $collection, 23 | '$id' => $id 24 | ]; 25 | 26 | if (isset($database)) { 27 | $ref['$db'] = $database; 28 | } 29 | 30 | return $ref; 31 | } 32 | 33 | /** 34 | * Fetches the object pointed to by a reference 35 | * 36 | * @param mongodb $db - Database to use. 37 | * @param array $ref - Reference to fetch. 38 | * 39 | * @return array - Returns the document to which the reference refers 40 | * or NULL if the document does not exist (the reference is broken). 41 | */ 42 | public static function get(MongoDB $db, array $ref) : array 43 | { 44 | if (!isset($ref['$id']) || !isset($ref['$collection'])) { 45 | return; 46 | } 47 | $ns = $ref['$collection']; 48 | $id = $ref['$id']; 49 | 50 | $refdb = null; 51 | if (isset($ref['$db'])) { 52 | $refdb = $ref['$db']; 53 | } 54 | 55 | if (!is_string($ns)) { 56 | throw new MongoException('MongoDBRef::get: $ref field must be a string', 10); 57 | } 58 | if (isset($refdb)) { 59 | if (!is_string($refdb)) { 60 | throw new MongoException('MongoDBRef::get: $db field of $ref must be a string', 11); 61 | } 62 | if ($refdb != (string)$db) { 63 | $db = $db->_getClient()->$refdb; 64 | } 65 | } 66 | $collection = new MongoCollection($db, $ns); 67 | $query = ['_id' => $id]; 68 | return $collection->findOne($query); 69 | } 70 | 71 | /** 72 | * Checks if an array is a database reference 73 | * 74 | * @param array $ref - Array to check. 75 | * 76 | * @return bool 77 | */ 78 | public static function isRef(array $ref) : bool 79 | { 80 | if (isset($ref['$id']) && isset($ref['collection'])) { 81 | return true; 82 | } 83 | 84 | return false; 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /src/types/MongoDate.php: -------------------------------------------------------------------------------- 1 | sec = time(); 31 | } else { 32 | $this->sec = $sec; 33 | } 34 | 35 | $this->usec = $usec; 36 | } 37 | 38 | /** 39 | * Returns a string representation of this date 40 | * 41 | * @return string - This date. 42 | */ 43 | public function __toString() { 44 | return (string) $this->sec . ' ' . $this->usec; 45 | } 46 | } -------------------------------------------------------------------------------- /src/types/MongoId.php: -------------------------------------------------------------------------------- 1 | hostname = self::getHostname(); 46 | if ($id === null) { 47 | $id = $this->generateId(); 48 | } else if (self::isValid($id)) { 49 | $this->disassembleId($id); 50 | } else { 51 | throw new MongoException('Invalid object ID', 19); 52 | } 53 | $this->{'$id'} = $id; 54 | $this->id = $id; 55 | } 56 | 57 | private function generateId() 58 | { 59 | if (null === self::$refInc) { 60 | self::$refInc = (int) mt_rand(0, pow(2, 24)); 61 | } 62 | 63 | $this->timestamp = time(); 64 | $this->inc = self::$refInc++; 65 | $this->pid = getmypid(); 66 | 67 | if ($this->pid > 32768) { 68 | $this->pid = 65536 - $this->pid; 69 | } 70 | 71 | return $this->assembleId(); 72 | } 73 | 74 | private function assembleId() 75 | { 76 | $hash = unpack('a3hash', md5($this->hostname, true))['hash']; 77 | $i1 = ($this->inc) & 255; 78 | $i2 = ($this->inc >> 8) & 255; 79 | $i3 = ($this->inc >> 16) & 255; 80 | $binId = pack( 81 | 'Na3vC3', 82 | $this->timestamp, 83 | $hash, 84 | $this->pid, 85 | $i3, $i2, $i1 86 | ); 87 | 88 | return bin2hex($binId); 89 | } 90 | 91 | private function disassembleId($id) 92 | { 93 | $vars = unpack('Nts/C3m/vpid/C3i', hex2bin($id)); 94 | $this->timestamp = $vars['ts']; 95 | $this->pid = $vars['pid']; 96 | $this->inc = $vars['i3'] | ($vars['i2'] << 8) | ($vars['i1'] << 16); 97 | } 98 | 99 | /** 100 | * Gets the hostname being used for this machine's ids 101 | * 102 | * @return string - Returns the hostname. 103 | */ 104 | public static function getHostname() 105 | { 106 | return gethostname(); 107 | } 108 | 109 | /** 110 | * Gets the number of seconds since the epoch that this id was created 111 | * 112 | * @return int - Returns the number of seconds since the epoch that 113 | * this id was created. There are only four bytes of timestamp stored, 114 | * so MongoDate is a better choice for storing exact or wide-ranging 115 | * times. 116 | */ 117 | public function getTimestamp() 118 | { 119 | return $this->timestamp; 120 | } 121 | 122 | /** 123 | * Gets the process ID 124 | * 125 | * @return int - Returns the PID of the MongoId. 126 | */ 127 | public function getPid() 128 | { 129 | return $this->pid; 130 | } 131 | 132 | /** 133 | * Gets the incremented value to create this id 134 | * 135 | * @return int - Returns the incremented value used to create this 136 | * MongoId. 137 | */ 138 | public function getInc() 139 | { 140 | return $this->inc; 141 | } 142 | 143 | /** 144 | * Check if a value is a valid ObjectId 145 | * 146 | * @param mixed $value - The value to check for validity. 147 | * 148 | * @return bool - Returns TRUE if value is a MongoId instance or a 149 | * string consisting of exactly 24 hexadecimal characters; otherwise, 150 | * FALSE is returned. 151 | */ 152 | public static function isValid($id) 153 | { 154 | return preg_match('/[0-9a-fA-F]{24}/', $id); 155 | } 156 | 157 | /** 158 | * Create a dummy MongoId 159 | * 160 | * @param array $props - Theoretically, an array of properties used to 161 | * create the new id. However, as MongoId instances have no properties, 162 | * this is not used. 163 | * 164 | * @return MongoId - A new id with the value 165 | * "000000000000000000000000". 166 | */ 167 | public static function __set_state($props) 168 | { 169 | $id = new self('000000000000000000000000'); 170 | foreach($props as $propName => $value) { 171 | $id->{$propName} = $value; 172 | } 173 | $id->id = $id->assembleId(); 174 | return $id; 175 | } 176 | 177 | /** 178 | * Returns a hexidecimal representation of this id 179 | * 180 | * @return string - This id. 181 | */ 182 | public function __toString() 183 | { 184 | return (string)$this->id; 185 | } 186 | } -------------------------------------------------------------------------------- /src/types/MongoInt32.php: -------------------------------------------------------------------------------- 1 | value = $value; 21 | } 22 | 23 | /** 24 | * Returns the string representation of this 32-bit integer. 25 | * 26 | * @return string - Returns the string representation of this integer. 27 | */ 28 | public function __toString() 29 | { 30 | return $this->value; 31 | } 32 | } -------------------------------------------------------------------------------- /src/types/MongoInt64.php: -------------------------------------------------------------------------------- 1 | value = $value; 21 | } 22 | 23 | /** 24 | * Returns the string representation of this 64-bit integer. 25 | * 26 | * @return string - Returns the string representation of this integer. 27 | */ 28 | public function __toString() 29 | { 30 | return $this->value; 31 | } 32 | } -------------------------------------------------------------------------------- /src/types/MongoMaxKey.php: -------------------------------------------------------------------------------- 1 | regex = (string)substr($regex, 1, $flagsStart - 1); 32 | $this->flags = (string)substr($regex, $flagsStart + 1); 33 | 34 | if (!$this->regexIsValid($regex)) { 35 | throw new MongoException('invalid regex', MongoException::INVALID_REGEX); 36 | } 37 | } 38 | 39 | public function regexIsValid($regex) 40 | { 41 | return substr_count($regex, '/') >= 2 && 42 | ((strlen($regex) && @preg_match($regex, null) !== false) || strlen($this->flags)); 43 | } 44 | 45 | /** 46 | * A string representation of this regular expression 47 | * 48 | * @return string - This regular expression in the form "/expr/flags". 49 | */ 50 | public function __toString() 51 | { 52 | return '/' . $this->regex . '/' . $this->flags; 53 | } 54 | } -------------------------------------------------------------------------------- /src/types/MongoTimestamp.php: -------------------------------------------------------------------------------- 1 | sec = $sec < 0 ? time() : (int) $sec; 23 | if ($inc < 0) { 24 | $this->inc = self::$globalInc; 25 | self::$globalInc++; 26 | } else { 27 | $this->inc = (int) $inc; 28 | } 29 | } 30 | 31 | /** 32 | * Returns a string representation of this timestamp 33 | * 34 | * @return string - The seconds since epoch represented by this 35 | * timestamp. 36 | */ 37 | public function __toString() { 38 | return (string)$this->sec; 39 | } 40 | } -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DIRNAME=`dirname $0` 4 | REALPATH=`which realpath` 5 | if [ ! -z "${REALPATH}" ]; then 6 | DIRNAME=`realpath ${DIRNAME}` 7 | fi 8 | 9 | #mongoimport --db test --collection cities --file test/cities.json --upsert 10 | mongoimport --db test --collection students --file test/students.json --upsert 11 | #to test the drop method in MongoCollection 12 | mongoimport --db test --collection temp --file test/students.json --upsert 13 | 14 | ${HPHP_HOME}/hphp/hhvm/hhvm \ 15 | -vDynamicExtensions.0=${DIRNAME}/mongo.so \ 16 | `which phpunit` ${DIRNAME}/test 17 | 18 | -------------------------------------------------------------------------------- /test/AutoLoader.php: -------------------------------------------------------------------------------- 1 | isDir() && !$file->isLink() && !$file->isDot()) { 15 | // recurse into directories other than a few special ones 16 | self::registerDirectory($file->getPathname()); 17 | } elseif (substr($file->getFilename(), -4) === '.php') { 18 | // save the class name / path of a .php file found 19 | $className = substr($file->getFilename(), 0, -4); 20 | AutoLoader::registerClass($className, $file->getPathname()); 21 | } 22 | } 23 | } 24 | 25 | public static function registerClass($className, $fileName) { 26 | AutoLoader::$classNames[$className] = $fileName; 27 | } 28 | 29 | public static function loadClass($className) { 30 | if (isset(AutoLoader::$classNames[$className])) { 31 | require_once(AutoLoader::$classNames[$className]); 32 | } 33 | } 34 | 35 | } 36 | 37 | spl_autoload_register(array('AutoLoader', 'loadClass')); 38 | -------------------------------------------------------------------------------- /test/MongoTestCase.php: -------------------------------------------------------------------------------- 1 | getTestDB()->drop(); 14 | } 15 | 16 | protected function tearDown() { 17 | $this->testClient = null; 18 | parent::tearDown(); 19 | } 20 | 21 | public function getTestClient() { 22 | if(!$this->testClient) { 23 | $this->testClient = new MongoClient(); 24 | } 25 | 26 | return $this->testClient; 27 | } 28 | 29 | public function getTestDB() { 30 | return $this->getTestClient()->selectDB(self::TEST_DB); 31 | } 32 | } -------------------------------------------------------------------------------- /test/basicTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(true, extension_loaded("mongo")); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/bootstrap.php: -------------------------------------------------------------------------------- 1 | assertEquals(array("x" => "a"), bson_decode($bson)); 16 | 17 | //echo "\nTesting oid\n"; 18 | $bson = pack('C', 0x07); // byte: oid type 19 | $bson .= pack('a*x', 'id'); // cstring: field name 20 | $bson .= pack('H24', "507f191e810c19729de860ea"); // byte*12: oid value 21 | $bson .= pack('x'); // null byte: document terminator 22 | $bson = pack('V', 4 + strlen($bson)) . $bson; // int32: document length 23 | 24 | $expected = array("id" => new MongoId("507f191e810c19729de860ea")); 25 | $this->assertEquals($expected, bson_decode($bson)); 26 | 27 | //echo "\nTesting nested docs\n"; 28 | $inner_doc = $bson; 29 | $inner_expected = $expected; 30 | $bson = pack('C', 0x03); // byte: document type 31 | $bson .= pack('a*x', 'doc'); // cstring: field name 32 | $bson .= $inner_doc; // an inner document 33 | $bson .= pack('x'); // null byte: document terminator 34 | $bson = pack('V', 4 + strlen($bson)) . $bson; // int32: document length 35 | //assert(bson_decode($bson) == var_dump(array(["x"] => ""))); 36 | $expected = array("doc" => $inner_expected); 37 | $this->assertEquals($expected, bson_decode($bson)); 38 | 39 | /*echo "\nTesting datetime\n"; 40 | $bson = pack('C', 0x09); // byte: datetime type 41 | $bson .= pack('a*x', 'date'); // cstring: field name 42 | $bson .= pack('H16', "0123456789abcde0"); 43 | $bson .= pack('x'); // null byte: document terminator 44 | $bson = pack('V', 4 + strlen($bson)) . $bson; // int32: document length 45 | //assert(bson_decode($bson) == var_dump(array(["x"] => ""))); 46 | $date = new MongoDate(); 47 | printf("Date: %s", $date); 48 | $expected = array("date" => $date); 49 | $this->assertEquals($expected, bson_decode($bson));*/ 50 | 51 | 52 | //echo "\nTesting string type\n"; 53 | $bson = pack('C', 0x02); // byte: string type 54 | $bson .= pack('a*x', 'x'); // cstring: field name 55 | $bson .= pack('V', 0); // int32: string length (invalid) 56 | $bson .= pack('a*x', ''); // cstring: string value 57 | $bson .= pack('x'); // null byte: document terminator 58 | $bson = pack('V', 4 + strlen($bson)) . $bson; // int32: document length 59 | //var_dump(bson_decode($bson)); 60 | 61 | } 62 | 63 | public function testEncodeDecode() { 64 | $a1 = array("hello" => "world"); 65 | //var_dump(bson_decode(bson_encode($a1))); 66 | $this->assertEquals($a1, bson_decode(bson_encode($a1))); 67 | 68 | $id = new MongoId(); 69 | $a2 = array("_id" => $id); 70 | 71 | $en_result = bson_encode($a2); 72 | $result = bson_decode($en_result); 73 | $this->assertEquals($a2, $result); 74 | 75 | $int32 = 2; 76 | $a2["a_int32"] = $int32; 77 | $result = bson_decode(bson_encode($a2)); 78 | $this->assertEquals($a2, $result); 79 | /* 80 | $int64 = new MongoInt64("64"); 81 | $a2["a_int64"] = $int64; 82 | //var_dump(bson_decode(bson_encode($a2["a_int32"]))); 83 | //$this->assertTrue(bson_decode(bson_encode($a2["a_int64"])) == $a2["a_int64"]); 84 | */ 85 | $bool = true; 86 | $a2["boolean"] = $bool; 87 | $this->assertEquals($a2, bson_decode(bson_encode($a2))); 88 | 89 | $null = null; 90 | $a2["null"] = $null; 91 | $this->assertEquals($a2, bson_decode(bson_encode($a2))); 92 | 93 | $double = 3.2; 94 | $a2["double"] = $double; 95 | $this->assertEquals($a2, bson_decode(bson_encode($a2))); 96 | } 97 | 98 | public function testEncodeDecodeNestedDocument() { 99 | $id1 = new MongoId(); 100 | 101 | $bool = true; 102 | $string = "Top level doc"; 103 | $int64 = 9001; 104 | $date = new MongoDate(); 105 | $timestamp = new MongoTimestamp(); 106 | $minkey = new MongoMinKey(); 107 | $maxkey = new MongoMaxKey(); 108 | $bindata = new MongoBinData("1111"); 109 | 110 | $id2 = new MongoId(); 111 | $string2 = "Inner doc"; 112 | $array = [0, 1, 2 , 3]; 113 | 114 | $inner_doc = array("_id" => $id2, "name" => $string2, "list" => $array); 115 | 116 | $this->assertEquals($inner_doc, bson_decode(bson_encode($inner_doc))); 117 | 118 | $out_doc = array("_id" => $id1, 119 | "name" => $string, 120 | "val" => $int64, 121 | "date" => $date, 122 | "timestamp" => $timestamp, 123 | "minkey" => $minkey, 124 | "maxkey" => $maxkey, 125 | "bindata" => $bindata, 126 | "inner_doc" => $inner_doc); 127 | 128 | // var_dump(bson_decode(bson_encode($out_doc))); 129 | 130 | $this->assertEquals($out_doc, bson_decode(bson_encode($out_doc))); 131 | } 132 | 133 | public function testDecodeCorruptException() { 134 | $id1 = new MongoId(); 135 | 136 | $bool = true; 137 | $string = "Top level doc"; 138 | $int64 = 9001; 139 | $date = new MongoDate(); 140 | $timestamp = new MongoTimestamp(); 141 | $minkey = new MongoMinKey(); 142 | $maxkey = new MongoMaxKey(); 143 | $bindata = new MongoBinData("1111"); 144 | 145 | $out_doc = array("_id" => $id1, 146 | "name" => $string, 147 | "val" => $int64, 148 | "date" => $date, 149 | "timestamp" => $timestamp, 150 | "minkey" => $minkey, 151 | "maxkey" => $maxkey, 152 | "bindata" => $bindata); 153 | 154 | $encoded_bson = bson_encode($out_doc); 155 | 156 | $corrupted_bson = substr($encoded_bson, 0, strlen($encoded_bson) - 4); 157 | 158 | $message = "Unexpected end of BSON. Input document is likely corrupted!"; 159 | 160 | $this->setExpectedException('MongoException', $message); 161 | 162 | bson_decode($corrupted_bson); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /test/mongoClientTest.php: -------------------------------------------------------------------------------- 1 | getTestClient(); 7 | //var_dump((string) $cli); 8 | //var_dump($cli->listDBs()); 9 | } 10 | } -------------------------------------------------------------------------------- /test/mongoCollectionTest.php: -------------------------------------------------------------------------------- 1 | toIndexString($keys); 10 | } 11 | } 12 | 13 | class MongoCollectionTest extends MongoTestCase { 14 | 15 | public function testInsertAndRemove() { 16 | $cli = $this->getTestClient(); 17 | $database_name = "test"; 18 | $db = new MongoDB($cli, $database_name); 19 | $coll_name = "students"; 20 | $coll = new MongoCollection($db, $coll_name); 21 | 22 | // in case function never completes, need to remove Dan or will trigger insert error 23 | $coll->remove(array("name"=>"Dan")); 24 | 25 | //Test case for insert and remove 26 | $new_doc = array("_id"=>"123456781234567812345678", "name" => "Dan"); //24-digit _id required 27 | //$this->assertEquals(true, $coll->insert($new_doc)); 28 | $cursor = $coll->find(array("name" => "Dan")); 29 | $cursor->rewind(); 30 | while ($cursor->valid()) 31 | { 32 | $value = $cursor->current(); 33 | //$this->assertEquals("Dan", $value["name"]); 34 | $cursor->next(); 35 | } 36 | //$this->assertEquals(true, $coll->remove(array("name"=>"Dan"))); 37 | $cursor = $coll->find(array("name" => "Dan")); 38 | $cursor->rewind(); 39 | //$this->assertEquals(array(), $cursor->current()); 40 | // //Test case for drop 41 | // $temp_coll = new MongoCollection($db, "temp"); 42 | // //$this->assertEquals(true, $temp_coll->drop()["return"]); 43 | 44 | // //Test case for save 45 | // $new_doc = array("_id"=>"123456791234567912345679", "name" => "Eva"); //24-digit _id required 46 | // //$this->assertEquals(true, $coll->save($new_doc)); 47 | // $cursor = $coll->find(array("name" => "Eva")); 48 | // $cursor->rewind(); 49 | // while ($cursor->valid()) 50 | // { 51 | // $value = $cursor->current(); 52 | // if ((strpos($value["return"], "\"name\" : \"Eva\"")) == FALSE) { 53 | // throw new Exception("MongoCollection save for new document failed."); 54 | // } 55 | // $cursor->next(); 56 | // } 57 | // $new_doc_modified = array("_id"=>"123456791234567912345679", "name" => "Frank"); //24-digit _id required 58 | // $this->assertEquals(true, $coll->save($new_doc_modified)); 59 | // $cursor = $coll->find(array("name" => "Frank")); 60 | // $cursor->rewind(); 61 | // while ($cursor->valid()) 62 | // { 63 | // $value = $cursor->current(); 64 | // if (strpos($value["return"], "\"name\" : \"Frank\"") == FALSE) { 65 | // throw new Exception("MongoCollection save for existing document failed."); 66 | // } 67 | // $cursor->next(); 68 | // } 69 | } 70 | 71 | public function testToIndexString() { 72 | $db = $this->getTestDB(); 73 | $coll_name = "students"; 74 | $coll = $db->selectCollection($coll_name); 75 | 76 | // Testing toIndexString 77 | $c = new MongoCollectionStub(); 78 | $actual = $c->createIndexString("foo"); 79 | $expected = "foo_1"; 80 | $this->assertEquals($expected, $actual); 81 | 82 | $actual = $c->createIndexString(array('name' => 1, 'age' => -1, 'bool' => false)); 83 | $expected = "name_1_age_-1_bool_1"; 84 | $this->assertEquals($expected, $actual); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/mongoCursorTest.php: -------------------------------------------------------------------------------- 1 | getTestClient(); 7 | $database_name = "test.students"; 8 | $cursor = new MongoCursor( $cli, 9 | $database_name); 10 | 11 | $cursor->rewind(); 12 | while ($cursor->valid()) 13 | { 14 | //$key = $cursor->key(); //This should be tested after BSON-PHP decoder is finished 15 | //var_dump($key); 16 | $value = $cursor->current(); 17 | //var_dump($value); 18 | $cursor->next(); 19 | } 20 | } 21 | 22 | public function testStartedIterating() { 23 | $cli = $this->getTestClient(); 24 | $database_name = "test.students"; 25 | $cursor = new MongoCursor( $cli, 26 | $database_name, 27 | array("name" => "Bob"), 28 | array() ); 29 | $cursor->rewind(); 30 | /* TODO: add assertEquals to ensure that the document retrieved is what we want 31 | while ($cursor->valid()) 32 | { 33 | //$key = $cursor->key(); //This should be tested after BSON-PHP decoder is finished 34 | //var_dump($key); 35 | $value = $cursor->current(); 36 | var_dump($value); 37 | $cursor->next(); 38 | } 39 | */ 40 | 41 | //Expect to have an exception since the cursor has started iterating. 42 | $this->setExpectedException('MongoCursorException'); 43 | $cursor->addOption("", ""); 44 | } 45 | 46 | public function testExplain() { 47 | $db = $this->getTestDB(); 48 | $coll = $db->selectCollection("students"); 49 | $cur = $coll->find(); 50 | $cur->rewind(); 51 | $res = $cur->current(); 52 | $cur->next(); 53 | var_dump($cur->explain()); 54 | //var_dump($cur); 55 | //$cur->rewind(); 56 | //$res2 = $cur->current(); 57 | //$this->assertEquals($res, $res2); 58 | 59 | } 60 | 61 | public function testSetFlagOne() { 62 | $cli = $this->getTestClient(); 63 | $database_name = "test.students"; 64 | //$cursor = new MongoCursor( $cli, $database_name); 65 | $cursor = new MongoCursor( $cli, 66 | $database_name, 67 | array("name" => "Bob"), 68 | array() ); 69 | 70 | $cursor->setFlag(1, true); 71 | $cursor->rewind(); 72 | $info = $cursor->info(); 73 | $this->assertEquals(true, $info["flags"][1]); 74 | //var_dump($info["flags"]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/mongoDBTest.php: -------------------------------------------------------------------------------- 1 | getTestClient(); 7 | 8 | //echo "Going to selectDB"; 9 | $db = $this->getTestDB(); 10 | //var_dump($db->getProfilingLevel()); 11 | //$db = new MongoDB($cli, $database_name); 12 | } 13 | 14 | public function testCreateDropCollection() { 15 | $db = $this->getTestDB(); 16 | $coll_name = "hello"; 17 | $coll = $db->createCollection("hello"); 18 | //$this->assertEquals(1, $db_response["ok"]); 19 | 20 | $db_response = $db->dropCollection($coll); 21 | $this->assertEquals(1, $db_response["ok"]); 22 | //$res = $db->dropCollection($coll); 23 | //$res = $db->createCollection("hello"); 24 | } 25 | 26 | public function testGetCollectionNames() { 27 | $cli = $this->getTestClient(); 28 | $new_colls = array("t1","t2","t3"); 29 | $db = $cli->selectDB("testCollectionNames"); 30 | foreach ($new_colls as $coll) { 31 | // $db->createCollection($coll); 32 | } 33 | 34 | //$res = $db->getCollectionNames(); 35 | //$this->assertEquals($new_colls, $res); 36 | } 37 | } -------------------------------------------------------------------------------- /test/mongoDateTest.php: -------------------------------------------------------------------------------- 1 | getServerVersion()); 5 | -------------------------------------------------------------------------------- /test/students.json: -------------------------------------------------------------------------------- 1 | {"name": "Alice", "subjects": ["Computer Science", "Statistics", "English", "Physics"], "GPA": 4.0, "_id": "1"} 2 | {"name": "Bob", "subjects": ["History", "Biology", "Mathematics"], "GPA": 3.6, "_id": "2"} 3 | {"name": "Charlie", "subjects": ["Anthropology", "Computer Science", "Physics", "East Asian Studies"], "GPA": 3.2, "_id": "3"} -------------------------------------------------------------------------------- /test/test_collection.json: -------------------------------------------------------------------------------- 1 | { "_id" : "1", "test_field" : "1", "num" : "1" } 2 | { "_id" : "2", "test_field" : "1", "num" : "2" } 3 | { "_id" : "3", "test_field" : "1", "num" : "3" } --------------------------------------------------------------------------------