From e2b4708bbddcac796ef440f1ac5e2336ad996a70 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 9 Dec 2013 14:42:28 +0100 Subject: [PATCH] added support for updateCounters to elasticsearch issue #1313 --- extensions/elasticsearch/ActiveRecord.php | 66 ++++++++++++++++++++-- extensions/redis/ActiveRecord.php | 9 +-- framework/yii/db/BaseActiveRecord.php | 2 - .../extensions/elasticsearch/ActiveRecordTest.php | 7 --- tests/unit/framework/ar/ActiveRecordTestTrait.php | 2 +- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/extensions/elasticsearch/ActiveRecord.php b/extensions/elasticsearch/ActiveRecord.php index 3afadf6..3c8b4a8 100644 --- a/extensions/elasticsearch/ActiveRecord.php +++ b/extensions/elasticsearch/ActiveRecord.php @@ -340,10 +340,9 @@ class ActiveRecord extends BaseActiveRecord * @param array $attributes attribute values (name-value pairs) to be saved into the table * @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. - * @param array $params this parameter is ignored in elasticsearch implementation. * @return integer the number of rows updated */ - public static function updateAll($attributes, $condition = [], $params = []) + public static function updateAll($attributes, $condition = []) { if (count($condition) == 1 && isset($condition[ActiveRecord::PRIMARY_KEY_NAME])) { $primaryKeys = (array) $condition[ActiveRecord::PRIMARY_KEY_NAME]; @@ -362,9 +361,9 @@ class ActiveRecord extends BaseActiveRecord "_index" => static::index(), ], ]); - $data = Json::encode(array( + $data = Json::encode([ "doc" => $attributes - )); + ]); $bulk .= $action . "\n" . $data . "\n"; } @@ -380,6 +379,62 @@ class ActiveRecord extends BaseActiveRecord return $n; } + /** + * Updates all matching records using the provided counter changes and conditions. + * For example, to increment all customers' age by 1, + * + * ~~~ + * Customer::updateAllCounters(['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. + * @return integer the number of rows updated + */ + public static function updateAllCounters($counters, $condition = []) + { + if (count($condition) == 1 && isset($condition[ActiveRecord::PRIMARY_KEY_NAME])) { + $primaryKeys = (array) $condition[ActiveRecord::PRIMARY_KEY_NAME]; + } else { + $primaryKeys = static::find()->where($condition)->column(ActiveRecord::PRIMARY_KEY_NAME); + } + if (empty($primaryKeys) || empty($counters)) { + return 0; + } + $bulk = ''; + foreach((array) $primaryKeys as $pk) { + $action = Json::encode([ + "update" => [ + "_id" => $pk, + "_type" => static::type(), + "_index" => static::index(), + ], + ]); + $script = ''; + foreach($counters as $counter => $value) { + $script .= "ctx._source.$counter += $counter;\n"; + } + $data = Json::encode([ + "script" => $script, + "params" => $counters + ]); + $bulk .= $action . "\n" . $data . "\n"; + } + + // TODO do this via command + $url = [static::index(), static::type(), '_bulk']; + $response = static::getDb()->post($url, [], $bulk); + + $n=0; + foreach($response['items'] as $item) { + if ($item['update']['ok']) { + $n++; + } + } + return $n; + } /** * Deletes rows in the table using the provided conditions. @@ -393,10 +448,9 @@ class ActiveRecord extends BaseActiveRecord * * @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL. * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. - * @param array $params this parameter is ignored in elasticsearch implementation. * @return integer the number of rows deleted */ - public static function deleteAll($condition = [], $params = []) + public static function deleteAll($condition = []) { if (count($condition) == 1 && isset($condition[ActiveRecord::PRIMARY_KEY_NAME])) { $primaryKeys = (array) $condition[ActiveRecord::PRIMARY_KEY_NAME]; diff --git a/extensions/redis/ActiveRecord.php b/extensions/redis/ActiveRecord.php index a308a32..2e2bcbb 100644 --- a/extensions/redis/ActiveRecord.php +++ b/extensions/redis/ActiveRecord.php @@ -150,10 +150,9 @@ class ActiveRecord extends BaseActiveRecord * @param array $attributes attribute values (name-value pairs) to be saved into the table * @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. - * @param array $params this parameter is ignored in redis implementation. * @return integer the number of rows updated */ - public static function updateAll($attributes, $condition = null, $params = []) + public static function updateAll($attributes, $condition = null) { if (empty($attributes)) { return 0; @@ -203,10 +202,9 @@ class ActiveRecord extends BaseActiveRecord * Use negative values if you want to decrement the counters. * @param array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. - * @param array $params this parameter is ignored in redis implementation. * @return integer the number of rows updated */ - public static function updateAllCounters($counters, $condition = null, $params = []) + public static function updateAllCounters($counters, $condition = null) { if (empty($counters)) { return 0; @@ -235,10 +233,9 @@ class ActiveRecord extends BaseActiveRecord * * @param array $condition the conditions that will be put in the WHERE part of the DELETE SQL. * Please refer to [[ActiveQuery::where()]] on how to specify this parameter. - * @param array $params this parameter is ignored in redis implementation. * @return integer the number of rows deleted */ - public static function deleteAll($condition = null, $params = []) + public static function deleteAll($condition = null) { $db = static::getDb(); $attributeKeys = []; diff --git a/framework/yii/db/BaseActiveRecord.php b/framework/yii/db/BaseActiveRecord.php index 00360c4..01e1925 100644 --- a/framework/yii/db/BaseActiveRecord.php +++ b/framework/yii/db/BaseActiveRecord.php @@ -158,8 +158,6 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface * 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 = '') diff --git a/tests/unit/extensions/elasticsearch/ActiveRecordTest.php b/tests/unit/extensions/elasticsearch/ActiveRecordTest.php index ccdc90f..b1d103e 100644 --- a/tests/unit/extensions/elasticsearch/ActiveRecordTest.php +++ b/tests/unit/extensions/elasticsearch/ActiveRecordTest.php @@ -313,13 +313,6 @@ class ActiveRecordTest extends ElasticSearchTestCase $this->assertEquals([], $order->items); } - public function testUpdateCounters() - { - // Update Counters is not supported by elasticsearch -// $this->setExpectedException('yii\base\NotSupportedException'); -// ActiveRecordTestTrait::testUpdateCounters(); - } - /** * Some PDO implementations(e.g. cubrid) do not support boolean values. * Make sure this does not affect AR layer. diff --git a/tests/unit/framework/ar/ActiveRecordTestTrait.php b/tests/unit/framework/ar/ActiveRecordTestTrait.php index 44bcf4d..3f1739e 100644 --- a/tests/unit/framework/ar/ActiveRecordTestTrait.php +++ b/tests/unit/framework/ar/ActiveRecordTestTrait.php @@ -670,7 +670,7 @@ trait ActiveRecordTestTrait $this->assertEquals(1, $orderItem->quantity); $ret = $orderItem->updateCounters(['quantity' => -1]); $this->afterSave(); - $this->assertTrue($ret); + $this->assertEquals(1, $ret); $this->assertEquals(0, $orderItem->quantity); $orderItem = $this->callOrderItemFind($pk); $this->assertEquals(0, $orderItem->quantity);