From f759ee1865f8e97c438f7a6128ac91822f799b04 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 5 Sep 2013 09:47:41 +0200 Subject: [PATCH] Elasticsearch WIP --- framework/yii/db/elasticsearch/ActiveQuery.php | 343 +++++++++++++++ framework/yii/db/elasticsearch/ActiveRecord.php | 543 ++++++++++++++++++++++++ framework/yii/db/elasticsearch/Connection.php | 144 +++++++ framework/yii/db/elasticsearch/Node.php | 23 + 4 files changed, 1053 insertions(+) create mode 100644 framework/yii/db/elasticsearch/ActiveQuery.php create mode 100644 framework/yii/db/elasticsearch/ActiveRecord.php create mode 100644 framework/yii/db/elasticsearch/Connection.php create mode 100644 framework/yii/db/elasticsearch/Node.php diff --git a/framework/yii/db/elasticsearch/ActiveQuery.php b/framework/yii/db/elasticsearch/ActiveQuery.php new file mode 100644 index 0000000..a3c5e13 --- /dev/null +++ b/framework/yii/db/elasticsearch/ActiveQuery.php @@ -0,0 +1,343 @@ + + * @since 2.0 + */ +class ActiveQuery extends \yii\base\Component +{ + /** + * @var string the name of the ActiveRecord class. + */ + public $modelClass; + /** + * @var array list of relations that this query should be performed with + */ + public $with; + /** + * @var string the name of the column by which query results should be indexed by. + * This is only used when the query result is returned as an array when calling [[all()]]. + */ + public $indexBy; + /** + * @var boolean whether to return each record as an array. If false (default), an object + * of [[modelClass]] will be created to represent each record. + */ + public $asArray; + /** + * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit. + */ + public $limit; + /** + * @var integer zero-based offset from where the records are to be returned. + * If not set, it means starting from the beginning. + * If less than zero it means starting n elements from the end. + */ + public $offset; + /** + * @var array array of primary keys of the records to find. + */ + public $primaryKeys; + + /** + * List of multiple pks must be zero based + * + * @param $primaryKeys + * @return ActiveQuery + */ + public function primaryKeys($primaryKeys) { + if (is_array($primaryKeys) && isset($primaryKeys[0])) { + $this->primaryKeys = $primaryKeys; + } else { + $this->primaryKeys = array($primaryKeys); + } + + return $this; + } + + /** + * Executes query and returns all results as an array. + * @return array the query results. If the query results in nothing, an empty array will be returned. + */ + public function all() + { + $modelClass = $this->modelClass; + /** @var Connection $db */ + $db = $modelClass::getDb(); + if (($primaryKeys = $this->primaryKeys) === null) { + $start = $this->offset === null ? 0 : $this->offset; + $end = $this->limit === null ? -1 : $start + $this->limit; + $primaryKeys = $db->executeCommand('LRANGE', array($modelClass::tableName(), $start, $end)); + } + $rows = array(); + foreach($primaryKeys as $pk) { + $key = $modelClass::tableName() . ':a:' . $modelClass::hashPk($pk); + // get attributes + $data = $db->executeCommand('HGETALL', array($key)); + $row = array(); + for($i=0;$icreateModels($rows); + if (!empty($this->with)) { + $this->populateRelations($models, $this->with); + } + return $models; + } else { + return array(); + } + } + + /** + * Executes query and returns a single row of result. + * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]], + * the query result may be either an array or an ActiveRecord object. Null will be returned + * if the query results in nothing. + */ + public function one() + { + $modelClass = $this->modelClass; + /** @var Connection $db */ + $db = $modelClass::getDb(); + if (($primaryKeys = $this->primaryKeys) === null) { + $start = $this->offset === null ? 0 : $this->offset; + $primaryKeys = $db->executeCommand('LRANGE', array($modelClass::tableName(), $start, $start + 1)); + } + $pk = reset($primaryKeys); + $key = $modelClass::tableName() . ':a:' . $modelClass::hashPk($pk); + // get attributes + $data = $db->executeCommand('HGETALL', array($key)); + if ($data === array()) { + return null; + } + $row = array(); + for($i=0;$iasArray) { + /** @var $class ActiveRecord */ + $class = $this->modelClass; + $model = $class::create($row); + if (!empty($this->with)) { + $models = array($model); + $this->populateRelations($models, $this->with); + $model = $models[0]; + } + return $model; + } else { + return $row; + } + } + + /** + * Returns the number of records. + * @param string $q the COUNT expression. Defaults to '*'. + * Make sure you properly quote column names. + * @return integer number of records + */ + public function count() + { + $modelClass = $this->modelClass; + /** @var Connection $db */ + $db = $modelClass::getDb(); + return $db->executeCommand('LLEN', array($modelClass::tableName())); + } + + /** + * Returns the query result as a scalar value. + * The value returned will be the first column in the first row of the query results. + * @return string|boolean the value of the first column in the first row of the query result. + * False is returned if the query result is empty. + */ + public function scalar($column) + { + $record = $this->one(); + return $record->$column; + } + + /** + * Returns a value indicating whether the query result contains any row of data. + * @return boolean whether the query result contains any row of data. + */ + public function exists() + { + return $this->one() !== null; + } + + + /** + * Sets the [[asArray]] property. + * TODO: refactor, it is duplicated from yii/db/ActiveQuery + * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. + * @return ActiveQuery the query object itself + */ + public function asArray($value = true) + { + $this->asArray = $value; + return $this; + } + + /** + * Sets the LIMIT part of the query. + * TODO: refactor, it is duplicated from yii/db/Query + * @param integer $limit the limit + * @return Query the query object itself + */ + public function limit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Sets the OFFSET part of the query. + * TODO: refactor, it is duplicated from yii/db/Query + * @param integer $offset the offset + * @return Query the query object itself + */ + public function offset($offset) + { + $this->offset = $offset; + return $this; + } + + /** + * Specifies the relations with which this query should be performed. + * + * The parameters to this method can be either one or multiple strings, or a single array + * of relation names and the optional callbacks to customize the relations. + * + * The followings are some usage examples: + * + * ~~~ + * // find customers together with their orders and country + * Customer::find()->with('orders', 'country')->all(); + * // find customers together with their country and orders of status 1 + * Customer::find()->with(array( + * 'orders' => function($query) { + * $query->andWhere('status = 1'); + * }, + * 'country', + * ))->all(); + * ~~~ + * + * TODO: refactor, it is duplicated from yii/db/ActiveQuery + * @return ActiveQuery the query object itself + */ + public function with() + { + $this->with = func_get_args(); + if (isset($this->with[0]) && is_array($this->with[0])) { + // the parameter is given as an array + $this->with = $this->with[0]; + } + return $this; + } + + /** + * Sets the [[indexBy]] property. + * TODO: refactor, it is duplicated from yii/db/ActiveQuery + * @param string $column the name of the column by which the query results should be indexed by. + * @return ActiveQuery the query object itself + */ + public function indexBy($column) + { + $this->indexBy = $column; + return $this; + } + + // TODO: refactor, it is duplicated from yii/db/ActiveQuery + private function createModels($rows) + { + $models = array(); + if ($this->asArray) { + if ($this->indexBy === null) { + return $rows; + } + foreach ($rows as $row) { + $models[$row[$this->indexBy]] = $row; + } + } else { + /** @var $class ActiveRecord */ + $class = $this->modelClass; + if ($this->indexBy === null) { + foreach ($rows as $row) { + $models[] = $class::create($row); + } + } else { + foreach ($rows as $row) { + $model = $class::create($row); + $models[$model->{$this->indexBy}] = $model; + } + } + } + return $models; + } + + // TODO: refactor, it is duplicated from yii/db/ActiveQuery + private function populateRelations(&$models, $with) + { + $primaryModel = new $this->modelClass; + $relations = $this->normalizeRelations($primaryModel, $with); + foreach ($relations as $name => $relation) { + if ($relation->asArray === null) { + // inherit asArray from primary query + $relation->asArray = $this->asArray; + } + $relation->findWith($name, $models); + } + } + + /** + * TODO: refactor, it is duplicated from yii/db/ActiveQuery + * @param ActiveRecord $model + * @param array $with + * @return ActiveRelation[] + */ + private function normalizeRelations($model, $with) + { + $relations = array(); + foreach ($with as $name => $callback) { + if (is_integer($name)) { + $name = $callback; + $callback = null; + } + if (($pos = strpos($name, '.')) !== false) { + // with sub-relations + $childName = substr($name, $pos + 1); + $name = substr($name, 0, $pos); + } else { + $childName = null; + } + + $t = strtolower($name); + if (!isset($relations[$t])) { + $relation = $model->getRelation($name); + $relation->primaryModel = null; + $relations[$t] = $relation; + } else { + $relation = $relations[$t]; + } + + if (isset($childName)) { + $relation->with[$childName] = $callback; + } elseif ($callback !== null) { + call_user_func($callback, $relation); + } + } + return $relations; + } +} diff --git a/framework/yii/db/elasticsearch/ActiveRecord.php b/framework/yii/db/elasticsearch/ActiveRecord.php new file mode 100644 index 0000000..4282e8d --- /dev/null +++ b/framework/yii/db/elasticsearch/ActiveRecord.php @@ -0,0 +1,543 @@ + + * @since 2.0 + */ +abstract class ActiveRecord extends \yii\db\ActiveRecord +{ + /** + * Returns the database connection used by this AR class. + * By default, the "elasticsearch" application component is used as the database connection. + * You may override this method if you want to use a different database connection. + * @return Connection the database connection used by this AR class. + */ + public static function getDb() + { + return \Yii::$app->elasticsearch; + } + + public static function primaryKey() + { + return array('id'); + } + + /** + * Creates an [[ActiveQuery]] instance for query purpose. + * + * @include @yii/db/ActiveRecord-find.md + * + * @param mixed $q the query parameter. This can be one of the followings: + * + * - a scalar value (integer or string): query by a single primary key value and return the + * corresponding record. + * - an array of name-value pairs: query by a set of column values and return a single record matching all of them. + * - null: return a new [[ActiveQuery]] object for further query purpose. + * + * @return ActiveQuery|ActiveRecord|null When `$q` is null, a new [[ActiveQuery]] instance + * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be + * returned (null will be returned if there is no matching). + * @see createQuery() + */ + public static function find($q = null) // TODO optimize API + { + $query = static::createQuery(); + if (is_array($q)) { + return $query->primaryKeys($q)->one(); + } elseif ($q !== null) { + // query by primary key + $primaryKey = static::primaryKey(); + return $query->primaryKeys(array($primaryKey[0] => $q))->one(); + } + return $query; + } + + /** + * @inheritdoc + */ + public static function findBySql($sql, $params = array()) + { + throw new NotSupportedException('findBySql() is not supported by elasticsearch ActiveRecord'); + } + + /** + * Creates an [[ActiveQuery]] instance. + * This method is called by [[find()]], [[findBySql()]] and [[count()]] to start a SELECT query. + * You may override this method to return a customized query (e.g. `CustomerQuery` specified + * written for querying `Customer` purpose.) + * @return ActiveQuery the newly created [[ActiveQuery]] instance. + */ + public static function createQuery() + { + return new ActiveQuery(array( + 'modelClass' => get_called_class(), + )); + } + + /** + * Declares the name of the database table associated with this AR class. + * @return string the table name + */ + public static function tableName() + { + return static::getTableSchema()->name; + } + + public static function indexName() + { + return static::getTableSchema()->name; + } + + /** + * Returns the schema information of the DB table associated with this AR class. + * @return TableSchema the schema information of the DB table associated with this AR class. + */ + public static function getTableSchema() + { + // TODO should be cached + throw new InvalidConfigException(__CLASS__.'::getTableSchema() needs to be overridden in subclasses and return a TableSchema.'); + } + + /** + * Inserts a row into the associated database table using the attribute values of this record. + * + * This method performs the following steps in order: + * + * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation + * fails, it will skip the rest of the steps; + * 2. call [[afterValidate()]] when `$runValidation` is true. + * 3. call [[beforeSave()]]. If the method returns false, it will skip the + * rest of the steps; + * 4. insert the record into database. If this fails, it will skip the rest of the steps; + * 5. call [[afterSave()]]; + * + * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], + * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]] + * will be raised by the corresponding methods. + * + * Only the [[changedAttributes|changed attribute values]] will be inserted into database. + * + * If the table's primary key is auto-incremental and is null during insertion, + * it will be populated with the actual value after insertion. + * + * For example, to insert a customer record: + * + * ~~~ + * $customer = new Customer; + * $customer->name = $name; + * $customer->email = $email; + * $customer->insert(); + * ~~~ + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be inserted into the database. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the attributes are valid and the record is inserted successfully. + */ + public function insert($runValidation = true, $attributes = null) + { + if ($runValidation && !$this->validate($attributes)) { + return false; + } + if ($this->beforeSave(true)) { + $db = static::getDb(); + $values = $this->getDirtyAttributes($attributes); + $pk = array(); + foreach ($this->primaryKey() as $key) { + $pk[$key] = $values[$key] = $this->getAttribute($key); + if ($pk[$key] === null) { + $pk[$key] = $values[$key] = 0; // TODO add support for incrementing PK + $this->setAttribute($key, $values[$key]); + } + } + + // TODO store record in index + + $this->setOldAttributes($values); + $this->afterSave(true); + return true; + } + return false; + } + + /** + * Updates the whole table using the provided attribute values and conditions. + * For example, to change the status to be 1 for all customers whose status is 2: + * + * ~~~ + * Customer::updateAll(array('status' => 1), 'status = 2'); + * ~~~ + * + * @param array $attributes attribute values (name-value pairs) to be saved into the table + * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return integer the number of rows updated + */ + public static function updateAll($attributes, $condition = '', $params = array()) + { + $db = static::getDb(); + + // TODO massive update (do a find and then update each record) + + if (empty($attributes)) { + return 0; + } + $n=0; +// foreach(... as $pk) { +// +// // TODO update records +// +// $n++; +// } + + return $n; + } + + /** + * Updates the whole table using the provided counter changes and conditions. + * For example, to increment all customers' age by 1, + * + * ~~~ + * Customer::updateAllCounters(array('age' => 1)); + * ~~~ + * + * @param array $counters the counters to be updated (attribute name => increment value). + * Use negative values if you want to decrement the counters. + * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * Do not name the parameters as `:bp0`, `:bp1`, etc., because they are used internally by this method. + * @return integer the number of rows updated + */ + public static function updateAllCounters($counters, $condition = '', $params = array()) + { + // TODO implement + throw new NotSupportedException('update counters is not supported by elasticsearch.'); + } + + /** + * Deletes rows in the table using the provided conditions. + * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. + * + * For example, to delete all customers whose status is 3: + * + * ~~~ + * Customer::deleteAll('status = 3'); + * ~~~ + * + * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name=>value) to be bound to the query. + * @return integer the number of rows deleted + */ + public static function deleteAll($condition = '', $params = array()) + { + $db = static::getDb(); + + // TODO massive delete (do a find and then delete each record) + + if (empty($condition)) { + return 0; + } + $n = 0; +// foreach($condition as $pk) { +// +// $n++; +// } + return $n; + } + + /** + * Declares a `has-one` relation. + * The declaration is returned in terms of an [[ActiveRelation]] instance + * through which the related record can be queried and retrieved back. + * + * A `has-one` relation means that there is at most one related record matching + * the criteria set by this relation, e.g., a customer has one country. + * + * For example, to declare the `country` relation for `Customer` class, we can write + * the following code in the `Customer` class: + * + * ~~~ + * public function getCountry() + * { + * return $this->hasOne('Country', array('id' => 'country_id')); + * } + * ~~~ + * + * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name + * in the related class `Country`, while the 'country_id' value refers to an attribute name + * in the current AR class. + * + * Call methods declared in [[ActiveRelation]] to further customize the relation. + * + * @param string $class the class name of the related record + * @param array $link the primary-foreign key constraint. The keys of the array refer to + * the columns in the table associated with the `$class` model, while the values of the + * array refer to the corresponding columns in the table associated with this AR class. + * @return ActiveRelation the relation object. + */ + public function hasOne($class, $link) + { + return new ActiveRelation(array( + 'modelClass' => $this->getNamespacedClass($class), + 'primaryModel' => $this, + 'link' => $link, + 'multiple' => false, + )); + } + + /** + * Declares a `has-many` relation. + * The declaration is returned in terms of an [[ActiveRelation]] instance + * through which the related record can be queried and retrieved back. + * + * A `has-many` relation means that there are multiple related records matching + * the criteria set by this relation, e.g., a customer has many orders. + * + * For example, to declare the `orders` relation for `Customer` class, we can write + * the following code in the `Customer` class: + * + * ~~~ + * public function getOrders() + * { + * return $this->hasMany('Order', array('customer_id' => 'id')); + * } + * ~~~ + * + * Note that in the above, the 'customer_id' key in the `$link` parameter refers to + * an attribute name in the related class `Order`, while the 'id' value refers to + * an attribute name in the current AR class. + * + * @param string $class the class name of the related record + * @param array $link the primary-foreign key constraint. The keys of the array refer to + * the columns in the table associated with the `$class` model, while the values of the + * array refer to the corresponding columns in the table associated with this AR class. + * @return ActiveRelation the relation object. + */ + public function hasMany($class, $link) + { + return new ActiveRelation(array( + 'modelClass' => $this->getNamespacedClass($class), + 'primaryModel' => $this, + 'link' => $link, + 'multiple' => true, + )); + } + + /** + * Returns the relation object with the specified name. + * A relation is defined by a getter method which returns an [[ActiveRelation]] object. + * It can be declared in either the Active Record class itself or one of its behaviors. + * @param string $name the relation name + * @return ActiveRelation the relation object + * @throws InvalidParamException if the named relation does not exist. + */ + public function getRelation($name) + { + $getter = 'get' . $name; + try { + $relation = $this->$getter(); + if ($relation instanceof ActiveRelation) { + return $relation; + } + } catch (UnknownMethodException $e) { + } + throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".'); + } + + /** + * Establishes the relationship between two models. + * + * The relationship is established by setting the foreign key value(s) in one model + * to be the corresponding primary key value(s) in the other model. + * The model with the foreign key will be saved into database without performing validation. + * + * If the relationship involves a pivot table, a new row will be inserted into the + * pivot table which contains the primary key values from both models. + * + * Note that this method requires that the primary key value is not null. + * + * @param string $name the name of the relationship + * @param ActiveRecord $model the model to be linked with the current one. + * @param array $extraColumns additional column values to be saved into the pivot table. + * This parameter is only meaningful for a relationship involving a pivot table + * (i.e., a relation set with `[[ActiveRelation::via()]]` or `[[ActiveRelation::viaTable()]]`.) + * @throws InvalidCallException if the method is unable to link two models. + */ + public function link($name, $model, $extraColumns = array()) + { + $relation = $this->getRelation($name); + + if ($relation->via !== null) { + // TODO + + + } else { + $p1 = $model->isPrimaryKey(array_keys($relation->link)); + $p2 = $this->isPrimaryKey(array_values($relation->link)); + if ($p1 && $p2) { + if ($this->getIsNewRecord() && $model->getIsNewRecord()) { + throw new InvalidCallException('Unable to link models: both models are newly created.'); + } elseif ($this->getIsNewRecord()) { + $this->bindModels(array_flip($relation->link), $this, $model); + } else { + $this->bindModels($relation->link, $model, $this); + } + } elseif ($p1) { + $this->bindModels(array_flip($relation->link), $this, $model); + } elseif ($p2) { + $this->bindModels($relation->link, $model, $this); + } else { + throw new InvalidCallException('Unable to link models: the link does not involve any primary key.'); + } + } + + // update lazily loaded related objects + if (!$relation->multiple) { + $this->_related[$name] = $model; + } elseif (isset($this->_related[$name])) { + if ($relation->indexBy !== null) { + $indexBy = $relation->indexBy; + $this->_related[$name][$model->$indexBy] = $model; + } else { + $this->_related[$name][] = $model; + } + } + } + + /** + * @param array $link + * @param ActiveRecord $foreignModel + * @param ActiveRecord $primaryModel + * @throws InvalidCallException + */ + private function bindModels($link, $foreignModel, $primaryModel) + { + foreach ($link as $fk => $pk) { + $value = $primaryModel->$pk; + if ($value === null) { + throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.'); + } + $foreignModel->$fk = $value; + } + $foreignModel->save(false); + } + + /** + * Destroys the relationship between two models. + * + * The model with the foreign key of the relationship will be deleted if `$delete` is true. + * Otherwise, the foreign key will be set null and the model will be saved without validation. + * + * @param string $name the name of the relationship. + * @param ActiveRecord $model the model to be unlinked from the current one. + * @param boolean $delete whether to delete the model that contains the foreign key. + * If false, the model's foreign key will be set null and saved. + * If true, the model containing the foreign key will be deleted. + * @throws InvalidCallException if the models cannot be unlinked + */ + public function unlink($name, $model, $delete = false) + { + // TODO + $relation = $this->getRelation($name); + + if ($relation->via !== null) { + if (is_array($relation->via)) { + /** @var $viaRelation ActiveRelation */ + list($viaName, $viaRelation) = $relation->via; + /** @var $viaClass ActiveRecord */ + $viaClass = $viaRelation->modelClass; + $viaTable = $viaClass::tableName(); + unset($this->_related[strtolower($viaName)]); + } else { + $viaRelation = $relation->via; + $viaTable = reset($relation->via->from); + } + $columns = array(); + foreach ($viaRelation->link as $a => $b) { + $columns[$a] = $this->$b; + } + foreach ($relation->link as $a => $b) { + $columns[$b] = $model->$a; + } + $command = static::getDb()->createCommand(); + if ($delete) { + $command->delete($viaTable, $columns)->execute(); + } else { + $nulls = array(); + foreach (array_keys($columns) as $a) { + $nulls[$a] = null; + } + $command->update($viaTable, $nulls, $columns)->execute(); + } + } else { + $p1 = $model->isPrimaryKey(array_keys($relation->link)); + $p2 = $this->isPrimaryKey(array_values($relation->link)); + if ($p1 && $p2 || $p2) { + foreach ($relation->link as $a => $b) { + $model->$a = null; + } + $delete ? $model->delete() : $model->save(false); + } elseif ($p1) { + foreach ($relation->link as $b) { + $this->$b = null; + } + $delete ? $this->delete() : $this->save(false); + } else { + throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.'); + } + } + + if (!$relation->multiple) { + unset($this->_related[$name]); + } elseif (isset($this->_related[$name])) { + /** @var $b ActiveRecord */ + foreach ($this->_related[$name] as $a => $b) { + if ($model->getPrimaryKey() == $b->getPrimaryKey()) { + unset($this->_related[$name][$a]); + } + } + } + } + + /** + * TODO duplicate code, refactor + * @param array $keys + * @return boolean + */ + private function isPrimaryKey($keys) + { + $pks = $this->primaryKey(); + foreach ($keys as $key) { + if (!in_array($key, $pks, true)) { + return false; + } + } + return true; + } + + + + // TODO implement link and unlink +} diff --git a/framework/yii/db/elasticsearch/Connection.php b/framework/yii/db/elasticsearch/Connection.php new file mode 100644 index 0000000..6524369 --- /dev/null +++ b/framework/yii/db/elasticsearch/Connection.php @@ -0,0 +1,144 @@ + + * @since 2.0 + */ +class Connection extends Component +{ + /** + * @event Event an event that is triggered after a DB connection is established + */ + const EVENT_AFTER_OPEN = 'afterOpen'; + + // TODO add autodetection of cluster nodes + public $nodes = array(); + + // TODO use timeouts + /** + * @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout") + */ + public $connectionTimeout = null; + /** + * @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used. + */ + public $dataTimeout = null; + + + + public function init() + { + if ($this->nodes === array()) { + throw new InvalidConfigException('elasticsearch needs at least one node.'); + } + } + + /** + * Closes the connection when this component is being serialized. + * @return array + */ + public function __sleep() + { + $this->close(); + return array_keys(get_object_vars($this)); + } + + /** + * Returns a value indicating whether the DB connection is established. + * @return boolean whether the DB connection is established + */ + public function getIsActive() + { + return false; // TODO implement + } + + /** + * Establishes a DB connection. + * It does nothing if a DB connection has already been established. + * @throws Exception if connection fails + */ + public function open() + { +/* if ($this->_socket === null) { + if (empty($this->dsn)) { + throw new InvalidConfigException('Connection.dsn cannot be empty.'); + } + $dsn = explode('/', $this->dsn); + $host = $dsn[2]; + if (strpos($host, ':')===false) { + $host .= ':6379'; + } + $db = isset($dsn[3]) ? $dsn[3] : 0; + + \Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__); + $this->_socket = @stream_socket_client( + $host, + $errorNumber, + $errorDescription, + $this->connectionTimeout ? $this->connectionTimeout : ini_get("default_socket_timeout") + ); + if ($this->_socket) { + if ($this->dataTimeout !== null) { + stream_set_timeout($this->_socket, $timeout=(int)$this->dataTimeout, (int) (($this->dataTimeout - $timeout) * 1000000)); + } + if ($this->password !== null) { + $this->executeCommand('AUTH', array($this->password)); + } + $this->executeCommand('SELECT', array($db)); + $this->initConnection(); + } else { + \Yii::error("Failed to open DB connection ({$this->dsn}): " . $errorNumber . ' - ' . $errorDescription, __CLASS__); + $message = YII_DEBUG ? 'Failed to open DB connection: ' . $errorNumber . ' - ' . $errorDescription : 'Failed to open DB connection.'; + throw new Exception($message, $errorDescription, (int)$errorNumber); + } + }*/ + // TODO implement + } + + /** + * Closes the currently active DB connection. + * It does nothing if the connection is already closed. + */ + public function close() + { + // TODO implement +/* if ($this->_socket !== null) { + \Yii::trace('Closing DB connection: ' . $this->dsn, __CLASS__); + $this->executeCommand('QUIT'); + stream_socket_shutdown($this->_socket, STREAM_SHUT_RDWR); + $this->_socket = null; + $this->_transaction = null; + }*/ + } + + /** + * Initializes the DB connection. + * This method is invoked right after the DB connection is established. + * The default implementation triggers an [[EVENT_AFTER_OPEN]] event. + */ + protected function initConnection() + { + $this->trigger(self::EVENT_AFTER_OPEN); + } + + /** + * Returns the name of the DB driver for the current [[dsn]]. + * @return string name of the DB driver + */ + public function getDriverName() + { + return 'elasticsearch'; + } +} \ No newline at end of file diff --git a/framework/yii/db/elasticsearch/Node.php b/framework/yii/db/elasticsearch/Node.php new file mode 100644 index 0000000..280d157 --- /dev/null +++ b/framework/yii/db/elasticsearch/Node.php @@ -0,0 +1,23 @@ + + * @since 2.0 + */ +class Node extends Object +{ + public $host; + public $port; +} \ No newline at end of file