diff --git a/extensions/yii/elasticsearch/ActiveQuery.php b/extensions/yii/elasticsearch/ActiveQuery.php index 96d6681..d67327c 100644 --- a/extensions/yii/elasticsearch/ActiveQuery.php +++ b/extensions/yii/elasticsearch/ActiveQuery.php @@ -90,16 +90,26 @@ class ActiveQuery extends Query implements ActiveQueryInterface } unset($row); } + /** @var ActiveRecord $modelClass */ + $modelClass = $this->modelClass; + $pk = $modelClass::primaryKey()[0]; if ($this->asArray && $this->indexBy) { foreach ($result['hits']['hits'] as &$row) { - $row['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $row['_id']; + if ($pk === '_id') { + $row['_source']['_id'] = $row['_id']; + } + $row['_source']['_score'] = $row['_score']; $row = $row['_source']; } + unset($row); } $models = $this->createModels($result['hits']['hits']); if ($this->asArray && !$this->indexBy) { foreach($models as $key => $model) { - $model['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $model['_id']; + if ($pk === '_id') { + $model['_source']['_id'] = $model['_id']; + } + $model['_source']['_score'] = $model['_score']; $models[$key] = $model['_source']; } } @@ -123,8 +133,14 @@ class ActiveQuery extends Query implements ActiveQueryInterface return null; } if ($this->asArray) { + /** @var ActiveRecord $modelClass */ + $modelClass = $this->modelClass; $model = $result['_source']; - $model[ActiveRecord::PRIMARY_KEY_NAME] = $result['_id']; + $pk = $modelClass::primaryKey()[0]; + if ($pk === '_id') { + $model['_id'] = $result['_id']; + } + $model['_score'] = $result['_score']; } else { /** @var ActiveRecord $class */ $class = $this->modelClass; @@ -147,8 +163,14 @@ class ActiveQuery extends Query implements ActiveQueryInterface if (!empty($result['hits']['hits'])) { $models = $this->createModels($result['hits']['hits']); if ($this->asArray) { + /** @var ActiveRecord $modelClass */ + $modelClass = $this->modelClass; + $pk = $modelClass::primaryKey()[0]; foreach($models as $key => $model) { - $model['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $model['_id']; + if ($pk === '_id') { + $model['_source']['_id'] = $model['_id']; + } + $model['_source']['_score'] = $model['_score']; $models[$key] = $model['_source']; } } @@ -167,7 +189,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface { $record = parent::one($db); if ($record !== false) { - if ($field == ActiveRecord::PRIMARY_KEY_NAME) { + if ($field == '_id') { return $record['_id']; } elseif (isset($record['_source'][$field])) { return $record['_source'][$field]; @@ -181,7 +203,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface */ public function column($field, $db = null) { - if ($field == ActiveRecord::PRIMARY_KEY_NAME) { + if ($field == '_id') { $command = $this->createCommand($db); $command->queryParts['fields'] = []; $result = $command->search(); diff --git a/extensions/yii/elasticsearch/ActiveRecord.php b/extensions/yii/elasticsearch/ActiveRecord.php index 9cc6549..f91df2c 100644 --- a/extensions/yii/elasticsearch/ActiveRecord.php +++ b/extensions/yii/elasticsearch/ActiveRecord.php @@ -44,9 +44,8 @@ use yii\helpers\StringHelper; */ class ActiveRecord extends BaseActiveRecord { - const PRIMARY_KEY_NAME = 'id'; - private $_id; + private $_score; private $_version; /** @@ -67,14 +66,6 @@ class ActiveRecord extends BaseActiveRecord { $query = static::createQuery(); if (is_array($q)) { - if (count($q) == 1 && (array_key_exists(ActiveRecord::PRIMARY_KEY_NAME, $q)) && $query->where === null) { - $pk = $q[ActiveRecord::PRIMARY_KEY_NAME]; - if (is_array($pk)) { - return static::mget($pk); - } else { - return static::get($pk); - } - } return $query->andWhere($q)->one(); } elseif ($q !== null) { return static::get($q); @@ -155,9 +146,12 @@ class ActiveRecord extends BaseActiveRecord // TODO implement copy and move as pk change is not possible - public function getId() + /** + * @return float returns the score of this record when it was retrieved via a [[find()]] query. + */ + public function getScore() { - return $this->_id; + return $this->_score; } /** @@ -165,10 +159,11 @@ class ActiveRecord extends BaseActiveRecord * @param mixed $value * @throws \yii\base\InvalidCallException when record is not new */ - public function setId($value) + public function setPrimaryKey($value) { - if ($this->isNewRecord) { - $this->_id = $value; + $pk = static::primaryKey()[0]; + if ($this->getIsNewRecord() || $pk != '_id') { + $this->$pk = $value; } else { throw new InvalidCallException('Changing the primaryKey of an already saved record is not allowed.'); } @@ -179,10 +174,11 @@ class ActiveRecord extends BaseActiveRecord */ public function getPrimaryKey($asArray = false) { + $pk = static::primaryKey()[0]; if ($asArray) { - return [ActiveRecord::PRIMARY_KEY_NAME => $this->_id]; + return [$pk => $this->$pk]; } else { - return $this->_id; + return $this->$pk; } } @@ -191,30 +187,53 @@ class ActiveRecord extends BaseActiveRecord */ public function getOldPrimaryKey($asArray = false) { - $id = $this->isNewRecord ? null : $this->_id; + $pk = static::primaryKey()[0]; + if ($this->getIsNewRecord()) { + $id = null; + } elseif ($pk == '_id') { + $id = $this->_id; + } else { + $id = $this->getOldAttribute($pk); + } if ($asArray) { - return [ActiveRecord::PRIMARY_KEY_NAME => $id]; + return [$pk => $id]; } else { - return $this->_id; + return $id; } } /** - * This method defines the primary. + * This method defines the attribute that uniquely identifies a record. + * + * The primaryKey for elasticsearch documents is the `_id` field by default. This field is not part of the + * ActiveRecord attributes so you should never add `_id` to the list of [[attributes()|attributes]]. + * + * You may overide this method to define the primary key name when you have defined + * [path mapping](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-id-field.html) + * for the `_id` field so that it is part of the `_source` and thus part of the [[attributes()|attributes]]. * - * The primaryKey for elasticsearch documents is always `primaryKey`. It can not be changed. + * Note that elasticsearch only supports _one_ attribute to be the primary key. However to match the signature + * of the [[\yii\db\ActiveRecordInterface|ActiveRecordInterface]] this methods returns an array instead of a + * single string. * - * @return string[] the primary keys of this record. + * @return string[] array of primary key attributes. Only the first element of the array will be used. */ public static function primaryKey() { - return [ActiveRecord::PRIMARY_KEY_NAME]; + return ['_id']; } /** * Returns the list of all attribute names of the model. + * * This method must be overridden by child classes to define available attributes. - * @return array list of attribute names. + * + * Attributes are names of fields of the corresponding elasticsearch document. + * The primaryKey for elasticsearch documents is the `_id` field by default which is not part of the attributes. + * You may define [path mapping](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-id-field.html) + * for the `_id` field so that it is part of the `_source` fields and thus becomes part of the attributes. + * + * @return string[] list of attribute names. */ public function attributes() { @@ -246,8 +265,11 @@ class ActiveRecord extends BaseActiveRecord */ public static function create($row) { - $row['_source'][ActiveRecord::PRIMARY_KEY_NAME] = $row['_id']; $record = parent::create($row['_source']); + $pk = static::primaryKey()[0]; + $record->$pk = $row['_id']; + $record->_score = isset($row['_score']) ? $row['_score'] : null; + $record->_version = isset($row['_version']) ? $row['_version'] : null; // TODO version should always be available... return $record; } @@ -317,11 +339,16 @@ class ActiveRecord extends BaseActiveRecord $options ); - if (!$response['ok']) { + if (!isset($response['ok'])) { return false; } - $this->_id = $response['_id']; + $pk = static::primaryKey()[0]; + $this->$pk = $response['_id']; + if ($pk != '_id') { + $values[$pk] = $response['_id']; + } $this->_version = $response['_version']; + $this->_score = null; $this->setOldAttributes($values); $this->afterSave(true); return true; @@ -344,16 +371,17 @@ class ActiveRecord extends BaseActiveRecord */ public static function updateAll($attributes, $condition = []) { - if (count($condition) == 1 && isset($condition[ActiveRecord::PRIMARY_KEY_NAME])) { - $primaryKeys = (array) $condition[ActiveRecord::PRIMARY_KEY_NAME]; + $pkName = static::primaryKey()[0]; + if (count($condition) == 1 && isset($condition[$pkName])) { + $primaryKeys = is_array($condition[$pkName]) ? $condition[$pkName] : [$condition[$pkName]]; } else { - $primaryKeys = static::find()->where($condition)->column(ActiveRecord::PRIMARY_KEY_NAME); + $primaryKeys = static::find()->where($condition)->column($pkName); // TODO check whether this works with default pk _id } if (empty($primaryKeys)) { return 0; } $bulk = ''; - foreach((array) $primaryKeys as $pk) { + foreach($primaryKeys as $pk) { $action = Json::encode([ "update" => [ "_id" => $pk, @@ -371,11 +399,17 @@ class ActiveRecord extends BaseActiveRecord $url = [static::index(), static::type(), '_bulk']; $response = static::getDb()->post($url, [], $bulk); $n=0; + $errors = []; foreach($response['items'] as $item) { - if ($item['update']['ok']) { + if (isset($item['update']['error'])) { + $errors[] = $item['update']; + } elseif ($item['update']['ok']) { $n++; } } + if (!empty($errors)) { + throw new Exception(__METHOD__ . ' failed updating records.', $errors); + } return $n; } @@ -395,16 +429,17 @@ class ActiveRecord extends BaseActiveRecord */ public static function updateAllCounters($counters, $condition = []) { - if (count($condition) == 1 && isset($condition[ActiveRecord::PRIMARY_KEY_NAME])) { - $primaryKeys = (array) $condition[ActiveRecord::PRIMARY_KEY_NAME]; + $pkName = static::primaryKey()[0]; + if (count($condition) == 1 && isset($condition[$pkName])) { + $primaryKeys = is_array($condition[$pkName]) ? $condition[$pkName] : [$condition[$pkName]]; } else { - $primaryKeys = static::find()->where($condition)->column(ActiveRecord::PRIMARY_KEY_NAME); + $primaryKeys = static::find()->where($condition)->column($pkName); // TODO check whether this works with default pk _id } if (empty($primaryKeys) || empty($counters)) { return 0; } $bulk = ''; - foreach((array) $primaryKeys as $pk) { + foreach($primaryKeys as $pk) { $action = Json::encode([ "update" => [ "_id" => $pk, @@ -426,13 +461,18 @@ class ActiveRecord extends BaseActiveRecord // TODO do this via command $url = [static::index(), static::type(), '_bulk']; $response = static::getDb()->post($url, [], $bulk); - $n=0; + $errors = []; foreach($response['items'] as $item) { - if ($item['update']['ok']) { + if (isset($item['update']['error'])) { + $errors[] = $item['update']; + } elseif ($item['update']['ok']) { $n++; } } + if (!empty($errors)) { + throw new Exception(__METHOD__ . ' failed updating records counters.', $errors); + } return $n; } @@ -452,16 +492,17 @@ class ActiveRecord extends BaseActiveRecord */ public static function deleteAll($condition = []) { - if (count($condition) == 1 && isset($condition[ActiveRecord::PRIMARY_KEY_NAME])) { - $primaryKeys = (array) $condition[ActiveRecord::PRIMARY_KEY_NAME]; + $pkName = static::primaryKey()[0]; + if (count($condition) == 1 && isset($condition[$pkName])) { + $primaryKeys = is_array($condition[$pkName]) ? $condition[$pkName] : [$condition[$pkName]]; } else { - $primaryKeys = static::find()->where($condition)->column(ActiveRecord::PRIMARY_KEY_NAME); + $primaryKeys = static::find()->where($condition)->column($pkName); // TODO check whether this works with default pk _id } if (empty($primaryKeys)) { return 0; } $bulk = ''; - foreach((array) $primaryKeys as $pk) { + foreach($primaryKeys as $pk) { $bulk .= Json::encode([ "delete" => [ "_id" => $pk, @@ -475,11 +516,17 @@ class ActiveRecord extends BaseActiveRecord $url = [static::index(), static::type(), '_bulk']; $response = static::getDb()->post($url, [], $bulk); $n=0; + $errors = []; foreach($response['items'] as $item) { - if ($item['delete']['found'] && $item['delete']['ok']) { + if (isset($item['delete']['error'])) { + $errors[] = $item['delete']; + } elseif ($item['delete']['found'] && $item['delete']['ok']) { $n++; } } + if (!empty($errors)) { + throw new Exception(__METHOD__ . ' failed deleting records.', $errors); + } return $n; } } diff --git a/extensions/yii/elasticsearch/CHANGELOG.md b/extensions/yii/elasticsearch/CHANGELOG.md index 1a84ed3..55d52af 100644 --- a/extensions/yii/elasticsearch/CHANGELOG.md +++ b/extensions/yii/elasticsearch/CHANGELOG.md @@ -5,6 +5,8 @@ Yii Framework 2 elasticsearch extension Change Log ---------------------------- - Enh #1382: Added a debug toolbar panel for elasticsearch (cebe) +- Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe) +- Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe) 2.0.0 alpha, December 1, 2013 ----------------------------- diff --git a/extensions/yii/elasticsearch/QueryBuilder.php b/extensions/yii/elasticsearch/QueryBuilder.php index eae9451..aa37471 100644 --- a/extensions/yii/elasticsearch/QueryBuilder.php +++ b/extensions/yii/elasticsearch/QueryBuilder.php @@ -114,7 +114,7 @@ class QueryBuilder extends \yii\base\Object } else { $column = $name; } - if ($column == ActiveRecord::PRIMARY_KEY_NAME) { + if ($column == '_id') { $column = '_uid'; } @@ -176,7 +176,7 @@ class QueryBuilder extends \yii\base\Object { $parts = []; foreach($condition as $attribute => $value) { - if ($attribute == ActiveRecord::PRIMARY_KEY_NAME) { + if ($attribute == '_id') { if ($value == null) { // there is no null pk $parts[] = ['script' => ['script' => '0==1']]; } else { @@ -235,8 +235,8 @@ class QueryBuilder extends \yii\base\Object } list($column, $value1, $value2) = $operands; - if ($column == ActiveRecord::PRIMARY_KEY_NAME) { - throw new NotSupportedException('Between condition is not supported for primaryKey.'); + if ($column == '_id') { + throw new NotSupportedException('Between condition is not supported for the _id field.'); } $filter = ['range' => [$column => ['gte' => $value1, 'lte' => $value2]]]; if ($operator == 'not between') { @@ -274,7 +274,7 @@ class QueryBuilder extends \yii\base\Object unset($values[$i]); } } - if ($column == ActiveRecord::PRIMARY_KEY_NAME) { + if ($column == '_id') { if (empty($values) && $canBeNull) { // there is no null pk $filter = ['script' => ['script' => '0==1']]; } else { @@ -306,6 +306,6 @@ class QueryBuilder extends \yii\base\Object private function buildLikeCondition($operator, $operands) { - throw new NotSupportedException('like conditions is not supported by elasticsearch.'); + throw new NotSupportedException('like conditions are not supported by elasticsearch.'); } } diff --git a/extensions/yii/elasticsearch/README.md b/extensions/yii/elasticsearch/README.md index 1cf5a7c..2d2529f 100644 --- a/extensions/yii/elasticsearch/README.md +++ b/extensions/yii/elasticsearch/README.md @@ -55,12 +55,12 @@ For general information on how to use yii's ActiveRecord please refer to the [gu For defining an elasticsearch ActiveRecord class your record class needs to extend from `yii\elasticsearch\ActiveRecord` and implement at least the `attributes()` method to define the attributes of the record. -The primary key (the `_id` field in elasticsearch terms) is represented by `getId()` and `setId()` and can not be changed. -The primary key is not part of the attributes. - - primary key can be defined via [[primaryKey()]] which defaults to `id` if not specified. -The primaryKey needs to be part of the attributes so make sure you have an `id` attribute defined if you do -not specify your own primary key. +The handling of primary keys is different in elasticsearch as the primary key (the `_id` field in elasticsearch terms) +is not part of the attributes by default. However it is possible to define a [path mapping](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-id-field.html) +for the `_id` field to be part of the attributes. +See [elasticsearch docs](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-id-field.html) on how to define it. +The `_id` field of a document/record can be accessed using [[ActiveRecord::getPrimaryKey()]] and [[ActiveRecord::setPrimaryKey()]]. +When path mapping is defined, the attribute name can be defined using the [[primaryKey()]] method. The following is an example model called `Customer`: @@ -72,6 +72,7 @@ class Customer extends \yii\elasticsearch\ActiveRecord */ public function attributes() { + // path mapping for '_id' is setup to field 'id' return ['id', 'name', 'address', 'registration_date']; } @@ -105,25 +106,23 @@ It supports the same interface and features except the following limitations and and [type](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/glossary.html#glossary-type) to query against. - `select()` has been replaced with `fields()` which basically does the same but `fields` is more elasticsearch terminology. It defines the fields to retrieve from a document. -- `via`-relations can not be defined via a table as there are not tables in elasticsearch. You can only define relations via other records. -- As elasticsearch is a data storage and search engine there is of course support added for search your records. +- `via`-relations can not be defined via a table as there are no tables in elasticsearch. You can only define relations via other records. +- As elasticsearch is not only a data storage but also a search engine there is of course support added for search your records. There are `query()`, `filter()` and `addFacets()` methods that allows to compose an elasticsearch query. See the usage example below on how they work and check out the [Query DSL](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl.html) on how to compose `query` and `filter` parts. - It is also possible to define relations from elasticsearch ActiveRecords to normal ActiveRecord classes and vice versa. -Elasticsearch separates primary key from attributes. You need to set the `id` property of the record to set its primary key. - Usage example: ```php $customer = new Customer(); -$customer->id = 1; +$customer->primaryKey = 1; // in this case equivalent to $customer->id = 1; $customer->attributes = ['name' => 'test']; $customer->save(); $customer = Customer::get(1); // get a record by pk -$customers = Customer::get([1,2,3]); // get a records multiple by pk +$customers = Customer::mget([1,2,3]); // get multiple records by pk $customer = Customer::find()->where(['name' => 'test'])->one(); // find by query $customers = Customer::find()->active()->all(); // find all by query (using the `active` scope) @@ -152,10 +151,11 @@ Using the elasticsearch DebugPanel ---------------------------------- The yii2 elasticsearch extensions provides a `DebugPanel` that can be integrated with the yii debug module -an shows the executed elasticsearch queries. It also allows to run these queries on different cluster nodes -an view the results. +and shows the executed elasticsearch queries. It also allows to run these queries +and view the results. -Add the following to you application config to enable it: +Add the following to you application config to enable it (if you already have the debug module +enabled, it is sufficient to just add the panels configuration): ```php // ... @@ -173,4 +173,18 @@ Add the following to you application config to enable it: // ... ``` -![elasticsearch DebugPanel](README-debug.png) \ No newline at end of file +![elasticsearch DebugPanel](README-debug.png) + + +Relation definitions with records whose primary keys are not part of attributes +------------------------------------------------------------------------------- + +TODO + + +Patterns +-------- + +### Fetching records from different indexes/types + +TODO diff --git a/framework/yii/db/BaseActiveRecord.php b/framework/yii/db/BaseActiveRecord.php index dcd613e..6646b4a 100644 --- a/framework/yii/db/BaseActiveRecord.php +++ b/framework/yii/db/BaseActiveRecord.php @@ -923,7 +923,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface */ public function equals($record) { - if ($this->isNewRecord || $record->isNewRecord) { + if ($this->getIsNewRecord() || $record->getIsNewRecord()) { return false; } return get_class($this) === get_class($record) && $this->getPrimaryKey() === $record->getPrimaryKey(); diff --git a/tests/unit/data/ar/elasticsearch/Customer.php b/tests/unit/data/ar/elasticsearch/Customer.php index 0c07d08..22d2c09 100644 --- a/tests/unit/data/ar/elasticsearch/Customer.php +++ b/tests/unit/data/ar/elasticsearch/Customer.php @@ -1,6 +1,7 @@ hasMany(Order::className(), array('customer_id' => ActiveRecord::PRIMARY_KEY_NAME))->orderBy('create_time'); + return $this->hasMany(Order::className(), array('customer_id' => 'id'))->orderBy('create_time'); } public static function active($query) @@ -40,4 +46,25 @@ class Customer extends ActiveRecord ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord; parent::afterSave($insert); } + + /** + * sets up the index for this record + * @param Command $command + */ + public static function setUpMapping($command, $statusIsBoolean = false) + { + $command->deleteMapping(static::index(), static::type()); + $command->setMapping(static::index(), static::type(), [ + static::type() => [ + "_id" => ["path" => "id", "index" => "not_analyzed", "store" => "yes"], + "properties" => [ + "name" => ["type" => "string", "index" => "not_analyzed"], + "email" => ["type" => "string", "index" => "not_analyzed"], + "address" => ["type" => "string", "index" => "analyzed"], + "status" => $statusIsBoolean ? ["type" => "boolean"] : ["type" => "integer"], + ] + ] + ]); + + } } diff --git a/tests/unit/data/ar/elasticsearch/Item.php b/tests/unit/data/ar/elasticsearch/Item.php index 7e09b0c..033c38d 100644 --- a/tests/unit/data/ar/elasticsearch/Item.php +++ b/tests/unit/data/ar/elasticsearch/Item.php @@ -1,6 +1,7 @@ deleteMapping(static::index(), static::type()); + $command->setMapping(static::index(), static::type(), [ + static::type() => [ + "_id" => ["path" => "id", "index" => "not_analyzed", "store" => "yes"], + "properties" => [ + "name" => ["type" => "string", "index" => "not_analyzed"], + "category_id" => ["type" => "integer"], + ] + ] + ]); + } } diff --git a/tests/unit/data/ar/elasticsearch/Order.php b/tests/unit/data/ar/elasticsearch/Order.php index 088011c..e7607cd 100644 --- a/tests/unit/data/ar/elasticsearch/Order.php +++ b/tests/unit/data/ar/elasticsearch/Order.php @@ -1,6 +1,7 @@ hasOne(Customer::className(), [ActiveRecord::PRIMARY_KEY_NAME => 'customer_id']); + return $this->hasOne(Customer::className(), ['id' => 'customer_id']); } public function getOrderItems() { - return $this->hasMany(OrderItem::className(), ['order_id' => ActiveRecord::PRIMARY_KEY_NAME]); + return $this->hasMany(OrderItem::className(), ['order_id' => 'id']); } public function getItems() { - return $this->hasMany(Item::className(), [ActiveRecord::PRIMARY_KEY_NAME => 'item_id']) + return $this->hasMany(Item::className(), ['id' => 'item_id']) ->via('orderItems')->orderBy('id'); } @@ -51,8 +57,8 @@ class Order extends ActiveRecord // public function getBooks() // { -// return $this->hasMany('Item', [ActiveRecord::PRIMARY_KEY_NAME => 'item_id']) -// ->viaTable('tbl_order_item', ['order_id' => ActiveRecord::PRIMARY_KEY_NAME]) +// return $this->hasMany('Item', ['id' => 'item_id']) +// ->viaTable('tbl_order_item', ['order_id' => 'id']) // ->where(['category_id' => 1]); // } @@ -65,4 +71,24 @@ class Order extends ActiveRecord return false; } } + + /** + * sets up the index for this record + * @param Command $command + */ + public static function setUpMapping($command) + { + $command->deleteMapping(static::index(), static::type()); + $command->setMapping(static::index(), static::type(), [ + static::type() => [ + "_id" => ["path" => "id", "index" => "not_analyzed", "store" => "yes"], + "properties" => [ + "customer_id" => ["type" => "integer"], +// "create_time" => ["type" => "string", "index" => "not_analyzed"], + "total" => ["type" => "integer"], + ] + ] + ]); + + } } diff --git a/tests/unit/data/ar/elasticsearch/OrderItem.php b/tests/unit/data/ar/elasticsearch/OrderItem.php index a43d8b2..36961d9 100644 --- a/tests/unit/data/ar/elasticsearch/OrderItem.php +++ b/tests/unit/data/ar/elasticsearch/OrderItem.php @@ -1,6 +1,7 @@ hasOne(Order::className(), [ActiveRecord::PRIMARY_KEY_NAME => 'order_id']); + return $this->hasOne(Order::className(), ['id' => 'order_id']); } public function getItem() { - return $this->hasOne(Item::className(), [ActiveRecord::PRIMARY_KEY_NAME => 'item_id']); + return $this->hasOne(Item::className(), ['id' => 'item_id']); + } + + /** + * sets up the index for this record + * @param Command $command + */ + public static function setUpMapping($command) + { + $command->deleteMapping(static::index(), static::type()); + $command->setMapping(static::index(), static::type(), [ + static::type() => [ + "properties" => [ + "order_id" => ["type" => "integer"], + "item_id" => ["type" => "integer"], + "quantity" => ["type" => "integer"], + "subtotal" => ["type" => "integer"], + ] + ] + ]); + } } diff --git a/tests/unit/extensions/elasticsearch/ActiveRecordTest.php b/tests/unit/extensions/elasticsearch/ActiveRecordTest.php index b1d103e..8d3db69 100644 --- a/tests/unit/extensions/elasticsearch/ActiveRecordTest.php +++ b/tests/unit/extensions/elasticsearch/ActiveRecordTest.php @@ -47,18 +47,15 @@ class ActiveRecordTest extends ElasticSearchTestCase if ($db->createCommand()->indexExists('yiitest')) { $db->createCommand()->deleteIndex('yiitest'); } + $db->createCommand()->createIndex('yiitest'); - $db->post(['yiitest'], [], Json::encode([ - 'mappings' => [ - "item" => [ - "_source" => [ "enabled" => true ], - "properties" => [ - // allow proper sorting by name - "name" => ["type" => "string", "index" => "not_analyzed"], - ] - ] - ], - ])); + $command = $db->createCommand(); + Customer::setUpMapping($command); + Item::setUpMapping($command); + Order::setUpMapping($command); + OrderItem::setUpMapping($command); + + $db->createCommand()->flushIndex('yiitest'); $customer = new Customer(); $customer->id = 1; @@ -132,6 +129,20 @@ class ActiveRecordTest extends ElasticSearchTestCase $db->createCommand()->flushIndex('yiitest'); } + public function testFindAsArray() + { + // asArray + $customer = $this->callCustomerFind()->where(['id' => 2])->asArray()->one(); + $this->assertEquals([ + 'id' => 2, + 'email' => 'user2@example.com', + 'name' => 'user2', + 'address' => 'address2', + 'status' => 1, + '_score' => 1.0 + ], $customer); + } + public function testSearch() { $customers = $this->callCustomerFind()->search()['hits']; @@ -243,8 +254,8 @@ class ActiveRecordTest extends ElasticSearchTestCase public function testInsertNoPk() { - $this->assertEquals([ActiveRecord::PRIMARY_KEY_NAME], Customer::primaryKey()); - $pkName = ActiveRecord::PRIMARY_KEY_NAME; + $this->assertEquals(['id'], Customer::primaryKey()); + $pkName = 'id'; $customer = new Customer; $customer->email = 'user4@example.com'; @@ -257,6 +268,7 @@ class ActiveRecordTest extends ElasticSearchTestCase $this->assertTrue($customer->isNewRecord); $customer->save(); + $this->afterSave(); $this->assertNotNull($customer->primaryKey); $this->assertNotNull($customer->oldPrimaryKey); @@ -268,7 +280,7 @@ class ActiveRecordTest extends ElasticSearchTestCase public function testInsertPk() { - $pkName = ActiveRecord::PRIMARY_KEY_NAME; + $pkName = 'id'; $customer = new Customer; $customer->$pkName = 5; @@ -288,17 +300,26 @@ class ActiveRecordTest extends ElasticSearchTestCase public function testUpdatePk() { - $pkName = ActiveRecord::PRIMARY_KEY_NAME; + $pkName = 'id'; - $pk = [$pkName => 2]; - $orderItem = Order::find($pk); + $orderItem = Order::find([$pkName => 2]); $this->assertEquals(2, $orderItem->primaryKey); $this->assertEquals(2, $orderItem->oldPrimaryKey); $this->assertEquals(2, $orderItem->$pkName); - $this->setExpectedException('yii\base\InvalidCallException'); +// $this->setExpectedException('yii\base\InvalidCallException'); $orderItem->$pkName = 13; + $this->assertEquals(13, $orderItem->primaryKey); + $this->assertEquals(2, $orderItem->oldPrimaryKey); + $this->assertEquals(13, $orderItem->$pkName); $orderItem->save(); + $this->afterSave(); + $this->assertEquals(13, $orderItem->primaryKey); + $this->assertEquals(13, $orderItem->oldPrimaryKey); + $this->assertEquals(13, $orderItem->$pkName); + + $this->assertNull(Order::find([$pkName => 2])); + $this->assertNotNull(Order::find([$pkName => 13])); } public function testFindLazyVia2() @@ -306,7 +327,7 @@ class ActiveRecordTest extends ElasticSearchTestCase /** @var TestCase|ActiveRecordTestTrait $this */ /** @var Order $order */ $orderClass = $this->getOrderClass(); - $pkName = ActiveRecord::PRIMARY_KEY_NAME; + $pkName = 'id'; $order = new $orderClass(); $order->$pkName = 100; @@ -320,18 +341,8 @@ class ActiveRecordTest extends ElasticSearchTestCase public function testBooleanAttribute() { $db = $this->getConnection(); - $db->createCommand()->deleteIndex('yiitest'); - $db->post(['yiitest'], [], Json::encode([ - 'mappings' => [ - "customer" => [ - "_source" => [ "enabled" => true ], - "properties" => [ - // this is for the boolean test - "status" => ["type" => "boolean"], - ] - ] - ], - ])); + Customer::setUpMapping($db->createCommand(), true); + Customer::deleteAll(); $customerClass = $this->getCustomerClass(); $customer = new $customerClass(); diff --git a/tests/unit/framework/ar/ActiveRecordTestTrait.php b/tests/unit/framework/ar/ActiveRecordTestTrait.php index be4a40f..959b8c9 100644 --- a/tests/unit/framework/ar/ActiveRecordTestTrait.php +++ b/tests/unit/framework/ar/ActiveRecordTestTrait.php @@ -145,7 +145,10 @@ trait ActiveRecordTestTrait // scope $this->assertEquals(2, count($this->callCustomerFind()->active()->all())); $this->assertEquals(2, $this->callCustomerFind()->active()->count()); + } + public function testFindAsArray() + { // asArray $customer = $this->callCustomerFind()->where(['id' => 2])->asArray()->one(); $this->assertEquals([ @@ -494,6 +497,37 @@ trait ActiveRecordTestTrait public function testFindEagerViaRelationPreserveOrder() { /** @var TestCase|ActiveRecordTestTrait $this */ + + /* + Item (name, category_id) + Order (customer_id, create_time, total) + OrderItem (order_id, item_id, quantity, subtotal) + + Result should be the following: + + Order 1: 1, 1325282384, 110.0 + - orderItems: + OrderItem: 1, 1, 1, 30.0 + OrderItem: 1, 2, 2, 40.0 + - itemsInOrder: + Item 1: 'Agile Web Application Development with Yii1.1 and PHP5', 1 + Item 2: 'Yii 1.1 Application Development Cookbook', 1 + + Order 2: 2, 1325334482, 33.0 + - orderItems: + OrderItem: 2, 3, 1, 8.0 + OrderItem: 2, 4, 1, 10.0 + OrderItem: 2, 5, 1, 15.0 + - itemsInOrder: + Item 5: 'Cars', 2 + Item 3: 'Ice Age', 2 + Item 4: 'Toy Story', 2 + Order 3: 2, 1325502201, 40.0 + - orderItems: + OrderItem: 3, 2, 1, 40.0 + - itemsInOrder: + Item 3: 'Ice Age', 2 + */ $orders = $this->callOrderFind()->with('itemsInOrder1')->orderBy('create_time')->all(); $this->assertEquals(3, count($orders));