diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fe073d6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,48 @@ +language: php + +php: + - 5.6 + - 7.0 + +env: + global: + - PLUGIN_NAME=MongoDBLib + - DATASOURCE_NAME=MongodbSource + - REQUIRE="" + + matrix: + - CAKE_VERSION=2.8 + - CAKE_VERSION=2.9 + +addons: + apt: + sources: + - mongodb-upstart + - mongodb-3.2-precise + packages: + - mongodb-org-server + - mongodb-org-shell + +matrix: + include: + - php: 5.6 + env: + - CAKE_VERSION=2.8 + - CODECOVERAGE=1 + - php: 5.6 + env: + - PHPCS=1 + +before_script: + - if [[ $TRAVIS_PHP_VERSION =~ 5.6 ]] ; then echo yes | pecl install mongodb; fi; + - echo "extension=mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - git clone -b master https://github.com/FriendsOfCake/travis.git --depth 1 ../travis + - travis_wait ../travis/before_script.sh + - travis_retry composer install + - echo "require APP . 'vendor' . DS . 'autoload.php';" >> ../cakephp/app/Config/bootstrap.php + +script: + - if [ -d ../cakephp/app ]; then cd ../cakephp/app; fi; ./Console/cake test $PLUGIN_NAME Datasource/$DATASOURCE_NAME + +notifications: + email: false diff --git a/models/behaviors/schemaless.php b/Model/Behavior/SchemalessBehavior.php similarity index 93% rename from models/behaviors/schemaless.php rename to Model/Behavior/SchemalessBehavior.php index 03fb696..b721eb0 100644 --- a/models/behaviors/schemaless.php +++ b/Model/Behavior/SchemalessBehavior.php @@ -68,7 +68,7 @@ class SchemalessBehavior extends ModelBehavior { * @return void * @access public */ - public function setup(&$Model, $config = array()) { + public function setup(Model $Model, $config = array()) { //$this->settings[$Model->alias] = array_merge($this->_defaultSettings, $config); } @@ -81,7 +81,7 @@ public function setup(&$Model, $config = array()) { * @return void * @access public */ - public function beforeSave(&$Model) { + public function beforeSave(Model $Model, $options = array()) { $Model->cacheSources = false; $Model->schema(true); return true; diff --git a/models/behaviors/sql_compatible.php b/Model/Behavior/SqlCompatibleBehavior.php similarity index 84% rename from models/behaviors/sql_compatible.php rename to Model/Behavior/SqlCompatibleBehavior.php index 61e23ec..99f5433 100644 --- a/models/behaviors/sql_compatible.php +++ b/Model/Behavior/SqlCompatibleBehavior.php @@ -78,7 +78,7 @@ class SqlCompatibleBehavior extends ModelBehavior { * @return void * @access public */ - public function setup(&$Model, $config = array()) { + public function setup(Model $Model, $config = array()) { $this->settings[$Model->alias] = array_merge($this->_defaultSettings, $config); } @@ -91,7 +91,7 @@ public function setup(&$Model, $config = array()) { * @return void * @access public */ - public function afterFind(&$Model, $results, $primary) { + public function afterFind(Model $Model, $results, $primary=false) { if ($this->settings[$Model->alias]['convertDates']) { $this->convertDates($results); } @@ -108,11 +108,14 @@ public function afterFind(&$Model, $results, $primary) { * @return void * @access public */ - public function beforeFind(&$Model, $query) { + public function beforeFind(Model $Model, $query) { + if (is_array($query['order'])) { + $this->_translateOrders($Model, $query['order']); + } if (is_array($query['conditions']) && $this->_translateConditions($Model, $query['conditions'])) { return $query; } - return true; + return $query; } /** @@ -120,18 +123,40 @@ public function beforeFind(&$Model, $query) { * * @param mixed $results * @return void - * @access public + * @access protected */ - public function convertDates(&$results) { + protected function convertDates(&$results) { if (is_array($results)) { foreach($results as &$row) { $this->convertDates($row); } - } elseif (is_a($results, 'MongoDate')) { - $results = date('Y-M-d h:i:s', $results->sec); + } elseif (is_a($results, 'MongoDB\BSON\UTCDateTime')) { + $results = date('Y-M-d h:i:s', $results->toDateTime()->getTimestamp()); + } + } + + +/** + * translateOrders method + * change order syntax from SQL style to Mongo style + * + * @param mixed $Model + * @param mixed $orders + * @return void + * @access protected + */ + protected function _translateOrders(Model &$Model, &$orders) { + if(!empty($orders[0])) { + foreach($orders[0] as $key => $val) { + if(preg_match('/^(.+) (ASC|DESC)$/i', $val, $match)) { + $orders[0][$match[1]] = $match[2]; + unset($orders[0][$key]); + } + } } } + /** * translateConditions method * @@ -142,7 +167,7 @@ public function convertDates(&$results) { * @return void * @access protected */ - protected function _translateConditions(&$Model, &$conditions) { + protected function _translateConditions(Model &$Model, &$conditions) { $return = false; foreach($conditions as $key => &$value) { $uKey = strtoupper($key); @@ -261,7 +286,7 @@ protected function _translateConditions(&$Model, &$conditions) { * @return string * @access protected */ - protected function _translateOperator($Model, $operator) { + protected function _translateOperator(Model $Model, $operator) { if (!empty($this->settings[$Model->alias]['operators'][$operator])) { return $this->settings[$Model->alias]['operators'][$operator]; } diff --git a/models/datasources/mongodb_source.php b/Model/Datasource/MongodbSource.php similarity index 63% rename from models/datasources/mongodb_source.php rename to Model/Datasource/MongodbSource.php index bf03e29..846676c 100644 --- a/models/datasources/mongodb_source.php +++ b/Model/Datasource/MongodbSource.php @@ -2,10 +2,11 @@ /** * A CakePHP datasource for the mongoDB (http://www.mongodb.org/) document-oriented database. * - * This datasource uses Pecl Mongo (http://php.net/mongo) - * and is thus dependent on PHP 5.0 and greater. + * This datasource uses the new MongoDB Driver for PHP 5.6+ and, more importantly PHP 7.0 + * * * Original implementation by ichikaway(Yasushi Ichikawa) http://github.com/ichikaway/ + * Updated by elricho (Richard Uren) http://github/elricho * * Reference: * Nate Abele's lithium mongoDB datasource (http://li3.rad-dev.org/) @@ -13,7 +14,7 @@ * * Copyright 2010, Yasushi Ichikawa http://github.com/ichikaway/ * - * Contributors: Predominant, Jrbasso, tkyk, AD7six + * Contributors: Predominant, Jrbasso, tkyk, AD7six, elricho * * Licensed under The MIT License * Redistributions of files must retain the above copyright notice. @@ -25,6 +26,7 @@ */ App::import('Datasource', 'DboSource'); +App::import('Utility', 'CakeText'); /** * MongoDB Source @@ -60,7 +62,7 @@ class MongodbSource extends DboSource { * @var string * @access protected */ - protected $_driverVersion = Mongo::VERSION; + protected $_driverVersion = MONGODB_VERSION; /** * startTime property @@ -72,6 +74,14 @@ class MongodbSource extends DboSource { */ protected $_startTime = null; +/** + * Set fancy options for find queries .. timeouts, tailable cusrors etc ... + * + * @var string + * @access protected + **/ + protected $_findOptions = array(); + /** * Base Config * @@ -91,7 +101,27 @@ class MongodbSource extends DboSource { 'port' => '27017', 'login' => '', 'password' => '', - 'replicaset' => '', + 'replicaset' => '' + ); + +/** + * collection options + * + * set collection options for various mongo write operations. + * options can be found in the php manual + * http://www.php.net/manual/en/mongocollection.save.php + * http://www.php.net/manual/en/mongocollection.insert.php + * http://www.php.net/manual/en/mongocollection.batchinsert.php + * http://www.php.net/manual/en/mongocollection.update.php + * + * @var array + */ + + public $collectionOptions = array( + 'save' => array(), + 'insert' => array(), + 'batchInsert' => array(), + 'update' => array() ); /** @@ -123,6 +153,21 @@ class MongodbSource extends DboSource { 'modified' => array('type' => 'datetime', 'default' => null) ); +/** + * Typemap used for most all find requests + * This best emulates the legacy query capabilities the plugin previously provided + * + * @var array + * @access protected + */ + protected $typeMap = array( + 'typeMap' => array( + 'root' => 'array', + 'document' => 'array', + 'array' => 'array' + ) + ); + /** * construct method * @@ -163,45 +208,25 @@ public function commit() { /** * Connect to the database * - * If using 1.0.2 or above use the mongodb:// format to connect - * The connect syntax changed in version 1.0.2 - so check for that too - * - * If authentication information in present then authenticate the connection - * * @return boolean Connected * @access public */ public function connect() { $this->connected = false; - try{ + try { + $connectionString = $this->createConnectionString($this->config); - $host = $this->createConnectionName($this->config, $this->_driverVersion); - - if (isset($this->config['replicaset']) && count($this->config['replicaset']) === 2) { - $this->connection = new Mongo($this->config['replicaset']['host'], $this->config['replicaset']['options']); - if (isset($this->config['slaveok'])) { - $this->connection->setSlaveOkay($this->config['slaveok']); - } - } else if ($this->_driverVersion >= '1.2.0') { - $this->connection = new Mongo($host, array("persist" => $this->config['persistent'])); + if (isset($this->config['replicaset']['host'])) { + $this->connection = new MongoDB\Client($this->config['replicaset']['host'], $this->config['replicaset']['options'], $this->typeMap); } else { - $this->connection = new Mongo($host, true, $this->config['persistent']); + $this->connection = new MongoDB\Client($connectionString, array(), $this->typeMap); } - - if ($this->_db = $this->connection->selectDB($this->config['database'])) { - if (!empty($this->config['login']) && $this->_driverVersion < '1.2.0') { - $return = $this->_db->authenticate($this->config['login'], $this->config['password']); - if (!$return || !$return['ok']) { - trigger_error('MongodbSource::connect ' . $return['errmsg']); - return false; - } - } + if ($this->_db = $this->connection->selectDatabase($this->config['database'])) { $this->connected = true; } - - } catch(MongoException $e) { + } catch(MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); } @@ -209,30 +234,22 @@ public function connect() { } /** - * create connection name. + * Create connection string * * @param array $config - * @param string $version version of MongoDriver + * @string connection string */ - public function createConnectionName($config, $version) { - $host = null; - - if ($version >= '1.0.2') { - $host = "mongodb://"; - } else { - $host = ''; - } - $hostname = $config['host'] . ':' . $config['port']; - - if(!empty($config['login'])){ - $host .= $config['login'] .':'. $config['password'] . '@' . $hostname . '/'. $config['database']; - } else { - $host .= $hostname; - } + public function createConnectionString($config) { + $host = "mongodb://"; + $hostname = $config['host'] . ':' . $config['port']; - return $host; + if (! empty($config['login'])) { + $host .= $config['login'] .':'. $config['password'] . '@' . $hostname . '/'. $config['database']; + } else { + $host .= $hostname; } - + return $host; + } /** * Inserts multiple values into a table @@ -243,8 +260,6 @@ public function createConnectionName($config, $version) { * @access public */ public function insertMulti($table, $fields, $values) { - $table = $this->fullTableName($table); - if (!is_array($fields) || !is_array($values)) { return false; } @@ -255,18 +270,20 @@ public function insertMulti($table, $fields, $values) { } $data[] = array_combine($fields, $row); } - $this->_prepareLogQuery($table); // just sets a timer - try{ - $return = $this->_db - ->selectCollection($table) - ->batchInsert($data, array('safe' => true)); - } catch (MongoException $e) { + $this->_prepareLogQuery($Model->table); // just sets a timer + $params = array_merge($this->collectionOptions['batchInsert']); + try { + $collection = $this->_db + ->selectCollection($Model->table) + ->insertMany($data, $params); + } catch (MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); } if ($this->fullDebug) { - $this->logQuery("db.{$table}.insertMulti( :data , array('safe' => true))", compact('data')); + $this->logQuery("db.{$Model->table}.insertMulti( :data , :params )", compact('data','params')); } + return $return; } /** @@ -305,10 +322,7 @@ public function getMongoCollection(&$Model) { if ($this->connected === false) { return false; } - - $collection = $this->_db - ->selectCollection($Model->table); - return $collection; + return $this->_db->selectCollection($Model->table); } /** @@ -347,36 +361,50 @@ public function close() { * @access public */ public function disconnect() { - if ($this->connected) { - $this->connected = !$this->connection->close(); + if ($this->connected !== false) { + $this->connected = false; unset($this->_db, $this->connection); - return !$this->connected; } return true; } /** - * Get list of available Collections + * Set special options for the find command + * Options are in 'key' => 'value' format. Anything from the following URL should be fine : + * https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-find/ + * + * @return void + * @access public + */ + public function setFindOptions($options) { + $this->_findOptions = $options; + } + +/** + * Set typeMap + * See this URL for a typemap discussion + * https://docs.mongodb.com/php-library/master/reference/bson/#type-maps * + * @return void + * @access public + */ + public function setTypeMap($typeMap) { + $this->typeMap = $typeMap; + } + +/** + * Get list of available Collections + * Mongodb can create collections on the fly, so return true if connected. + * * @param array $data * @return array Collections * @access public */ public function listSources($data = null) { - if (!$this->isConnected()) { + if (! $this->isConnected()) { return false; } - - $collections = $this->_db->listCollections(); - $sources = array(); - - if(!empty($collections)){ - foreach($collections as $collection){ - $sources[] = $collection->getName(); - } - } - - return $sources; + return true; } /** @@ -390,15 +418,15 @@ public function listSources($data = null) { * @return array if model instance has mongoSchema, return it. * @access public */ - public function describe(&$Model, $field = null) { + public function describe($Model) { $Model->primaryKey = '_id'; $schema = array(); - if (!empty($Model->mongoSchema) && is_array($Model->mongoSchema)) { + if (! empty($Model->mongoSchema) && is_array($Model->mongoSchema)) { $schema = $Model->mongoSchema; return $schema + $this->_defaultSchema; } elseif ($this->isConnected() && is_a($Model, 'Model') && !empty($Model->Behaviors)) { - $Model->Behaviors->attach('Mongodb.Schemaless'); - if (!$Model->data) { + $Model->Behaviors->attach('MongoDBLib.Schemaless'); + if (! $Model->data) { if ($this->_db->selectCollection($Model->table)->count()) { return $this->deriveSchemaFromData($Model, $this->_db->selectCollection($Model->table)->findOne()); } @@ -426,7 +454,7 @@ public function begin() { * @return array * @access public */ - public function calculate(&$Model) { + public function calculate(Model $Model, $func, $params = array()) { return array('count' => true); } @@ -451,8 +479,8 @@ public function name($name) { * @return boolean Insert result * @access public */ - public function create(&$Model, $fields = null, $values = null) { - if (!$this->isConnected()) { + public function create(Model $Model, $fields = null, $values = null) { + if (! $this->isConnected()) { return false; } @@ -461,28 +489,29 @@ public function create(&$Model, $fields = null, $values = null) { } else { $data = $Model->data; } - if (!empty($data['_id'])) { + if (! empty($data['_id'])) { $this->_convertId($data['_id']); } $this->_prepareLogQuery($Model); // just sets a timer - try{ - $return = $this->_db + $params = $this->collectionOptions['insert']; + try { + $this->lastResult = $this->_db ->selectCollection($Model->table) - ->insert($data, true); - } catch (MongoException $e) { + ->insertOne($data, $params); + $return = true; + } catch (MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); } if ($this->fullDebug) { - $this->logQuery("db.{$Model->useTable}.insert( :data , true)", compact('data')); + $this->logQuery("db.{$Model->table}.insert( :data , :params )", compact('data','params')); } - if (!empty($return) && $return['ok']) { - - $id = $data['_id']; - if($this->config['set_string_id'] && is_object($data['_id'])) { - $id = $data['_id']->__toString(); + if (! empty($return)) { + $id = $this->lastResult->getInsertedId(); + if ($this->config['set_string_id'] && is_object($id)) { + $id = $this->lastResult->getInsertedId()->__toString(); } $Model->setInsertID($id); $Model->id = $id; @@ -515,7 +544,7 @@ public function createSchema($schema, $tableName = null) { * @return void * @access public */ - public function dropSchema($schema, $tableName = null) { + public function dropSchema(CakeSchema $schema, $tableName = null) { if (!$this->isConnected()) { return false; } @@ -555,7 +584,7 @@ public function dropSchema($schema, $tableName = null) { * @access public */ public function distinct(&$Model, $keys = array(), $params = array()) { - if (!$this->isConnected()) { + if (! $this->isConnected()) { return false; } @@ -564,25 +593,27 @@ public function distinct(&$Model, $keys = array(), $params = array()) { if (array_key_exists('conditions', $params)) { $params = $params['conditions']; } - try{ + try { $return = $this->_db ->selectCollection($Model->table) ->distinct($keys, $params); - } catch (MongoException $e) { + } catch (MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); } if ($this->fullDebug) { - $this->logQuery("db.{$Model->useTable}.distinct( :keys, :params )", compact('keys', 'params')); + $this->logQuery("db.{$Model->table}.distinct( :keys, :params )", compact('keys', 'params')); } return $return; } - /** * group method * + * Note : https://docs.mongodb.com/php-library/master/upgrade/#old-and-new-methods + * As of 2016-10-28 Mongo advises "Not yet implemented. See PHPLIB-177. Use MongoDB\Database::command. + * * @param mixed $Model * @param array $params array() * Set params same as MongoCollection::group() @@ -600,32 +631,40 @@ public function distinct(&$Model, $keys = array(), $params = array()) { * @return void * @access public */ - public function group(&$Model, $params = array()) { - - if (!$this->isConnected() || count($params) === 0 ) { + public function group($params, Model $Model = null) { + if (! $this->isConnected() || count($params) === 0 ) { return false; } - $this->_prepareLogQuery($Model); // just sets a timer - - $key = (empty($params['key'])) ? array() : $params['key']; - $initial = (empty($params['initial'])) ? array() : $params['initial']; - $reduce = (empty($params['reduce'])) ? array() : $params['reduce']; - $options = (empty($params['options'])) ? array() : $params['options']; - - try{ - $return = $this->_db - ->selectCollection($Model->table) - ->group($key, $initial, $reduce, $options); - } catch (MongoException $e) { + $this->_prepareLogQuery($Model); + $key = empty($params['key']) ? array() : $params['key']; + $initial = empty($params['initial']) ? array() : $params['initial']; + $reduce = empty($params['reduce']) ? array() : $params['reduce']; + $cond = empty($params['conditions']) ? array() : $params['conditions']; + $options = empty($params['options']) ? array() : $params['options']; + + try { + $tmp = $this->_db + ->command( + array( + 'group' => array( + 'ns' => $Model->table, + 'key' => $key, + 'initial' => $initial, + 'cond' => $cond, + '$reduce' => $reduce + ) + ), + $options + ); + $return = $tmp->toArray()[0]; + } catch (MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); } if ($this->fullDebug) { - $this->logQuery("db.{$Model->useTable}.group( :key, :initial, :reduce, :options )", $params); + $this->logQuery("db.{$Model->table}.group( :key, :initial, :reduce, :options )", $params); } - - return $return; } @@ -640,22 +679,22 @@ public function group(&$Model, $params = array()) { * @access public */ public function ensureIndex(&$Model, $keys = array(), $params = array()) { - if (!$this->isConnected()) { + if (! $this->isConnected()) { return false; } $this->_prepareLogQuery($Model); // just sets a timer - try{ + try { $return = $this->_db ->selectCollection($Model->table) - ->ensureIndex($keys, $params); - } catch (MongoException $e) { + ->createIndex($keys, $params); + } catch (MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); } if ($this->fullDebug) { - $this->logQuery("db.{$Model->useTable}.ensureIndex( :keys, :params )", compact('keys', 'params')); + $this->logQuery("db.{$Model->table}.ensureIndex( :keys, :params )", compact('keys', 'params')); } return $return; @@ -681,15 +720,14 @@ public function ensureIndex(&$Model, $keys = array(), $params = array()) { * @return boolean Update result * @access public */ - public function update(&$Model, $fields = null, $values = null, $conditions = null) { - - if (!$this->isConnected()) { + public function update(Model $Model, $fields = null, $values = null, $conditions = null) { + if (! $this->isConnected()) { return false; } if ($fields !== null && $values !== null) { $data = array_combine($fields, $values); - } elseif($fields !== null && $conditions !== null) { + } elseif ($fields !== null && $conditions !== null) { return $this->updateAll($Model, $fields, $conditions); } else{ $data = $Model->data; @@ -700,49 +738,58 @@ public function update(&$Model, $fields = null, $values = null, $conditions = nu } $this->_convertId($data['_id']); - try{ + try { $mongoCollectionObj = $this->_db ->selectCollection($Model->table); - } catch (MongoException $e) { + } catch (MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); return false; } $this->_prepareLogQuery($Model); // just sets a timer - if (!empty($data['_id'])) { + $return = false; + if (! empty($data['_id'])) { $this->_convertId($data['_id']); $cond = array('_id' => $data['_id']); unset($data['_id']); $data = $this->setMongoUpdateOperator($Model, $data); - - try{ - $return = $mongoCollectionObj->update($cond, $data, array("multiple" => false)); - } catch (MongoException $e) { + $params = $this->collectionOptions['update']; + try { + if ($Model->mongoNoSetOperator === true) { + $this->lastResult = $mongoCollectionObj->replaceOne($cond, $data, $params); + } else { + $this->lastResult = $mongoCollectionObj->updateOne($cond, $data, $params); + } + $return = true; + } catch (MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); } if ($this->fullDebug) { - $this->logQuery("db.{$Model->useTable}.update( :conditions, :data, :params )", - array('conditions' => $cond, 'data' => $data, 'params' => array("multiple" => false)) + $this->logQuery("db.{$Model->table}.update( :conditions, :data, :params )", + array('conditions' => $cond, 'data' => $data, 'params' => $params) ); } } else { - try{ - $return = $mongoCollectionObj->save($data); - } catch (MongoException $e) { + // Not sure this block ever executes. + // If $data['_id'] is empty does the Model call $this->create() instead ?? + $params = $this->collectionOptions['save']; + try { + $this->lastResult = $mongoCollectionObj->insertOne($data, $params); + $return = true; + } catch (MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); } if ($this->fullDebug) { - $this->logQuery("db.{$Model->useTable}.save( :data )", compact('data')); + $this->logQuery("db.{$Model->useTable}.save( :data, :params )", compact('data', 'params')); } } return $return; } - /** * setMongoUpdateOperator * @@ -755,15 +802,15 @@ public function update(&$Model, $fields = null, $values = null, $conditions = nu * @access public */ public function setMongoUpdateOperator(&$Model, $data) { - if(isset($data['updated'])) { + if (isset($data['updated'])) { $updateField = 'updated'; } else { - $updateField = 'modified'; + $updateField = 'modified'; } //setting Mongo operator - if(empty($Model->mongoNoSetOperator)) { - if(!preg_grep('/^\$/', array_keys($data))) { + if (empty($Model->mongoNoSetOperator)) { + if (! preg_grep('/^\$/', array_keys($data))) { $data = array('$set' => $data); } else { if(!empty($data[$updateField])) { @@ -772,22 +819,20 @@ public function setMongoUpdateOperator(&$Model, $data) { $data['$set'] = array($updateField => $modified); } } - } elseif(substr($Model->mongoNoSetOperator,0,1) === '$') { - if(!empty($data[$updateField])) { + } elseif (substr($Model->mongoNoSetOperator,0,1) === '$') { + if (! empty($data[$updateField])) { $modified = $data[$updateField]; unset($data[$updateField]); $data = array($Model->mongoNoSetOperator => $data, '$set' => array($updateField => $modified)); } else { $data = array($Model->mongoNoSetOperator => $data); - } } - return $data; } /** - * Update multiple Record + * Update multiple documents * * @param Model $Model Model Instance * @param array $fields Field data @@ -796,31 +841,30 @@ public function setMongoUpdateOperator(&$Model, $data) { * @access public */ public function updateAll(&$Model, $fields = null, $conditions = null) { - if (!$this->isConnected()) { + if (! $this->isConnected()) { return false; } $this->_stripAlias($conditions, $Model->alias); $this->_stripAlias($fields, $Model->alias, false, 'value'); - $fields = $this->setMongoUpdateOperator($Model, $fields); - - $this->_prepareLogQuery($Model); // just sets a timer - try{ - $return = $this->_db + $this->_prepareLogQuery($Model); + + try { + $this->lastResult = $this->_db ->selectCollection($Model->table) - ->update($conditions, $fields, array("multiple" => true)); - } catch (MongoException $e) { + ->updateMany($conditions, $fields); + } catch (MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); } if ($this->fullDebug) { - $this->logQuery("db.{$Model->useTable}.update( :conditions, :fields, :params )", - array('conditions' => $conditions, 'fields' => $fields, 'params' => array("multiple" => true)) + $this->logQuery("db.{$Model->table}.update( :conditions, :fields, :params )", + array('conditions' => $conditions, 'fields' => $fields, 'params' => $this->collectionOptions['update']) ); } - return $return; + return ! empty($this->lastResult); } /** @@ -832,7 +876,7 @@ public function updateAll(&$Model, $fields = null, $conditions = null) { * @access public */ public function deriveSchemaFromData($Model, $data = array()) { - if (!$data) { + if (! $data) { $data = $Model->data; if ($data && array_key_exists($Model->alias, $data)) { $data = $data[$Model->alias]; @@ -878,50 +922,47 @@ public function deriveSchemaFromData($Model, $data = array()) { * @return boolean Update result * @access public */ - public function delete(&$Model, $conditions = null) { - if (!$this->isConnected()) { + public function delete(Model $Model, $conditions = null) { + if (! $this->isConnected()) { return false; } $id = null; - $this->_stripAlias($conditions, $Model->alias); if ($conditions === true) { $conditions = array(); } elseif (empty($conditions)) { $id = $Model->id; - } elseif (!empty($conditions) && !is_array($conditions)) { + } elseif (! empty($conditions) && !is_array($conditions)) { $id = $conditions; $conditions = array(); } - $mongoCollectionObj = $this->_db - ->selectCollection($Model->table); - $this->_stripAlias($conditions, $Model->alias); - if (!empty($id)) { + if (! empty($id)) { $conditions['_id'] = $id; } - if (!empty($conditions['_id'])) { + if (! empty($conditions['_id'])) { $this->_convertId($conditions['_id'], true); } $return = false; - $r = false; - try{ - $this->_prepareLogQuery($Model); // just sets a timer - $return = $mongoCollectionObj->remove($conditions); - if ($this->fullDebug) { - $this->logQuery("db.{$Model->useTable}.remove( :conditions )", - compact('conditions') - ); - } + $count = 0; + try { + $this->_prepareLogQuery($Model); + $this->lastResult = $this->_db + ->selectCollection($Model->table) + ->deleteMany($conditions); + $count = $this->lastResult->getDeletedCount(); $return = true; - } catch (MongoException $e) { + } catch (MongoDB\Driver\Exception\Exception $e) { $this->error = $e->getMessage(); trigger_error($this->error); } + if ($this->fullDebug) { + $this->logQuery("db.{$Model->table}.remove( :conditions )", compact('conditions', 'count')); + } return $return; } @@ -935,33 +976,47 @@ public function delete(&$Model, $conditions = null) { * @return array Results * @access public */ - public function read(&$Model, $query = array()) { - if (!$this->isConnected()) { + public function read(Model $Model, $query = array(), $recursive = null) { + if (! $this->isConnected()) { return false; } $this->_setEmptyValues($query); extract($query); - if (!empty($order[0])) { + if (! empty($order[0])) { $order = array_shift($order); } $this->_stripAlias($conditions, $Model->alias); $this->_stripAlias($fields, $Model->alias, false, 'value'); $this->_stripAlias($order, $Model->alias, false, 'both'); - if (!empty($conditions['_id'])) { + if (! empty($conditions['_id'])) { $this->_convertId($conditions['_id']); } $fields = (is_array($fields)) ? $fields : array($fields => 1); + // Check for string keys in $fields array. + // New mongodb driver not happy using field names as array values for projection, + // it wants field names as keys eg. array(field1 => 1 , field2 => 1, field3 => 1) + // So clean that up here. + if (count(array_filter(array_keys($fields), 'is_string')) == 0) { + // No string keys found .. assuming sequential array + $tmp = array(); + foreach($fields as $field) { + $tmp[$field] = 1; + } + $fields = $tmp; + } + if ($conditions === true) { $conditions = array(); } elseif (!is_array($conditions)) { $conditions = array($conditions); } - $order = (is_array($order)) ? $order : array($order); + // TODO : Janky ! Rework. + $order = (is_array($order)) ? $order : array($order); if (is_array($order)) { foreach($order as $field => &$dir) { if (is_numeric($field) || is_null($dir)) { @@ -979,81 +1034,128 @@ public function read(&$Model, $query = array()) { } } - if (empty($offset) && $page && $limit) { + if (empty($offset) && $page && $limit) $offset = ($page - 1) * $limit; - } $return = array(); + $this->_prepareLogQuery($Model); - $this->_prepareLogQuery($Model); // just sets a timer - if (empty($modify)) { - if ($Model->findQueryType === 'count' && $fields == array('count' => true)) { + $queryType = isset($Model->findQueryType) ? $Model->findQueryType : 'all'; + + if ($queryType === 'count') { + try { $count = $this->_db ->selectCollection($Model->table) ->count($conditions); - if ($this->fullDebug) { - $this->logQuery("db.{$Model->useTable}.count( :conditions )", - compact('conditions', 'count') - ); + } catch (MongoDB\Driver\Exception\Exception $e) { + $this->error = $e->getMessage(); + trigger_error($this->error); + } + + if ($this->fullDebug) + $this->logQuery("db.{$Model->useTable}.count( :conditions )", compact('conditions', 'count')); + + return array(array($Model->alias => array('count' => $count))); + } + + if ($queryType === 'all' || $queryType === 'first') { + $options = array( + 'projection' => $fields, + 'sort' => $order, + 'limit' => $limit, + 'skip' => $offset + ); + if (! empty($this->_findOptions)) { + $options = array_merge($options, $this->_findOptions); + } + + $count = 0; + try { + $cursor = $this->_db + ->selectCollection($Model->table) + ->find($conditions, $options); + + // Iterate over cursor + foreach($cursor as $mongodata) { + if ($this->config['set_string_id'] && ! empty($mongodata['_id']) && is_object($mongodata['_id'])) { + $mongodata['_id'] = $mongodata['_id']->__toString(); + } + $return[][$Model->alias] = $mongodata; + $count++; } - return array(array($Model->alias => array('count' => $count))); + } catch (MongoDB\Driver\Exception\Exception $e) { + $this->error = $e->getMessage(); + trigger_error($this->error); } - $return = $this->_db - ->selectCollection($Model->table) - ->find($conditions, $fields) - ->sort($order) - ->limit($limit) - ->skip($offset); if ($this->fullDebug) { - $count = $return->count(true); - $this->logQuery("db.{$Model->useTable}.find( :conditions, :fields ).sort( :order ).limit( :limit ).skip( :offset )", - compact('conditions', 'fields', 'order', 'limit', 'offset', 'count') - ); + $this->logQuery("db.{$Model->table}.find( :conditions, :options )", compact('conditions', 'options', 'count')); } - } else { - $options = array_filter(array( - 'findandmodify' => $Model->table, - 'query' => $conditions, + + return $return; + } + + // There was code in the previous version to allow setting a 'modify' flag to execute a findAndModify .. + // I've moved that into a query type. + if ($Model->findQueryType === 'modify') { + $options = array( + 'projection' => $fields, 'sort' => $order, - 'remove' => !empty($remove), - 'update' => array('$set' => $modify), - 'new' => !empty($new), - 'fields' => $fields, - 'upsert' => !empty($upsert) - )); - $return = $this->_db - ->command($options); - if ($this->fullDebug) { - if ($return['ok']) { - $count = 1; - if ($this->config['set_string_id'] && !empty($return['value']['_id']) && is_object($return['value']['_id'])) { - $return['value']['_id'] = $return['value']['_id']->__toString(); - } - $return[][$Model->alias] = $return['value']; - } else { - $count = 0; + 'limit' => $limit, + 'skip' => $offset, + 'returnDocument' => ! empty($new), + 'upsert' => ! empty($upsert), + ); + + // Merge preset options + if (! empty($this->_findOptions)) + $options = array_merge($options, $this->_findOptions); + + // If remove is set then replace the document otherwise update. + if (! empty($remove)) { + try { + $this->lastResult = $this->_db + ->selectCollection($Model->table) + ->findOneAndReplace($conditions, array('$set' => $modify), $options); + } catch (MongoDB\Driver\Exception\Exception $e) { + $this->error = $e->getMessage(); + trigger_error($this->error); + } + + if ($this->fullDebug) { + $logQuery = "db.{$Model->table}.findOneAndReplace( :conditions, :options )"; + } + } else { + + try { + $this->lastResult = $this->_db + ->selectCollection($Model->table) + ->findOneAndUpdate($conditions, array('$set' => $modify), $options); + } catch (MongoDB\Driver\Exception\Exception $e) { + $this->error = $e->getMessage(); + trigger_error($this->error); + } + + if ($this->fullDebug) { + $logQuery = "db.{$Model->table}.findOneAndUpdate( :conditions, :options )"; } - $this->logQuery("db.runCommand( :options )", - array('options' => array_filter($options), 'count' => $count) - ); } - } - if ($Model->findQueryType === 'count') { - return array(array($Model->alias => array('count' => $return->count()))); - } + //$result = MongoDB\BSON\toPHP($this->lastResult); + $result = $this->lastResult; - if (is_object($return)) { - $_return = array(); - while ($return->hasNext()) { - $mongodata = $return->getNext(); - if ($this->config['set_string_id'] && !empty($mongodata['_id']) && is_object($mongodata['_id'])) { - $mongodata['_id'] = $mongodata['_id']->__toString(); + $count = 0; + if (! empty($result)) { + $count = 1; + if ($this->config['set_string_id'] && ! empty($result['_id']) && is_object($result['_id'])) { + $result['_id'] = $result['_id']->__toString(); } - $_return[][$Model->alias] = $mongodata; + $return[][$Model->alias] = $result; + } + + if ($this->fullDebug) { + $this->logQuery($logQuery, compact($conditions, $options, $count)); } - return $_return; } return $return; } @@ -1078,11 +1180,18 @@ public function rollback() { * @access public */ public function truncate($table) { - if (!$this->isConnected()) { + if (! $this->isConnected()) { return false; } - - return $this->execute('db.' . $this->fullTableName($table) . '.remove();'); + try { + return $this->_db + ->selectCollection($table) + ->deleteMany(); + } catch (MongoDB\Driver\Exception\Exception $e) { + $this->error = $e->getMessage(); + trigger_error($this->error); + } + return false; } /** @@ -1094,62 +1203,70 @@ public function truncate($table) { * @return void * @access public */ - public function query($query, $params = array()) { - if (!$this->isConnected()) { - return false; + public function query() { + $args = func_get_args(); + $query = $args[0]; + $params = array(); + if (count($args) > 1) { + $params = $args[1]; } - if($query === 'getMongoDb') { + if (! $this->isConnected()) + return false; + + if ($query === 'getMongoDb') return $this->getMongoDb(); - } - $this->_prepareLogQuery($Model); // just sets a timer - $return = $this->_db - ->command($query); - if ($this->fullDebug) { - $this->logQuery("db.runCommand( :query )", compact('query')); + // Compatibility with previous plugin + if ($query == 'db.version()') { + $doc = $this->query(array('serverStatus' => 1)); + return $doc['version']; } + $this->_prepareLogQuery($Model); + $return = array(); + try { + $cursor = $this->_db + ->command($query); + + if (! is_object($cursor)) { + if ($this->fullDebug) { + $this->logQuery("Failed : db.command( :query )", compact('query')); + } + return false; + } + + $count = 0; + // Its a cursor - but is it always only a one document cursor ? + foreach($cursor as $doc) { + $return = $doc; + $count++; + } + + if ($this->fullDebug) { + $this->logQuery("db.command( :query )", compact('query', 'count')); + } + } catch (MongoDB\Driver\Exception\Exception $e) { + $this->error = $e->getMessage(); + trigger_error($this->error); + } return $return; } /** * mapReduce * + * Method maintained for backwards compatibility + * * @param mixed $query - * @param integer $timeout (milli second) + * @param integer $timeout (milli second) NOTE: Currently ignored. * @return mixed false or array * @access public */ public function mapReduce($query, $timeout = null) { - - //above MongoDB1.8, query must object. - if(isset($query['query']) && !is_object($query['query'])) { - $query['query'] = (object)$query['query']; - } - - $result = $this->query($query); - - if($result['ok']) { - if (isset($query['out']['inline']) && $query['out']['inline'] === 1) { - if (is_array($result['results'])) { - $data = $result['results']; - }else{ - $data = false; - } - }else { - $data = $this->_db->selectCollection($result['result'])->find(); - if(!empty($timeout)) { - $data->timeout($timeout); - } - } - return $data; - } - return false; + return $this->query($query); } - - /** * Prepares a value, or an array of values for database queries by quoting and escaping them. * @@ -1178,30 +1295,14 @@ public function value($data, $column = null, $read = true) { * @return void * @access public */ - public function execute($query, $params = array()) { - if (!$this->isConnected()) { + public function execute($query, $options = array(), $params = array()) { + if (! $this->isConnected()) return false; - } - if (!$query || $query === true) { + if (! $query || $query === true) return; - } - $this->_prepareLogQuery($Model); // just sets a timer - $return = $this->_db - ->execute($query, $params); - if ($this->fullDebug) { - if ($params) { - $this->logQuery(":query, :params", - compact('query', 'params') - ); - } else { - $this->logQuery($query); - } - } - if ($return['ok']) { - return $return['retval']; - } - return $return; + + return $this->query($query, $params); } /** @@ -1213,16 +1314,11 @@ public function execute($query, $params = array()) { * @access protected */ protected function _setEmptyValues(&$data, $integers = array('limit', 'offset')) { - if (!is_array($data)) { + if (! is_array($data)) return; - } foreach($data as $key => $value) { if (empty($value)) { - if (in_array($key, $integers)) { - $data[$key] = 0; - } else { - $data[$key] = array(); - } + $data[$key] = (in_array($key, $integers)) ? 0 : array(); } } } @@ -1237,7 +1333,7 @@ protected function _setEmptyValues(&$data, $integers = array('limit', 'offset')) * @access protected */ protected function _prepareLogQuery(&$Model) { - if (!$this->fullDebug) { + if (! $this->fullDebug) { return false; } $this->_startTime = microtime(true); @@ -1248,22 +1344,6 @@ protected function _prepareLogQuery(&$Model) { return true; } -/** - * setTimeout Method - * - * Sets the MongoCursor timeout so long queries (like map / reduce) can run at will. - * Expressed in milliseconds, for an infinite timeout, set to -1 - * - * @param int $ms - * @return boolean - * @access public - */ - public function setTimeout($ms){ - MongoCursor::$timeout = $ms; - - return true; - } - /** * logQuery method * @@ -1280,18 +1360,17 @@ public function setTimeout($ms){ public function logQuery($query, $args = array()) { if ($args) { $this->_stringify($args); - $query = String::insert($query, $args); + $query = CakeText::insert($query, $args); } $this->took = round((microtime(true) - $this->_startTime) * 1000, 0); $this->affected = null; if (empty($this->error['err'])) { - $this->error = $this->_db->lastError(); + //$this->error = $this->_db->lastError(); if (!is_scalar($this->error)) { $this->error = json_encode($this->error); } } - $this->numRows = !empty($args['count'])?$args['count']:null; - + $this->numRows = ! empty($args['count']) ? $args['count'] : null; $query = preg_replace('@"ObjectId\((.*?)\)"@', 'ObjectId ("\1")', $query); return parent::logQuery($query); } @@ -1312,13 +1391,13 @@ protected function _convertId(&$mixed, $conditions = false) { if (strlen($mixed) !== 24) { return; } - $mixed = new MongoId($mixed); + $mixed = new MongoDB\BSON\ObjectID($mixed); } if (is_array($mixed)) { foreach($mixed as &$row) { $this->_convertId($row, false); } - if (!empty($mixed[0]) && $conditions) { + if (! empty($mixed[0]) && $conditions) { $mixed = array('$in' => $mixed); } } @@ -1341,9 +1420,9 @@ protected function _stringify(&$args = array(), $level = 0) { $this->_stringify($arg, $level + 1); } elseif (is_object($arg) && is_callable(array($arg, '__toString'))) { $class = get_class($arg); - if ($class === 'MongoId') { + if ($class === 'MongoDB\BSON\ObjectID') { $arg = 'ObjectId(' . $arg->__toString() . ')'; - } elseif ($class === 'MongoRegex') { + } elseif ($class === 'MongoDB\BSON\Regex ') { $arg = '_regexstart_' . $arg->__toString() . '_regexend_'; } else { $arg = $class . '(' . $arg->__toString() . ')'; @@ -1412,8 +1491,5 @@ protected function _stripAlias(&$args = array(), $alias = 'Model', $recurse = tr * @access public */ function MongoDbDateFormatter($date = null) { - if ($date) { - return new MongoDate($date); - } - return new MongoDate(); + return ($date) ? new MongoDB\BSON\UTCDateTime($date) : new MongoDB\BSON\UTCDateTime(time()); } \ No newline at end of file diff --git a/README.markdown b/README.markdown index 18047de..1af6e53 100644 --- a/README.markdown +++ b/README.markdown @@ -1,23 +1,30 @@ -# mongoDB datasource for CakePHP +# MongoDB datasource for CakePHP 2.8+ + +Note : This datasource is Beta, feedback and pull requests (with tests) welcome. :-) ## Requirements -PHP5, -pecl mongo (http://php.net/mongo) + + PHP 5.6, PHP 7+ + CakePHP 2.x (> 2.8, < 3.0) + +## Dependency + + php-mongodb extension (not the php-mongo extension). + mongodb-php-library (https://docs.mongodb.com/php-library/master/, dependency is listed in composer) ## Installation -this repository should be installed in the same way as any other plugin. +This repository should be installed in the same way as any other plugin. +See http://book.cakephp.org/2.0/en/plugins/how-to-install-plugins.html +Use composer + + composer require "handsetdetection/mongodblib=^1.0.0" + To install the driver for use in a single application: cd my/app/plugins - git clone git://github.com/ichikaway/cakephp-mongodb.git mongodb - -To install the driver for use in any/multiple application(s) - - # where ROOT is the name of the directory parent to the base index.php of CakePHP. - cd ROOT/plugins - git clone git://github.com/ichikaway/cakephp-mongodb.git mongodb + git clone git://github.com/HandsetDetection/MongoDBLib.git MongoDBLib ## Sample Code @@ -28,7 +35,7 @@ To use this DB driver, install (obviously) and define a db source such as follow class DATABASE_CONFIG { public $default = array( - 'driver' => 'mongodb.mongodbSource', + 'driver' => 'MongoDBLib.mongodbSource', 'database' => 'driver', 'host' => 'localhost', 'port' => 27017, @@ -42,35 +49,58 @@ To use this DB driver, install (obviously) and define a db source such as follow ); -More detail of replicaset in wiki: -https://github.com/ichikaway/cakephp-mongodb/wiki/How-to-connect-to-replicaset-servers +Model files need to have mongoSchema property, or make use of the schemaless behavior. +Mongo uses a primary key named "\_id" (cannot be renamed). It can be any format you like but if you don't explicitly set it Mongo will use an automatic 24 character (uu)id. -Model files need to have mongoSchema property - or make use of the schemaless behavior. +## Update Notes -Mongo uses a primary key named "\_id" (cannot be renamed). It can be any format you like but if you don't explicitly set it Mongo will use an automatic 24 character (uu)id. +This plugin was derived from the most excellent cakephp-mongodb plugin by Yasushi Ichikawa, originally built for CakePHP 1.3. +cakephp-mongodb works with the php-mono extension which has since been deprecated and will not run on PHP 7.0. -Before you start, you may find it useful to see [a model sample.](http://github.com/ichikaway/mongoDB-Datasource/blob/master/samples/models/post.php) -There are also some sample [controller actions: find,save,delete,deleteAll,updateAll](http://github.com/ichikaway/mongoDB-Datasource/blob/master/samples/controllers/posts_controller.php) note that your controller code needs no specific code to use this datasource. +This plugin is an updated version of cakephp-mongodb and strives to be as backwardly compatible as possible. It uses the +newer php-mongodb extension. If you're migrating from cakephp-mongodb to this plugin please be aware there are a number +of breaking changes. Namely, all the types and classes have changed. -## Author -Yasushi Ichikawa ([ichikaway](http://twitter.com/ichikaway)) +1) Types : All the types have changed. + +MongoId becomes MongoDB\BSON\ObjectID +MongoCode becomes MongoDB\BSON\JavaScript +MongoDate becomes MongoDB\BSON\UTCDateTime +MongoRegex becomes MongoDB\BSON\Regex +MongoBinData becomes MongoDB\BSON\Binary +MongoInt32 php-mongodb extension now chooses the best type automagically. +MongoInt64 php-mongodb extension now chooses the best type automagically. +MongoDBRef deprecated - no corresponding class +MongoMinKey becomes MongoDB\BSON\MinKey +MongoMaxKey becomes MongoDB\BSON\MaxKey +MongoTimestamp becomes MongoDB\BSON\Timestamp + +2) Classes +There's too much detail to cover here with the class changes so check out these references below. +Old classes : http://php.net/manual/en/book.mongo.php +New classes : http://php.net/manual/en/set.mongodb.php + +3) All CRUD calls are the same and should return identical information to the cakephp-mongodb plugin. +Calls doing mapReduce, aggregation and executing database commands should be extensively tested. + + +## Original Authors + +Yasushi Ichikawa ([ichikaway](http://twitter.com/ichikaway)) Andy Dawson ([AD7six](http://twitter.com/AD7six)) -## Contributors -[Predominant](http://github.com/predominant/) : Cleanup code, add documentation +## Original Contributors +[Predominant](http://github.com/predominant/) : Cleanup code, add documentation [Jrbasso](http://github.com/jrbasso/) : Cleanup code - [tkyk](http://github.com/tkyk/) : Fix bug, Add some function. -## Reference -Reference code, Thank you! +## Original Reference +Reference code, Thank you! [Nate Abele's lithium mongoDB datasource](http://li3.rad-dev.org/) - [Joél Perras' divan](http://github.com/jperras/divan/) - diff --git a/tests/cases/datasources/mongodb_source.test.php b/Test/Case/Datasource/MongodbSourceTest.php similarity index 74% rename from tests/cases/datasources/mongodb_source.test.php rename to Test/Case/Datasource/MongodbSourceTest.php index 9a8ec4e..da65c01 100644 --- a/tests/cases/datasources/mongodb_source.test.php +++ b/Test/Case/Datasource/MongodbSourceTest.php @@ -19,12 +19,11 @@ /** * Import relevant classes for testing */ -App::import('Model', 'Mongodb.MongodbSource'); -/** - * Generate Mock Model - */ -Mock::generate('AppModel', 'MockPost'); +App::uses('Model', 'Model'); +App::uses('AppModel', 'Model'); + + /** * Post Model for the test @@ -87,7 +86,6 @@ function manualUniqueValidation($check) { * @subpackage mongodb.tests.cases.datasources */ class MongoArticle extends AppModel { - public $useDbConfig = 'mongo_test'; } @@ -115,7 +113,7 @@ class MongodbSourceTest extends CakeTestCase { * */ protected $_config = array( - 'datasource' => 'mongodb', + 'datasource' => 'MongoDBLib.MongodbSource', 'host' => 'localhost', 'login' => '', 'password' => '', @@ -131,13 +129,10 @@ class MongodbSourceTest extends CakeTestCase { * @return void * @access public */ - public function startTest() { - $connections = ConnectionManager::enumConnectionObjects(); - - if (!empty($connections['test']['classname']) && $connections['test']['classname'] === 'mongodbSource') { - $config = new DATABASE_CONFIG(); - $this->_config = $config->test; - } + public function setUp() { + // Recreate the $test database definition as our Datasource. + ConnectionManager::drop('test'); + ConnectionManager::create('test', $this->_config); ConnectionManager::create('mongo_test', $this->_config); $this->Mongo = new MongodbSource($this->_config); @@ -145,10 +140,15 @@ public function startTest() { $this->Post = ClassRegistry::init('Post'); $this->Post->setDataSource('mongo_test'); - $this->mongodb =& ConnectionManager::getDataSource($this->Post->useDbConfig); + $this->mongodb = ConnectionManager::getDataSource('mongo_test'); $this->mongodb->connect(); $this->dropData(); + + $this->MongoArticle = ClassRegistry::init('MongoArticle'); + $this->MongoArticle->setDataSource('mongo_test'); + + //syslog(LOG_NOTICE, 'setUp '.$this->getName()); } /** @@ -157,7 +157,7 @@ public function startTest() { * @return void * @access public */ - public function endTest() { + public function tearDown() { $this->dropData(); unset($this->Post); unset($this->Mongo); @@ -188,9 +188,9 @@ public function insertData($data) { try { $this->mongodb ->connection - ->selectDB($this->_config['database']) + ->selectDatabase($this->_config['database']) ->selectCollection($this->Post->table) - ->insert($data, true); + ->insertOne($data); } catch (MongoException $e) { trigger_error($e->getMessage()); } @@ -206,10 +206,18 @@ public function dropData() { try { $db = $this->mongodb ->connection - ->selectDB($this->_config['database']); + ->selectDatabase($this->_config['database']); foreach($db->listCollections() as $collection) { - $collection->drop(); + $collectionName = $collection->getName(); + // Don't drop system collections. + if (! preg_match("/^system\./", $collectionName)) { + $result = $this->mongodb + ->connection + ->selectDatabase($this->_config['database']) + ->selectCollection($collectionName) + ->drop(); + } } } catch (MongoException $e) { trigger_error($e->getMessage()); @@ -223,54 +231,35 @@ public function dropData() { * @return void * @access public */ - public function testCreateConnectionName() { - $config = array( - 'datasource' => 'mongodb', - 'host' => 'localhost', - 'login' => '', - 'password' => '', - 'database' => 'test_mongo', - 'port' => 27017, - 'prefix' => '', - 'persistent' => false, - ); - $version = '1.2.2'; + public function testCreateConnectionName() { + $config = array( + 'datasource' => 'MongoDBLib.MongodbSource', + 'host' => 'localhost', + 'login' => '', + 'password' => '', + 'database' => 'test_mongo', + 'port' => 27017, + 'prefix' => '', + 'persistent' => false, + ); $expect = 'mongodb://localhost:27017'; - $host = $this->mongodb->createConnectionName($config, $version); + $host = $this->mongodb->createConnectionString($config); $this->assertIdentical($expect, $host); - - $config = array( - 'datasource' => 'mongodb', - 'host' => 'localhost', - 'login' => 'user', - 'password' => 'pass', - 'database' => 'test_mongo', - 'port' => 27017, - 'prefix' => '', - 'persistent' => false, - ); - $version = '1.2.2'; + $config = array( + 'datasource' => 'MongoDBLib.MongodbSource', + 'host' => 'localhost', + 'login' => 'user', + 'password' => 'pass', + 'database' => 'test_mongo', + 'port' => 27017, + 'prefix' => '', + 'persistent' => false, + ); $expect = 'mongodb://user:pass@localhost:27017/test_mongo'; - $host = $this->mongodb->createConnectionName($config, $version); + $host = $this->mongodb->createConnectionString($config); $this->assertIdentical($expect, $host); - - - $config = array( - 'datasource' => 'mongodb', - 'host' => 'localhost', - 'login' => 'user', - 'password' => 'pass', - 'database' => 'test_mongo', - 'port' => 27017, - 'prefix' => '', - 'persistent' => false, - ); - $version = '1.0.0'; - $expect = 'user:pass@localhost:27017/test_mongo'; - $host = $this->mongodb->createConnectionName($config, $version); - $this->assertIdentical($expect, $host); - } + } /** * Tests connection @@ -281,7 +270,6 @@ public function testCreateConnectionName() { public function testConnect() { $result = $this->Mongo->connect(); $this->assertTrue($result); - $this->assertTrue($this->Mongo->connected); $this->assertTrue($this->Mongo->isConnected()); } @@ -305,7 +293,7 @@ public function testDisconnect() { * @access public */ public function testListSources() { - $this->assertTrue(is_array($this->mongodb->listSources())); + $this->assertTrue($this->mongodb->listSources()); } /** @@ -318,11 +306,11 @@ public function testGetMongoDb() { $obj = $this->mongodb->getMongoDb(); $this->assertTrue(is_object($obj)); $objName = get_class($obj); - $this->assertEqual('MongoDB', $objName); + $this->assertEqual('MongoDB\Database', $objName); } /** - * Tests the Model::getMongoDb() call MongodbSource::getMongoDb + * Tests the Model::getMongoDb() call Mongodb::getMongoDb * * @return void * @access public @@ -331,7 +319,7 @@ public function testGetMongoDbFromModel() { $obj = $this->Post->getMongoDb(); $this->assertTrue(is_object($obj)); $objName = get_class($obj); - $this->assertEqual('MongoDB', $objName); + $this->assertEqual('MongoDB\Database', $objName); } /** @@ -344,7 +332,7 @@ public function testGetMongoCollection() { $obj = $this->mongodb->getMongoCollection($this->Post); $this->assertTrue(is_object($obj)); $objName = get_class($obj); - $this->assertEqual('MongoCollection', $objName); + $this->assertEqual('MongoDB\Collection', $objName); } /** @@ -354,7 +342,7 @@ public function testGetMongoCollection() { * @access public */ public function testDescribe() { - $mockObj = new MockPost(); + $mockObj = $this->getMock('AppModel'); $result = $this->mongodb->describe($mockObj); $expected = array( @@ -420,7 +408,7 @@ public function testSave() { $this->Post->create(); $saveResult = $this->Post->save($saveData); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $result = $this->Post->find('all'); @@ -433,8 +421,8 @@ public function testSave() { $this->assertEqual($data['body'], $resultData['body']); $this->assertEqual($data['text'], $resultData['text']); - $this->assertTrue(is_a($resultData['created'], 'MongoDate')); - $this->assertTrue(is_a($resultData['modified'], 'MongoDate')); + $this->assertTrue(is_a($resultData['created'], 'MongoDB\BSON\UTCDateTime')); + $this->assertTrue(is_a($resultData['modified'], 'MongoDB\BSON\UTCDateTime')); } /** @@ -452,7 +440,7 @@ public function testCheckInsertIdAfterSaving() { $this->Post->create(); $saveResult = $this->Post->save($saveData); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $this->assertEqual($this->Post->id, $this->Post->getInsertId()); $this->assertTrue(is_string($this->Post->id)); @@ -468,7 +456,7 @@ public function testCheckInsertIdAfterSaving() { $this->Post->create(); $saveResult = $this->Post->save($saveData); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $this->assertEqual($saveData['Post']['_id'] ,$this->Post->id); $this->assertEqual($this->Post->id, $this->Post->getInsertId()); @@ -482,8 +470,6 @@ public function testCheckInsertIdAfterSaving() { } - - /** * Tests saveAll method. * @@ -517,8 +503,8 @@ public function testSaveAll() { $this->assertEqual($data['body'], $resultData['body']); $this->assertEqual($data['text'], $resultData['text']); - $this->assertTrue(is_a($resultData['created'], 'MongoDate')); - $this->assertTrue(is_a($resultData['modified'], 'MongoDate')); + $this->assertTrue(is_a($resultData['created'], 'MongoDB\BSON\UTCDateTime')); + $this->assertTrue(is_a($resultData['modified'], 'MongoDB\BSON\UTCDateTime')); $resultData = $result[1]['Post']; $this->assertEqual(6, count($resultData)); @@ -528,8 +514,8 @@ public function testSaveAll() { $this->assertEqual($data['body'], $resultData['body']); $this->assertEqual($data['text'], $resultData['text']); - $this->assertTrue(is_a($resultData['created'], 'MongoDate')); - $this->assertTrue(is_a($resultData['modified'], 'MongoDate')); + $this->assertTrue(is_a($resultData['created'], 'MongoDB\BSON\UTCDateTime')); + $this->assertTrue(is_a($resultData['modified'], 'MongoDB\BSON\UTCDateTime')); } /** @@ -556,8 +542,8 @@ public function testUpdate() { $count1 = $this->Post->find('count'); $this->assertIdentical($count1 - $count0, 1, 'Save failed to create one row'); - $this->assertTrue($saveResult); - $this->assertTrue($postId); + $this->assertNotEmpty($saveResult); + $this->assertNotEmpty($postId); $findresult = $this->Post->find('all'); $this->assertEqual(0, $findresult[0]['Post']['count']); @@ -573,7 +559,7 @@ public function testUpdate() { $count2 = $this->Post->find('count'); $this->assertIdentical($count2 - $count1, 0, 'Save test 2 created another row, it did not update the existing row'); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $this->assertIdentical($this->Post->id, $postId); $this->Post->create(); @@ -589,7 +575,7 @@ public function testUpdate() { $count3 = $this->Post->find('count'); $this->assertIdentical($count3 - $count2, 0, 'Saving with the id in the data created another row'); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $this->assertIdentical($this->Post->id, $postId); $this->Post->create(); @@ -605,7 +591,7 @@ public function testUpdate() { $count4 = $this->Post->find('count'); $this->assertIdentical($count4 - $count3, 0, 'Saving with $Model->id set and no id in the data created another row'); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $this->assertIdentical($this->Post->id, $postId); $result = $this->Post->find('all'); @@ -620,7 +606,6 @@ public function testUpdate() { $this->assertEqual($updatedata['text'], $resultData['text']); $this->assertEqual(0, $resultData['count']); - // using $inc operator $this->Post->mongoNoSetOperator = '$inc'; $this->Post->create(); @@ -631,7 +616,7 @@ public function testUpdate() { $saveData['Post'] = $updatedataIncrement; $saveResult = $this->Post->save($saveData); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $this->assertIdentical($this->Post->id, $postId); $result = $this->Post->find('all'); @@ -689,8 +674,8 @@ public function testUpdateAll() { $this->assertEqual('ichikawa', $resultData['name']); $this->assertEqual($data['body'], $resultData['body']); $this->assertEqual($data['text'], $resultData['text']); - $this->assertTrue(is_a($resultData['created'], 'MongoDate')); - $this->assertTrue(is_a($resultData['modified'], 'MongoDate')); + $this->assertTrue(is_a($resultData['created'], 'MongoDB\BSON\UTCDateTime')); + $this->assertTrue(is_a($resultData['modified'], 'MongoDB\BSON\UTCDateTime')); $resultData = $result[1]['Post']; @@ -701,8 +686,8 @@ public function testUpdateAll() { $this->assertEqual('ichikawa', $resultData['name']); $this->assertEqual($data['body'], $resultData['body']); $this->assertEqual($data['text'], $resultData['text']); - $this->assertTrue(is_a($resultData['created'], 'MongoDate')); - $this->assertTrue(is_a($resultData['modified'], 'MongoDate')); + $this->assertTrue(is_a($resultData['created'], 'MongoDB\BSON\UTCDateTime')); + $this->assertTrue(is_a($resultData['modified'], 'MongoDB\BSON\UTCDateTime')); } /** @@ -778,10 +763,9 @@ public function testSetMongoUpdateOperator() { $result = $ds->setMongoUpdateOperator($this->Post, $data); $this->assertEqual($expect, $result); - + $this->Post->mongoNoSetOperator = null; } - /** * Tests update method without $set operator. * @@ -789,78 +773,78 @@ public function testSetMongoUpdateOperator() { * @access public */ public function testUpdateWithoutMongoSchemaProperty() { - $MongoArticle = ClassRegistry::init('MongoArticle'); + $this->MongoArticle = ClassRegistry::init('MongoArticle'); + syslog(LOG_NOTICE, "MongoArticle ".json_encode($this->MongoArticle)); + $data = array( 'title' => 'test', 'body' => 'aaaa', 'text' => 'bbbb', 'count' => 0, - 'created' => new mongoDate(), - 'modified' => new mongoDate(), + 'created' => new MongoDB\BSON\UTCDateTime(time()), + 'modified' => new MongoDB\BSON\UTCDateTime(time()), ); $saveData['MongoArticle'] = $data; - $MongoArticle->create(); - $saveResult = $MongoArticle->save($saveData); - $postId = $MongoArticle->id; + $this->MongoArticle->create(); + $saveResult = $this->MongoArticle->save($saveData); + $postId = $this->MongoArticle->id; //using $set operator - $MongoArticle->create(); + $this->MongoArticle->create(); $updatedata = array( '_id' => $postId, 'title' => 'test3', 'body' => 'aaaa3', ); $saveData['MongoArticle'] = $updatedata; - $saveResult = $MongoArticle->save($saveData); // using $set operator + $saveResult = $this->MongoArticle->save($saveData); // using $set operator - $this->assertTrue($saveResult); - $this->assertIdentical($MongoArticle->id, $postId); + $this->assertNotEmpty($saveResult); + $this->assertIdentical($this->MongoArticle->id, $postId); $result = null; - $result = $MongoArticle->find('all'); + $result = $this->MongoArticle->find('all'); $this->assertEqual(1, count($result)); $resultData = $result[0]['MongoArticle']; - $this->assertEqual($MongoArticle->id, $resultData['_id']); + $this->assertEqual($this->MongoArticle->id, $resultData['_id']); $this->assertEqual($updatedata['title'], $resultData['title']); //update $this->assertEqual($updatedata['body'], $resultData['body']); //update $this->assertEqual($data['text'], $resultData['text']); //not update $this->assertEqual($data['count'], $resultData['count']); //not update - - //using $inc operator insted of $set operator - $MongoArticle->create(); + $this->MongoArticle->create(); $updatedataInc = array( '_id' => $postId, '$inc' => array('count' => 1), ); $saveData['MongoArticle'] = $updatedataInc; - $saveResult = $MongoArticle->save($saveData); // using $set operator + $saveResult = $this->MongoArticle->save($saveData); // using $set operator - $this->assertTrue($saveResult); - $this->assertIdentical($MongoArticle->id, $postId); + $this->assertNotEmpty($saveResult); + $this->assertIdentical($this->MongoArticle->id, $postId); $result = null; - $result = $MongoArticle->find('all'); + $result = $this->MongoArticle->find('all'); $this->assertEqual(1, count($result)); $resultData = $result[0]['MongoArticle']; - $this->assertEqual($MongoArticle->id, $resultData['_id']); + $this->assertEqual($this->MongoArticle->id, $resultData['_id']); $this->assertEqual($updatedata['title'], $resultData['title']); //not update $this->assertEqual($updatedata['body'], $resultData['body']); //not update $this->assertEqual($data['text'], $resultData['text']); //not update $this->assertEqual(1, $resultData['count']); //increment //using $inc and $push - $MongoArticle->create(); + $this->MongoArticle->create(); $updatedataInc = array( '_id' => $postId, '$push' => array( 'comments' => array( - '_id' => new MongoId(), - 'created' => new MongoDate(), + '_id' => new MongoDB\BSON\ObjectID(), + 'created' => new MongoDB\BSON\UTCDateTime(time()), 'vote_count' => 0, 'user' => 'user1', 'body' => 'comment', @@ -869,16 +853,16 @@ public function testUpdateWithoutMongoSchemaProperty() { '$inc' => array('count' => 1), ); $saveData['MongoArticle'] = $updatedataInc; - $saveResult = $MongoArticle->save($saveData); // using $set operator + $saveResult = $this->MongoArticle->save($saveData); // using $set operator - $this->assertTrue($saveResult); - $this->assertIdentical($MongoArticle->id, $postId); + $this->assertNotEmpty($saveResult); + $this->assertIdentical($this->MongoArticle->id, $postId); $result = null; - $result = $MongoArticle->find('all'); + $result = $this->MongoArticle->find('all'); $this->assertEqual(1, count($result)); $resultData = $result[0]['MongoArticle']; - $this->assertEqual($MongoArticle->id, $resultData['_id']); + $this->assertEqual($this->MongoArticle->id, $resultData['_id']); $this->assertEqual($updatedata['title'], $resultData['title']); //not update $this->assertEqual($updatedata['body'], $resultData['body']); //not update $this->assertEqual($data['text'], $resultData['text']); //not update @@ -889,11 +873,10 @@ public function testUpdateWithoutMongoSchemaProperty() { $this->assertTrue(!empty($resultData['created'])); $this->assertTrue(!empty($resultData['modified'])); - //no $set operator - $MongoArticle->mongoNoSetOperator = true; + $this->MongoArticle->mongoNoSetOperator = true; - $MongoArticle->create(); + $this->MongoArticle->create(); $updatedata = array( '_id' => $postId, 'title' => 'test4', @@ -901,67 +884,62 @@ public function testUpdateWithoutMongoSchemaProperty() { 'count' => '1', ); $saveData['MongoArticle'] = $updatedata; - $saveResult = $MongoArticle->save($saveData); + $saveResult = $this->MongoArticle->save($saveData); - $this->assertTrue($saveResult); - $this->assertIdentical($MongoArticle->id, $postId); + $this->assertNotEmpty($saveResult); + $this->assertIdentical($this->MongoArticle->id, $postId); $result = null; - $result = $MongoArticle->find('all'); + $result = $this->MongoArticle->find('all'); $this->assertEqual(1, count($result)); $resultData = $result[0]['MongoArticle']; - $this->assertEqual($MongoArticle->id, $resultData['_id']); + $this->assertEqual($this->MongoArticle->id, $resultData['_id']); $this->assertEqual($updatedata['title'], $resultData['title']); //update $this->assertEqual($updatedata['body'], $resultData['body']); //update $this->assertTrue(empty($resultData['text'])); $this->assertEqual(1, $resultData['count']); - $MongoArticle->mongoNoSetOperator = null; - + $this->MongoArticle->mongoNoSetOperator = null; //use $push - $MongoArticle->create(); + $this->MongoArticle->create(); $updatedata = array( '_id' => $postId, 'push_column' => array('push1'), ); $saveData['MongoArticle'] = $updatedata; - $saveResult = $MongoArticle->save($saveData); //use $set + $saveResult = $this->MongoArticle->save($saveData); //use $set - $result = $MongoArticle->find('all'); + $result = $this->MongoArticle->find('all'); $resultData = $result[0]['MongoArticle']; $this->assertEqual('test4', $resultData['title']); // no update $this->assertEqual(array('push1'), $resultData['push_column']); - - $MongoArticle->mongoNoSetOperator = '$push'; - $MongoArticle->create(); + $this->MongoArticle->mongoNoSetOperator = '$push'; + $this->MongoArticle->create(); $updatedata = array( '_id' => $postId, 'push_column' => 'push2', ); $saveData['MongoArticle'] = $updatedata; - $saveResult = $MongoArticle->save($saveData); //use $push + $saveResult = $this->MongoArticle->save($saveData); //use $push - $this->assertTrue($saveResult); - $this->assertIdentical($MongoArticle->id, $postId); + $this->assertNotEmpty($saveResult); + $this->assertIdentical($this->MongoArticle->id, $postId); $result = null; - $result = $MongoArticle->find('all'); - + $result = $this->MongoArticle->find('all'); $this->assertEqual(1, count($result)); $resultData = $result[0]['MongoArticle']; - $this->assertEqual($MongoArticle->id, $resultData['_id']); + $this->assertEqual($this->MongoArticle->id, $resultData['_id']); $this->assertEqual('test4', $resultData['title']); // no update $this->assertEqual(array('push1','push2'), $resultData['push_column']); //update - $MongoArticle->mongoNoSetOperator = null; - - - unset($MongoArticle); + $this->MongoArticle->mongoNoSetOperator = null; + unset($this->MongoArticle); } @@ -972,13 +950,13 @@ public function testUpdateWithoutMongoSchemaProperty() { * @access public */ public function testGroupBy() { - for($i = 0 ; $i < 30 ; $i++) { + for ($i = 0 ; $i < 30 ; $i++) { $saveData[$i]['Post'] = array( - 'title' => 'test'.$i, - 'body' => 'aaaa'.$i, - 'text' => 'bbbb'.$i, - 'count' => $i, - ); + 'title' => 'test'.$i, + 'body' => 'aaaa'.$i, + 'text' => 'bbbb'.$i, + 'count' => $i, + ); } $saveData[30]['Post'] = array( @@ -999,16 +977,14 @@ public function testGroupBy() { $cond_count = 5; $query = array( - 'key' => array('title' => true ), - 'initial' => array('csum' => 0), - 'reduce' => 'function(obj, prev){prev.csum += 1;}', - 'options' => array( - 'condition' => array('count' => array('$lt' => $cond_count)), - ), - ); + 'key' => array('title' => true ), + 'initial' => array('csum' => 0), + 'reduce' => 'function(obj, result){result.csum += 1;}', + 'conditions' => array('count' => array('$lt' => $cond_count)), + ); $mongo = $this->Post->getDataSource(); - $result = $mongo->group($this->Post, $query); + $result = $mongo->group($query, $this->Post); $this->assertTrue($result['ok'] == 1 && count($result['retval']) > 0); $this->assertEqual($cond_count, count($result['retval'])); @@ -1018,10 +994,7 @@ public function testGroupBy() { $this->assertEqual(1, $result['retval'][0]['csum']); $this->assertEqual(2, $result['retval'][1]['csum']); $this->assertEqual(2, $result['retval'][2]['csum']); - -} - - + } /** * Tests query @@ -1033,11 +1006,11 @@ public function testGroupBy() { public function testQuery() { for($i = 0 ; $i < 30 ; $i++) { $saveData[$i]['Post'] = array( - 'title' => 'test'.$i, - 'body' => 'aaaa'.$i, - 'text' => 'bbbb'.$i, - 'count' => $i, - ); + 'title' => 'test'.$i, + 'body' => 'aaaa'.$i, + 'text' => 'bbbb'.$i, + 'count' => $i, + ); } $saveData[30]['Post'] = array( @@ -1063,7 +1036,6 @@ public function testQuery() { $this->Post->create(); $saveResult = $this->Post->saveAll($saveData); - //using query() Distinct $params = array( 'distinct' => 'posts', @@ -1074,19 +1046,18 @@ public function testQuery() { $this->assertEqual(2, $result['values'][2]); $this->assertEqual(32, $result['values'][30]); - //using query() group $cond_count = 5; $reduce = "function(obj,prev){prev.csum++;}"; $params = array( - 'group'=>array( - 'ns'=>'posts', - 'cond'=>array('count' => array('$lt' => $cond_count)), - 'key'=>array('title'=>true), - 'initial'=>array('csum'=>0), - '$reduce'=>$reduce - ) - ); + 'group' => array( + 'ns' => 'posts', + 'cond' => array('count' => array('$lt' => $cond_count)), + 'key' => array('title' => true), + 'initial' => array('csum' => 0), + '$reduce' => $reduce + ) + ); $result = $this->Post->query( $params ); @@ -1098,8 +1069,7 @@ public function testQuery() { $this->assertEqual(1, $result['retval'][0]['csum']); $this->assertEqual(2, $result['retval'][1]['csum']); $this->assertEqual(2, $result['retval'][2]['csum']); - -} + } /** * Tests MapReduce @@ -1107,107 +1077,85 @@ public function testQuery() { * @return void * @access public */ -public function testMapReduce() { - for($i = 0 ; $i < 30 ; $i++) { - $saveData[$i]['Post'] = array( + public function testMapReduce() { + for ($i = 0 ; $i < 30 ; $i++) { + $saveData[$i]['Post'] = array( 'title' => 'test'.$i, 'body' => 'aaaa'.$i, 'text' => 'bbbb'.$i, 'count' => $i, - ); - } + ); + } - $saveData[30]['Post'] = array( + $saveData[30]['Post'] = array( 'title' => 'test1', 'body' => 'aaaa1', 'text' => 'bbbb1', 'count' => 1, - ); - $saveData[31]['Post'] = array( + ); + + $saveData[31]['Post'] = array( 'title' => 'test2', 'body' => 'aaaa2', 'text' => 'bbbb2', 'count' => 2, - ); + ); - $saveData[32]['Post'] = array( + $saveData[32]['Post'] = array( 'title' => 'test2', 'body' => 'aaaa2', 'text' => 'bbbb2', 'count' => 32, - ); + ); - $this->Post->create(); - $saveResult = $this->Post->saveAll($saveData); + $this->Post->create(); + $saveResult = $this->Post->saveAll($saveData); - $map = new MongoCode("function() { emit(this.title,1); }"); - $reduce = new MongoCode("function(k, vals) { ". - "var sum = 0;". - "for (var i in vals) {". - "sum += vals[i];". - "}". - "return sum; }" + $map = new MongoDB\BSON\Javascript("function() { emit(this.title,1); }"); + $reduce = new MongoDB\BSON\Javascript("function(k, vals) { ". + "var sum = 0;". + "for (var i in vals) {". + "sum += vals[i];". + "}". + "return sum; }" ); - $params = array( - "mapreduce" => "posts", + // Results output to collection. + $params = array( + "mapReduce" => "posts", "map" => $map, "reduce" => $reduce, "query" => array( "count" => array('$gt' => -2), - ), + ), 'out' => 'test_mapreduce_posts', - ); - - $mongo = $this->Post->getDataSource(); - $results = $mongo->mapReduce($params); - - $posts = array(); - foreach ($results as $post) { - $posts[$post['_id']] = $post['value']; - } - - $this->assertEqual(30, count($posts)); - $this->assertEqual(1, $posts['test0']); - $this->assertEqual(2, $posts['test1']); - $this->assertEqual(3, $posts['test2']); - $this->assertEqual(1, $posts['test3']); - - - //set timeout - $results = $mongo->mapReduce($params, 100); //set timeout 100msec - $posts = array(); - foreach ($results as $post) { - $posts[$post['_id']] = $post['value']; - } - - $this->assertEqual(30, count($posts)); - $this->assertEqual(1, $posts['test0']); - $this->assertEqual(2, $posts['test1']); - $this->assertEqual(3, $posts['test2']); - $this->assertEqual(1, $posts['test3']); + ); + $mongo = $this->Post->getDataSource(); + $reply = $mongo->mapReduce($params); + $this->assertEqual($reply['result'], 'test_mapreduce_posts'); + $this->assertEqual($reply['ok'], 1); - //get results as inline data - $version = $this->getMongodVersion(); - if( $version >= '1.7.4') { + // Results inline $params = array( - "mapreduce" => "posts", - "map" => $map, - "reduce" => $reduce, - "query" => array( - "count" => array('$gt' => -2), - ), - 'out' => array('inline' => 1), - ); - - $results = $mongo->mapReduce($params); + "mapReduce" => "posts", + "map" => $map, + "reduce" => $reduce, + "query" => array( + "count" => array('$gt' => -2), + ), + 'out' => array('inline' => 1), + ); + + $reply = $this->Post->query($params); + $this->assertTrue(is_array($reply['results'])); + $this->assertEqual($reply['ok'], 1); $posts = array(); - foreach ($results as $post) { + foreach ($reply['results'] as $post) { $posts[$post['_id']] = $post['value']; } - + $this->assertEqual(30, count($posts)); $this->assertEqual(1, $posts['test0']); $this->assertEqual(2, $posts['test1']); @@ -1215,11 +1163,6 @@ public function testMapReduce() { $this->assertEqual(1, $posts['test3']); } - -} - - - /** * testSort method * @@ -1306,12 +1249,13 @@ public function testSchemaless() { 'created' => null ); - $MongoArticle = ClassRegistry::init('MongoArticle'); - $MongoArticle->create(); - $this->assertTrue($MongoArticle->save($toSave), 'Saving with no defined schema failed'); + $this->MongoArticle = ClassRegistry::init('MongoArticle'); + $this->MongoArticle->create(); + $this->assertNotEmpty($this->MongoArticle->save($toSave), 'Saving with no defined schema failed'); $expected = array_intersect_key($toSave, array_flip(array('title', 'body', 'tags'))); - $result = $MongoArticle->read(array('title', 'body', 'tags')); + $result = $this->MongoArticle->read(array('title', 'body', 'tags')); + syslog(LOG_NOTICE, 'Schemaless read '.json_encode(array_keys($result['MongoArticle']))); unset ($result['MongoArticle']['_id']); // prevent auto added field from screwing things up $this->assertEqual($expected, $result['MongoArticle']); @@ -1327,9 +1271,9 @@ public function testSchemaless() { 'modified' => null, 'created' => null ); - $MongoArticle->create(); - $this->assertTrue($MongoArticle->save($toSave), 'Saving with no defined schema failed'); - $starts = $MongoArticle->field('starts'); + $this->MongoArticle->create(); + $this->assertNotEmpty($this->MongoArticle->save($toSave), 'Saving with no defined schema failed'); + $starts = $this->MongoArticle->field('starts'); $this->assertEqual($toSave['starts'], $starts); } @@ -1352,7 +1296,7 @@ public function testSpecificId() { $this->Post->create(); $saveResult = $this->Post->save($saveData); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $found = $this->Post->find('first', array( 'fields' => array('_id', 'title', 'body', 'text'), @@ -1371,7 +1315,7 @@ public function testSpecificId() { $this->Post->create(); $saveResult = $this->Post->save($saveData); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $found = $this->Post->find('first', array( 'fields' => array('_id', 'title', 'body', 'text'), @@ -1393,8 +1337,8 @@ public function testOr() { return; } - $MongoArticle = ClassRegistry::init('MongoArticle'); - $MongoArticle->create(); + $this->MongoArticle = ClassRegistry::init('MongoArticle'); + $this->MongoArticle->create(); for ($i = 1; $i <= 20; $i++) { $data = array( @@ -1402,18 +1346,18 @@ public function testOr() { 'subtitle' => "Sub Article $i", ); $saveData['MongoArticle'] = $data; - $MongoArticle->create(); - $MongoArticle->save($saveData); + $this->MongoArticle->create(); + $this->MongoArticle->save($saveData); } - $expected = $MongoArticle->find('all', array( + $expected = $this->MongoArticle->find('all', array( 'conditions' => array( 'title' => array('$in' => array('Article 1', 'Article 10')) ), 'order' => array('number' => 'ASC') )); - $this->assertTrue(count($expected), 2); + $this->assertEqual(count($expected), 2); - $result = $MongoArticle->find('all', array( + $result = $this->MongoArticle->find('all', array( 'conditions' => array( '$or' => array( array('title' => 'Article 1'), @@ -1432,31 +1376,27 @@ public function testOr() { * @access public */ function testDeleteAll($cascade = true) { - $MongoArticle = ClassRegistry::init('MongoArticle'); - $MongoArticle->create(array('title' => 'Article 1', 'cat' => 1)); - $MongoArticle->save(); - - $MongoArticle->create(array('title' => 'Article 2', 'cat' => 1)); - $MongoArticle->save(); - - $MongoArticle->create(array('title' => 'Article 3', 'cat' => 2)); - $MongoArticle->save(); - - $MongoArticle->create(array('title' => 'Article 4', 'cat' => 2)); - $MongoArticle->save(); - - $count = $MongoArticle->find('count'); + $this->MongoArticle = ClassRegistry::init('MongoArticle'); + + $this->MongoArticle->create(array('title' => 'Article 1', 'cat' => 1)); + $this->MongoArticle->save(); + $this->MongoArticle->create(array('title' => 'Article 2', 'cat' => 1)); + $this->MongoArticle->save(); + $this->MongoArticle->create(array('title' => 'Article 3', 'cat' => 2)); + $this->MongoArticle->save(); + $this->MongoArticle->create(array('title' => 'Article 4', 'cat' => 2)); + $this->MongoArticle->save(); + + $count = $this->MongoArticle->find('count'); $this->assertEqual($count, 4); - $MongoArticle->deleteAll(array('cat' => 2), $cascade); - - $count = $MongoArticle->find('count'); + $this->MongoArticle->deleteAll(array('cat' => 2), $cascade); + $count = $this->MongoArticle->find('count'); $this->assertEqual($count, 2); - - $MongoArticle->deleteAll(true, $cascade); - - $count = $MongoArticle->find('count'); - $this->assertFalse($count); + + $this->MongoArticle->deleteAll(true, $cascade); + $count = $this->MongoArticle->find('count'); + $this->assertEqual($count, 0); } /** @@ -1476,24 +1416,27 @@ function testDeleteAllNoCascade() { * @access public */ public function testRegexSearch() { - $MongoArticle = ClassRegistry::init('MongoArticle'); - $MongoArticle->create(array('title' => 'Article 1', 'cat' => 1)); - $MongoArticle->save(); - $MongoArticle->create(array('title' => 'Article 2', 'cat' => 1)); - $MongoArticle->save(); - $MongoArticle->create(array('title' => 'Article 3', 'cat' => 2)); - $MongoArticle->save(); - - $count=$MongoArticle->find('count',array( - 'conditions'=>array( - 'title'=>'Article 2' + + $this->MongoArticle = ClassRegistry::init('MongoArticle'); + $this->MongoArticle->create(array('title' => 'Article 1', 'cat' => 1)); + $this->MongoArticle->save(); + + $this->MongoArticle->create(array('title' => 'Article 2', 'cat' => 1)); + $this->MongoArticle->save(); + + $this->MongoArticle->create(array('title' => 'Article 3', 'cat' => 2)); + $this->MongoArticle->save(); + + $count = $this->MongoArticle->find('count', array( + 'conditions' => array( + 'title' => 'Article 2' ) )); $this->assertEqual($count, 1); - $count = $MongoArticle->find('count',array( - 'conditions'=>array( - 'title'=> new MongoRegex('/^Article/') + $count = $this->MongoArticle->find('count', array( + 'conditions' => array( + 'title' => new MongoDB\BSON\Regex('^Article', '') ) )); $this->assertEqual($count, 3); @@ -1506,21 +1449,25 @@ public function testRegexSearch() { * @access public */ public function testEmptyReturn() { - $MongoArticle = ClassRegistry::init('MongoArticle'); - $MongoArticle->create(array('title' => 'Article 1', 'cat' => 1)); - $MongoArticle->save(); - $articles=$MongoArticle->find('all',array( - 'conditions'=>array( - 'title'=>'Article 2' + $this->MongoArticle = ClassRegistry::init('MongoArticle'); + $this->MongoArticle->create(array('title' => 'Article 1', 'cat' => 1)); + $this->MongoArticle->save(); + $articles = $this->MongoArticle->find('all', array( + 'conditions' => array( + 'title' => 'Article 2' ) )); $this->assertTrue(is_array($articles)); - $articles=$MongoArticle->find('first',array( - 'conditions'=>array( - 'title'=>'Article 2' + + $articles = $this->MongoArticle->find('first', array( + 'conditions' => array( + 'title' => 'Article 2' ) )); - $this->assertFalse(is_array($articles)); + // http://www.codeproject.com/Articles/987758/Changes-to-Model-find-first-in-CakePHP + // find('first') with not matching conditions resutns array(). + $this->assertTrue(is_array($articles)); + $this->assertEmpty($articles); } /** @@ -1538,10 +1485,10 @@ public function testSaveUniques() { ); $saveData['Post'] = $data; - $this->Post->Behaviors->attach('Mongodb.SqlCompatible'); + $this->Post->Behaviors->attach('MongoDBLib.SqlCompatible'); $this->Post->create(); $saveResult = $this->Post->save($saveData); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $data = array( 'title' => 'test', @@ -1572,7 +1519,7 @@ public function testSaveUniquesCustom() { $saveData['Post'] = $data; $this->Post->create(); $saveResult = $this->Post->save($saveData); - $this->assertTrue($saveResult); + $this->assertNotEmpty($saveResult); $data = array( 'title' => 'test', 'body' => 'asdf', @@ -1586,88 +1533,89 @@ public function testSaveUniquesCustom() { } public function testReturn() { - $MongoArticle = ClassRegistry::init('MongoArticle'); - $MongoArticle->create(array('title' => 'Article 1', 'cat' => 1)); - $MongoArticle->save(); - $MongoArticle->create(array('title' => 'Article 2', 'cat' => 1)); - $MongoArticle->save(); + $this->MongoArticle = ClassRegistry::init('MongoArticle'); + $this->MongoArticle->create(array('title' => 'Article 1', 'cat' => 1)); + $this->MongoArticle->save(); + $this->MongoArticle->create(array('title' => 'Article 2', 'cat' => 1)); + $this->MongoArticle->save(); - $return = $MongoArticle->find('all', array( + $return = $this->MongoArticle->find('all', array( 'conditions' => array( 'title' => 'Article 2' ) )); $this->assertTrue(is_array($return)); - $return = $MongoArticle->find('first', array( + $return = $this->MongoArticle->find('first', array( 'conditions' => array( 'title' => 'Article 2' ) )); $this->assertTrue(is_array($return)); - $return = $MongoArticle->find('first', array( + $return = $this->MongoArticle->find('first', array( 'conditions' => array( 'title' => 'Article 2' ) )); $this->assertTrue(is_array($return)); - $return = $MongoArticle->find('count', array( + $return = $this->MongoArticle->find('count', array( 'conditions' => array( 'title' => 'Article 2' ) )); $this->assertTrue(is_int($return)); - $return = $MongoArticle->find('neighbors', array( + $return = $this->MongoArticle->find('neighbors', array( 'conditions' => array( 'title' => 'Article 2' ) )); $this->assertTrue(is_array($return)); - $return = $MongoArticle->find('list', array( + $return = $this->MongoArticle->find('list', array( 'conditions' => array( 'title' => 'Article 2' ) )); $this->assertTrue(is_array($return)); - $return = $MongoArticle->find('all', array( + $return = $this->MongoArticle->find('all', array( 'conditions' => array( 'title' => 'Doesn\'t exist' ) )); $this->assertTrue(is_array($return)); - $return = $MongoArticle->find('first', array( + $return = $this->MongoArticle->find('first', array( 'conditions' => array( 'title' => 'Doesn\'t exist' ) )); - $this->assertFalse($return); + $this->assertTrue(is_array($return)); + $this->assertEmpty($return); - $return = $MongoArticle->find('count', array( + $return = $this->MongoArticle->find('count', array( 'conditions' => array( 'title' => 'Doesn\'t exist' ) )); $this->assertTrue(is_int($return)); + $this->assertEqual(0, $return); - $return = $MongoArticle->find('neighbors', array( + $return = $this->MongoArticle->find('neighbors', array( 'conditions' => array( 'title' => 'Doesn\'t exist' ) )); $this->assertTrue(is_array($return)); - $return = $MongoArticle->find('list', array( + $return = $this->MongoArticle->find('list', array( 'conditions' => array( 'title' => 'Doesn\'t exist' ) )); $this->assertTrue(is_array($return)); - } } \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..21be56a --- /dev/null +++ b/composer.json @@ -0,0 +1,23 @@ +{ + "name": "handsetdetection/mongodblib", + "description": "A CakePHP Driver from MongoDB using the new mongodb extension. For CakePHP 2.8+ (Not CakePHP 3.x)", + "homepage": "https://github.com/HandsetDetection/MongoDBLib", + "keywords": ["cakephp", "mongodb", "php", "mongo"], + "minimum-stability": "dev", + "license": "MIT", + "type": "cakephp-plugin", + "require": { + "php": ">=5.6", + "mongodb/mongodb": ">=1.0.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "authors": [ + { + "name": "Richard Uren", + "email": "elricho66@gmail.com", + "homepage": "http://www.handsetdetection.com/" + } + ] +} \ No newline at end of file diff --git a/docs/__filesource/fsource_mongodb_mongodb-models-behaviors_behaviorsschemaless.php.html b/docs/__filesource/fsource_mongodb_mongodb-models-behaviors_behaviorsschemaless.php.html deleted file mode 100644 index 5f8ef30..0000000 --- a/docs/__filesource/fsource_mongodb_mongodb-models-behaviors_behaviorsschemaless.php.html +++ /dev/null @@ -1,109 +0,0 @@ - - - -
- -Documentation is available at schemaless.php
-- Documentation generated on Tue, 26 Jul 2011 01:09:01 +0900 by phpDocumentor 1.4.3 -
- - \ No newline at end of file diff --git a/docs/__filesource/fsource_mongodb_mongodb-models-behaviors_behaviorssql_compatible.php.html b/docs/__filesource/fsource_mongodb_mongodb-models-behaviors_behaviorssql_compatible.php.html deleted file mode 100644 index 8181790..0000000 --- a/docs/__filesource/fsource_mongodb_mongodb-models-behaviors_behaviorssql_compatible.php.html +++ /dev/null @@ -1,290 +0,0 @@ - - - - - -Documentation is available at sql_compatible.php
-- Documentation generated on Tue, 26 Jul 2011 01:09:02 +0900 by phpDocumentor 1.4.3 -
- - \ No newline at end of file diff --git a/docs/blank.html b/docs/blank.html deleted file mode 100644 index 927a8fd..0000000 --- a/docs/blank.html +++ /dev/null @@ -1,13 +0,0 @@ - - -- Documentation generated on Tue, 26 Jul 2011 01:09:01 +0900 by phpDocumentor 1.4.3 -
- - \ No newline at end of file diff --git a/docs/elementindex.html b/docs/elementindex.html deleted file mode 100644 index 7406437..0000000 --- a/docs/elementindex.html +++ /dev/null @@ -1,674 +0,0 @@ - - - - - -- Documentation generated on Tue, 26 Jul 2011 01:09:02 +0900 by phpDocumentor 1.4.3 -
- - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index aafb23d..0000000 --- a/docs/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - -SchemalessBehavior class
- -- Located in /behaviors/schemaless.php (line 34) -
- - -ModelBehavior - | - --SchemalessBehavior- -
beforeSave method
-Set the schema to allow saving whatever has been passed
- - -setup method
-Don't currently have any settings at all - disabled
- - -- Documentation generated on Tue, 26 Jul 2011 01:09:01 +0900 by phpDocumentor 1.4.3 -
-SqlCompatibleBehavior class
- -- Located in /behaviors/sql_compatible.php (line 31) -
- - -ModelBehavior - | - --SqlCompatibleBehavior- -
name property
- - - - - - -Runtime settings
-Keyed on model alias
- - - - - - -defaultSettings property
- - - - - - -If requested, convert dates from MongoDate objects to standard date strings
- - -beforeFind method
-If conditions are an array ensure they are mongified
- - -Convert MongoDate objects to strings for the purpose of view simplicity
- - -setup method
-Allow overriding the operator map
- - -translateConditions method
-Loop on conditions and desqlify them
- - -translateOperator method
-Use the operator map for the model and return what the db really wants to hear
- - -- Documentation generated on Tue, 26 Jul 2011 01:09:02 +0900 by phpDocumentor 1.4.3 -
-Schemaless behavior.
-Adds functionality specific to MongoDB/schemaless dbs Allow /not/ specifying the model's schema, and derive it (for cake-compatibility) from the data being saved. Note that used carelessly this is a pretty dangerous thing to allow - means a user can modify input forms adding whatever fields they like (unless you'er using the security component) and fill your db with their junk.
PHP version 5
Copyright (c) 2010, Andy Dawson
Licensed under The MIT License Redistributions of files must retain the above copyright notice.
- - -Class | -Description | -
---|---|
- SchemalessBehavior - | -- SchemalessBehavior class - | -
- Documentation generated on Tue, 26 Jul 2011 01:09:01 +0900 by phpDocumentor 1.4.3 -
-Sql Compatible.
-Attach this behavior to be able to query mongo DBs without using mongo specific syntax. If you don't need this behavior don't attach it and save a few cycles
PHP version 5
Copyright (c) 2010, Andy Dawson
Licensed under The MIT License Redistributions of files must retain the above copyright notice.
- - -Class | -Description | -
---|---|
- SqlCompatibleBehavior - | -- SqlCompatibleBehavior class - | -
- Documentation generated on Tue, 26 Jul 2011 01:09:02 +0900 by phpDocumentor 1.4.3 -
-MongoDB Source
-- Located in /datasources/mongodb_source.php (line 35) -
- - -DboSource - | - --MongodbSource- -
column definition
- - - - - - -Are we connected to the DataSource?
-true - yes null - haven't tried yet false - nope, and we can't connect
- - - - - - -Base Config
-set_string_id: true: In read() method, convert MongoId object to string and set it to array 'id'. false: not convert and set.
- - - - - - -Database Instance
- - - - - - -Default schema for the mongo models
- - - - - - -Mongo Driver Version
- - - - - - -startTime property
-If debugging is enabled, stores the (micro)time the current query started
- - - - - - -construct method
-By default don't try to connect until you need to
- - -Destruct
- - -begin method
-Mongo doesn't support transactions
- - -Calculate
- - -Close database connection
- - -commit method
-MongoDB doesn't support transactions
- - -Connect to the database
-If using 1.0.2 or above use the mongodb:// format to connect The connect syntax changed in version 1.0.2 - so check for that too
If authentication information in present then authenticate the connection
- - -Create Data
- - -create connection name.
- - -createSchema method
-Mongo no care for creating schema. Mongo work with no schema.
- - -Delete Data
-For deleteAll(true, false) calls - conditions will arrive here as true - account for that and convert to an empty array For deleteAll(array('some conditions')) calls - conditions will arrive here as: array( Alias._id => array(1, 2, 3, ...) )
This format won't be understood by mongodb, it'll find 0 rows. convert to:
array( Alias._id => array('$in' => array(1, 2, 3, ...)) )
- - -deriveSchemaFromData method
- - -Describe
-Automatically bind the schemaless behavior if there is no explicit mongo schema. When called, if there is model data it will be used to derive a schema. a row is plucked out of the db and the data obtained used to derive the schema.
- - -Disconnect from the database
- - -distinct method
- - -dropSchema method
-Return a command to drop each table
- - -ensureIndex method
- - -execute method
-If there is no query or the query is true, execute has probably been called as part of a db-agnostic process which does not have a mongo equivalent, don't do anything.
- - -get MongoDB Collection Object
- - -get MongoDB Object
- - -group method
- - -array() Set params same as MongoCollection::group() key,initial, reduce, options(conditions, finalize)
Ex. $params = array( 'key' => array('field' => true), 'initial' => array('csum' => 0), 'reduce' => 'function(obj, prev){prev.csum += 1;}', 'options' => array( 'condition' => array('age' => array('$gt' => 20)), 'finalize' => array(), ), );
Inserts multiple values into a table
- - -check connection to the database
- - -isInterfaceSupported method
-listSources is infact supported, however: cake expects it to return a complete list of all possible sources in the selected db - the possible list of collections is infinte, so it's faster and simpler to tell cake that the interface is /not/ supported so it assumes that <insert name of your table here> exist
- - -Get list of available Collections
- - -logQuery method
-Set timers, errors and refer to the parent If there are arguments passed - inject them into the query Show MongoIds in a copy-and-paste-into-mongo format
- - -mapReduce
- - -Quotes identifiers.
-MongoDb does not need identifiers quoted, so this method simply returns the identifier.
- - -query method If call getMongoDb() from model, this method call getMongoDb().
- - -Read Data
-For deleteAll(true) calls - the conditions will arrive here as true - account for that and switch to an empty array
- - -rollback method
-MongoDB doesn't support transactions
- - -setTimeout Method
-Sets the MongoCursor timeout so long queries (like map / reduce) can run at will. Expressed in milliseconds, for an infinite timeout, set to -1
- - -Deletes all the records in a table
- - -Update Data
-This method uses $set operator automatically with MongoCollection::update(). If you don't want to use $set operator, you can chose any one as follw.
Update multiple Record
- - -Prepares a value, or an array of values for database queries by quoting and escaping them.
- - -convertId method
-$conditions is used to determine if it should try to auto correct _id => array() queries it only appies to conditions, hence the param name
- - -prepareLogQuery method
-Any prep work to log a query
- - -Set empty values, arrays or integers, for the variables Mongo uses
- - -stringify method
-Takes an array of args as an input and returns an array of json-encoded strings. Takes care of any objects the arrays might be holding (MongoID);
- - -Convert automatically array('Model.field' => 'foo') to array('field' => 'foo')
-This introduces the limitation that you can't have a (nested) field with the same name as the model But it's a small price to pay to be able to use other behaviors/functionality with mongoDB
- - -- Documentation generated on Tue, 26 Jul 2011 01:09:01 +0900 by phpDocumentor 1.4.3 -
-A CakePHP datasource for the mongoDB (http://www.mongodb.org/) document-oriented database.
-This datasource uses Pecl Mongo (http://php.net/mongo) and is thus dependent on PHP 5.0 and greater.
Original implementation by ichikaway(Yasushi Ichikawa) http://github.com/ichikaway/
Reference: Nate Abele's lithium mongoDB datasource (http://li3.rad-dev.org/) Joél Perras' divan(http://github.com/jperras/divan/)
Copyright 2010, Yasushi Ichikawa http://github.com/ichikaway/
Contributors: Predominant, Jrbasso, tkyk, AD7six
Licensed under The MIT License Redistributions of files must retain the above copyright notice.
- - -Class | -Description | -
---|---|
- MongodbSource - | -- MongoDB Source - | -
- Documentation generated on Tue, 26 Jul 2011 01:09:01 +0900 by phpDocumentor 1.4.3 -
-