arango recode
This commit is contained in:
parent
12c3862177
commit
4bb94c60c6
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
namespace devgroup\arangodb;
|
||||
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\db\ActiveQueryInterface;
|
||||
use yii\db\ActiveQueryTrait;
|
||||
use yii\db\ActiveRecordInterface;
|
||||
use yii\db\ActiveRelationTrait;
|
||||
use yii\helpers\VarDumper;
|
||||
|
||||
use triagens\ArangoDb\Document;
|
||||
|
||||
class ActiveQuery extends Query implements ActiveQueryInterface
|
||||
{
|
||||
use ActiveQueryTrait;
|
||||
use ActiveRelationTrait;
|
||||
|
||||
public function __construct($modelClass, $config = [])
|
||||
{
|
||||
$this->modelClass = $modelClass;
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
protected function buildQuery($query = null, $params = [])
|
||||
{
|
||||
if ($this->primaryModel !== null) {
|
||||
// lazy loading
|
||||
if ($this->via instanceof self) {
|
||||
// via pivot collection
|
||||
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
|
||||
$this->filterByModels($viaModels);
|
||||
} elseif (is_array($this->via)) {
|
||||
// via relation
|
||||
/* @var $viaQuery ActiveQuery */
|
||||
list($viaName, $viaQuery) = $this->via;
|
||||
if ($viaQuery->multiple) {
|
||||
$viaModels = $viaQuery->all();
|
||||
$this->primaryModel->populateRelation($viaName, $viaModels);
|
||||
} else {
|
||||
$model = $viaQuery->one();
|
||||
$this->primaryModel->populateRelation($viaName, $model);
|
||||
$viaModels = $model === null ? [] : [$model];
|
||||
}
|
||||
$this->filterByModels($viaModels);
|
||||
} else {
|
||||
$this->filterByModels([$this->primaryModel]);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::buildQuery($query, $params);
|
||||
}
|
||||
|
||||
private function createModels($rows)
|
||||
{
|
||||
$models = [];
|
||||
if ($this->asArray) {
|
||||
array_walk(
|
||||
$rows,
|
||||
function (&$doc) {
|
||||
if ($doc instanceof Document) {
|
||||
$doc = $doc->getAll();
|
||||
}
|
||||
}
|
||||
);
|
||||
if ($this->indexBy === null) {
|
||||
return $rows;
|
||||
}
|
||||
foreach ($rows as $row) {
|
||||
if (is_string($this->indexBy)) {
|
||||
$key = $row[$this->indexBy];
|
||||
} else {
|
||||
$key = call_user_func($this->indexBy, $row);
|
||||
}
|
||||
$models[$key] = $row;
|
||||
}
|
||||
} else {
|
||||
/* @var $class ActiveRecord */
|
||||
$class = $this->modelClass;
|
||||
if ($this->indexBy === null) {
|
||||
foreach ($rows as $row) {
|
||||
$model = $class::instantiate($row);
|
||||
$class::populateRecord($model, $row);
|
||||
$model->setIsNewRecord(false);
|
||||
$models[] = $model;
|
||||
}
|
||||
} else {
|
||||
foreach ($rows as $row) {
|
||||
$model = $class::instantiate($row);
|
||||
$class::populateRecord($model, $row);
|
||||
$model->setIsNewRecord(false);
|
||||
if (is_string($this->indexBy)) {
|
||||
$key = $model->{$this->indexBy};
|
||||
} else {
|
||||
$key = call_user_func($this->indexBy, $model);
|
||||
}
|
||||
$models[$key] = $model;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
}
|
||||
|
||||
private function prefixKeyColumns($attributes)
|
||||
{
|
||||
if ($this instanceof ActiveQuery) {
|
||||
/* @var $modelClass ActiveRecord */
|
||||
$modelClass = $this->modelClass;
|
||||
foreach ($attributes as $i => $attribute) {
|
||||
$attributes[$i] = "{$modelClass::collectionName()}.$attribute";
|
||||
}
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
public function all($db = null)
|
||||
{
|
||||
$statement = $this->createCommand();
|
||||
$cursor = $statement->execute();
|
||||
$rows = $cursor->getAll();
|
||||
if (!empty($rows)) {
|
||||
$models = $this->createModels($rows);
|
||||
if (!empty($this->with)) {
|
||||
$this->findWith($this->with, $models);
|
||||
}
|
||||
if (!$this->asArray) {
|
||||
foreach ($models as $model) {
|
||||
$model->afterFind();
|
||||
}
|
||||
}
|
||||
|
||||
return $models;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function one($db = null)
|
||||
{
|
||||
$row = parent::one($db);
|
||||
if ($row !== false) {
|
||||
if ($this->asArray) {
|
||||
$model = $row;
|
||||
} else {
|
||||
/* @var $class ActiveRecord */
|
||||
$class = $this->modelClass;
|
||||
$model = $class::instantiate($row);
|
||||
$class::populateRecord($model, $row);
|
||||
$model->setIsNewRecord(false);
|
||||
}
|
||||
if (!empty($this->with)) {
|
||||
$models = [$model];
|
||||
$this->findWith($this->with, $models);
|
||||
$model = $models[0];
|
||||
}
|
||||
if (!$this->asArray) {
|
||||
$model->afterFind();
|
||||
}
|
||||
|
||||
return $model;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,440 @@
|
|||
<?php
|
||||
|
||||
namespace devgroup\arangodb;
|
||||
|
||||
use Yii;
|
||||
use yii\base\ArrayableTrait;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\base\Model;
|
||||
use yii\db\ActiveQueryInterface;
|
||||
use yii\db\BaseActiveRecord;
|
||||
use yii\db\StaleObjectException;
|
||||
use yii\helpers\ArrayHelper;
|
||||
use yii\helpers\Inflector;
|
||||
use yii\helpers\StringHelper;
|
||||
|
||||
use triagens\ArangoDb\Document;
|
||||
|
||||
abstract class ActiveRecord extends BaseActiveRecord
|
||||
{
|
||||
/** @var Document $document */
|
||||
private $document;
|
||||
|
||||
public function __construct($config = [])
|
||||
{
|
||||
$this->document = new Document();
|
||||
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
public function mergeAttribute($name, $value)
|
||||
{
|
||||
$newValue = $this->getAttribute($name);
|
||||
if (!is_array($newValue)) {
|
||||
$newValue === null ? [] : [$newValue];
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$this->setAttribute($name, ArrayHelper::merge($newValue, $value));
|
||||
} else {
|
||||
$newValue[] = $value;
|
||||
$this->setAttribute($name, $newValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static function collectionName()
|
||||
{
|
||||
return Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
|
||||
}
|
||||
|
||||
public function setAttribute($name, $value)
|
||||
{
|
||||
$this->document->set($name, $value);
|
||||
parent::setAttribute($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary key **name(s)** for this AR class.
|
||||
*
|
||||
* Note that an array should be returned even when the record only has a single primary key.
|
||||
*
|
||||
* For the primary key **value** see [[getPrimaryKey()]] instead.
|
||||
*
|
||||
* @return string[] the primary key name(s) for this AR class.
|
||||
*/
|
||||
public static function primaryKey()
|
||||
{
|
||||
return ['_key'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an [[ActiveQueryInterface|ActiveQuery]] instance for query purpose.
|
||||
*
|
||||
* The returned [[ActiveQueryInterface|ActiveQuery]] instance can be further customized by calling
|
||||
* methods defined in [[ActiveQueryInterface]] before `one()` or `all()` is called to return
|
||||
* populated ActiveRecord instances. For example,
|
||||
*
|
||||
* ```php
|
||||
* // find the customer whose ID is 1
|
||||
* $customer = Customer::find()->where(['id' => 1])->one();
|
||||
*
|
||||
* // find all active customers and order them by their age:
|
||||
* $customers = Customer::find()
|
||||
* ->where(['status' => 1])
|
||||
* ->orderBy('age')
|
||||
* ->all();
|
||||
* ```
|
||||
*
|
||||
* This method is also called by [[BaseActiveRecord::hasOne()]] and [[BaseActiveRecord::hasMany()]] to
|
||||
* create a relational query.
|
||||
*
|
||||
* You may override this method to return a customized query. For example,
|
||||
*
|
||||
* ```php
|
||||
* class Customer extends ActiveRecord
|
||||
* {
|
||||
* public static function find()
|
||||
* {
|
||||
* // use CustomerQuery instead of the default ActiveQuery
|
||||
* return new CustomerQuery(get_called_class());
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The following code shows how to apply a default condition for all queries:
|
||||
*
|
||||
* ```php
|
||||
* class Customer extends ActiveRecord
|
||||
* {
|
||||
* public static function find()
|
||||
* {
|
||||
* return parent::find()->where(['deleted' => false]);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Use andWhere()/orWhere() to apply the default condition
|
||||
* // SELECT FROM customer WHERE `deleted`=:deleted AND age>30
|
||||
* $customers = Customer::find()->andWhere('age>30')->all();
|
||||
*
|
||||
* // Use where() to ignore the default condition
|
||||
* // SELECT FROM customer WHERE age>30
|
||||
* $customers = Customer::find()->where('age>30')->all();
|
||||
*
|
||||
* @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
|
||||
*/
|
||||
public static function find()
|
||||
{
|
||||
/** @var ActiveQuery $query */
|
||||
$query = \Yii::createObject(ActiveQuery::className(), [get_called_class()]);
|
||||
$query->from(static::collectionName())->select(static::collectionName());
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ActiveRecord $record
|
||||
* @param Document $row
|
||||
*/
|
||||
public static function populateRecord($record, $row)
|
||||
{
|
||||
$record->document = $row;
|
||||
parent::populateRecord($record, $record->document->getAll());
|
||||
}
|
||||
|
||||
public function attributes()
|
||||
{
|
||||
$class = new \ReflectionClass($this);
|
||||
$names = [];
|
||||
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
|
||||
if (!$property->isStatic()) {
|
||||
$names[] = $property->getName();
|
||||
}
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the record into the database using the attribute values of this record.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* ```php
|
||||
* $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;
|
||||
}
|
||||
$result = $this->insertInternal($attributes);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function insertInternal($attributes = null)
|
||||
{
|
||||
if (!$this->beforeSave(true)) {
|
||||
return false;
|
||||
}
|
||||
$values = $this->getDirtyAttributes($attributes);
|
||||
if (empty($values)) {
|
||||
$currentAttributes = $this->getAttributes();
|
||||
foreach ($this->primaryKey() as $key) {
|
||||
$values[$key] = isset($currentAttributes[$key]) ? $currentAttributes[$key] : null;
|
||||
}
|
||||
}
|
||||
|
||||
$newId = static::getDb()->getDocumentHandler()->save(static::collectionName(), $values);
|
||||
static::populateRecord($this, static::getDb()->getDocument(static::collectionName(), $newId));
|
||||
$this->setIsNewRecord(false);
|
||||
|
||||
$changedAttributes = array_fill_keys(array_keys($values), null);
|
||||
$this->setOldAttributes($this->document->getAll());
|
||||
$this->afterSave(true, $changedAttributes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function updateInternal($attributes = null)
|
||||
{
|
||||
if (!$this->beforeSave(false)) {
|
||||
return false;
|
||||
}
|
||||
$values = $this->getDirtyAttributes($attributes);
|
||||
if (empty($values)) {
|
||||
$this->afterSave(false, $values);
|
||||
return 0;
|
||||
}
|
||||
$condition = $this->getOldPrimaryKey(true);
|
||||
$lock = $this->optimisticLock();
|
||||
if ($lock !== null) {
|
||||
if (!isset($values[$lock])) {
|
||||
$values[$lock] = $this->$lock + 1;
|
||||
}
|
||||
$condition[$lock] = $this->$lock;
|
||||
}
|
||||
|
||||
foreach ($values as $key => $attribute) {
|
||||
$this->setAttribute($key, $attribute);
|
||||
}
|
||||
|
||||
$rows = static::getDb()->getDocumentHandler()->updateById(
|
||||
static::collectionName(),
|
||||
$this->getOldAttribute('_key'),
|
||||
Document::createFromArray($values)
|
||||
);
|
||||
|
||||
if ($lock !== null && !$rows) {
|
||||
throw new StaleObjectException('The object being updated is outdated.');
|
||||
}
|
||||
|
||||
$changedAttributes = [];
|
||||
foreach ($values as $name => $value) {
|
||||
$changedAttributes[$name] = $this->getOldAttribute($name);
|
||||
$this->setOldAttribute($name, $value);
|
||||
}
|
||||
$this->afterSave(false, $changedAttributes);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection used by this AR class.
|
||||
* @return Connection the database connection used by this AR class.
|
||||
*/
|
||||
public static function getDb()
|
||||
{
|
||||
return \Yii::$app->get('arangodb');
|
||||
}
|
||||
|
||||
protected static function findByCondition($condition, $one)
|
||||
{
|
||||
/** @var ActiveQuery $query */
|
||||
$query = static::find();
|
||||
|
||||
if (!ArrayHelper::isAssociative($condition)) {
|
||||
// query by primary key
|
||||
$primaryKey = static::primaryKey();
|
||||
if (isset($primaryKey[0])) {
|
||||
$collection = static::collectionName();
|
||||
$condition = ["{$collection}.{$primaryKey[0]}" => $condition];
|
||||
} else {
|
||||
throw new InvalidConfigException(get_called_class() . ' must have a primary key.');
|
||||
}
|
||||
}
|
||||
|
||||
return $one ? $query->andWhere($condition)->one() : $query->andWhere($condition)->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates records 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(['status' => 1], ['status' => '2']);
|
||||
* ~~~
|
||||
*
|
||||
* @param array $attributes attribute values (name-value pairs) to be saved for the record.
|
||||
* Unlike [[update()]] these are not going to be validated.
|
||||
* @param array $condition the condition that matches the records that should get updated.
|
||||
* Please refer to [[QueryInterface::where()]] on how to specify this parameter.
|
||||
* An empty condition will match all records.
|
||||
* @return integer the number of rows updated
|
||||
*/
|
||||
public static function updateAll($attributes, $condition = [])
|
||||
{
|
||||
$docs = static::findAll($condition);
|
||||
|
||||
$count = 0;
|
||||
foreach ($docs as $doc) {
|
||||
foreach ($attributes as $key => $attribute) {
|
||||
$doc->setAttribute($key, $attribute);
|
||||
$doc->document->set($key, $attribute);
|
||||
}
|
||||
if (static::getDb()->getDocumentHandler()->update($doc->document)) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes records 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 array $condition the condition that matches the records that should get deleted.
|
||||
* Please refer to [[QueryInterface::where()]] on how to specify this parameter.
|
||||
* An empty condition will match all records.
|
||||
* @return integer the number of rows deleted
|
||||
*/
|
||||
public static function deleteAll($condition = null)
|
||||
{
|
||||
/** @var Document[] $docs */
|
||||
$records = static::findAll($condition);
|
||||
|
||||
$count = 0;
|
||||
foreach ($records as $record) {
|
||||
if (static::getDb()->getDocumentHandler()->remove($record->document)) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
public static function truncate()
|
||||
{
|
||||
return static::getDb()->getCollectionHandler()->truncate(static::collectionName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current record.
|
||||
*
|
||||
* This method will call [[insert()]] when [[getIsNewRecord()|isNewRecord]] is true, or [[update()]]
|
||||
* when [[getIsNewRecord()|isNewRecord]] is false.
|
||||
*
|
||||
* For example, to save a customer record:
|
||||
*
|
||||
* ~~~
|
||||
* $customer = new Customer; // or $customer = Customer::findOne($id);
|
||||
* $customer->name = $name;
|
||||
* $customer->email = $email;
|
||||
* $customer->save();
|
||||
* ~~~
|
||||
*
|
||||
* @param boolean $runValidation whether to perform validation before saving the record.
|
||||
* If the validation fails, the record will not be saved to database. `false` will be returned
|
||||
* in this case.
|
||||
* @param array $attributeNames 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 saving succeeds
|
||||
*/
|
||||
public function save($runValidation = true, $attributeNames = null)
|
||||
{
|
||||
if ($this->getIsNewRecord()) {
|
||||
return $this->insert($runValidation, $attributeNames);
|
||||
} else {
|
||||
return $this->update($runValidation, $attributeNames) !== false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the record from the database.
|
||||
*
|
||||
* @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
|
||||
* Note that it is possible that the number of rows deleted is 0, even though the deletion execution is successful.
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$result = false;
|
||||
if ($this->beforeDelete()) {
|
||||
$result = $this->deleteInternal();
|
||||
$this->afterDelete();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ActiveRecord::delete()
|
||||
* @throws StaleObjectException
|
||||
*/
|
||||
protected function deleteInternal()
|
||||
{
|
||||
$condition = $this->getOldPrimaryKey();
|
||||
$lock = $this->optimisticLock();
|
||||
if ($lock !== null) {
|
||||
$condition[$lock] = $this->$lock;
|
||||
}
|
||||
$result = static::getDb()->getDocumentHandler()->removeById(static::collectionName(), $condition);
|
||||
if ($lock !== null && !$result) {
|
||||
throw new StaleObjectException('The object being deleted is outdated.');
|
||||
}
|
||||
$this->setOldAttributes(null);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value indicating whether the current record is new (not saved in the database).
|
||||
* @return boolean whether the record is new and should be inserted when calling [[save()]].
|
||||
*/
|
||||
public function getIsNewRecord()
|
||||
{
|
||||
return $this->document->getIsNew();
|
||||
}
|
||||
|
||||
public function setIsNewRecord($value)
|
||||
{
|
||||
$this->document->setIsNew($value);
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->setAttributes($this->defaultValues());
|
||||
}
|
||||
|
||||
public function defaultValues()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -2,75 +2,18 @@
|
|||
|
||||
namespace devgroup\arangodb;
|
||||
|
||||
use yii\helpers\ArrayHelper;
|
||||
use yii\base\Object;
|
||||
use \triagens\ArangoDb\ConnectionOptions;
|
||||
|
||||
// set up some aliases for less typing later
|
||||
|
||||
use triagens\ArangoDb\ConnectionOptions as ArangoConnectionOptions;
|
||||
use triagens\ArangoDb\DocumentHandler as ArangoDocumentHandler;
|
||||
|
||||
use triagens\ArangoDb\Document as ArangoDocument;
|
||||
use triagens\ArangoDb\Exception as ArangoException;
|
||||
use triagens\ArangoDb\ConnectException as ArangoConnectException;
|
||||
use triagens\ArangoDb\ClientException as ArangoClientException;
|
||||
use triagens\ArangoDb\ServerException as ArangoServerException;
|
||||
use triagens\ArangoDb\UpdatePolicy as ArangoUpdatePolicy;
|
||||
use triagens\ArangoDb\Statement as Statement;
|
||||
|
||||
class ArangoDbConnection extends Object {
|
||||
private $_connection = null;
|
||||
public $connectionOptions = [
|
||||
// server endpoint to connect to
|
||||
ArangoConnectionOptions::OPTION_ENDPOINT => 'tcp://127.0.0.1:8529',
|
||||
// authorization type to use (currently supported: 'Basic')
|
||||
ArangoConnectionOptions::OPTION_AUTH_TYPE => 'Basic',
|
||||
// user for basic authorization
|
||||
ArangoConnectionOptions::OPTION_AUTH_USER => 'root',
|
||||
// password for basic authorization
|
||||
ArangoConnectionOptions::OPTION_AUTH_PASSWD => '',
|
||||
// connection persistence on server. can use either 'Close' (one-time connections) or 'Keep-Alive' (re-used connections)
|
||||
ArangoConnectionOptions::OPTION_CONNECTION => 'Close',
|
||||
// connect timeout in seconds
|
||||
ArangoConnectionOptions::OPTION_TIMEOUT => 3,
|
||||
// whether or not to reconnect when a keep-alive connection has timed out on server
|
||||
ArangoConnectionOptions::OPTION_RECONNECT => true,
|
||||
// optionally create new collections when inserting documents
|
||||
ArangoConnectionOptions::OPTION_CREATE => true,
|
||||
// optionally create new collections when inserting documents
|
||||
ArangoConnectionOptions::OPTION_UPDATE_POLICY => ArangoUpdatePolicy::LAST,
|
||||
];
|
||||
|
||||
private $_collectionHandler = null;
|
||||
private $_documentHandler = null;
|
||||
|
||||
public function __construct($config=[])
|
||||
class ArangoDbConnection extends \triagens\ArangoDb\Connection
|
||||
{
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
public function init()
|
||||
public function json_encode_wrapper($data, $options = null)
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$this->_connection = new Connection($this->connectionOptions);
|
||||
$this->_collectionHandler = new \triagens\ArangoDb\CollectionHandler($this->_connection);
|
||||
$this->_documentHandler = new \triagens\ArangoDb\DocumentHandler($this->_connection);
|
||||
if ($this->getOption(ConnectionOptions::OPTION_CHECK_UTF8_CONFORM) === true) {
|
||||
self::check_encoding($data);
|
||||
}
|
||||
|
||||
public function getDocument($collection, $id) {
|
||||
return $this->documentHandler()->get($collection, $id);
|
||||
}
|
||||
$response = json_encode($data, $options | JSON_FORCE_OBJECT);
|
||||
|
||||
public function documentHandler() {
|
||||
return $this->_documentHandler;
|
||||
}
|
||||
|
||||
public function statement($options=[]) {
|
||||
return new Statement($this->_connection, $options);
|
||||
}
|
||||
|
||||
public function collectionHandler() {
|
||||
return $this->_collectionHandler;
|
||||
return $response;
|
||||
}
|
||||
}
|
109
ArangoModel.php
109
ArangoModel.php
|
@ -1,109 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace devgroup\arangodb;
|
||||
|
||||
use yii;
|
||||
|
||||
|
||||
use triagens\ArangoDb\Document;
|
||||
|
||||
class ArangoModel extends \yii\base\Model {
|
||||
|
||||
private $_isNewRecord = true;
|
||||
|
||||
private $_doc = null;
|
||||
|
||||
public $_id = null;
|
||||
|
||||
public static function findById($id)
|
||||
{
|
||||
$parts = explode("\\", $id);
|
||||
if (count($parts)==2) {
|
||||
$id = $parts[1]; // для формата "Collection\1237643123"
|
||||
} else {
|
||||
$parts = explode("/", $id); // для формата "Collection/123123321"
|
||||
if (count($parts)==2) {
|
||||
$id = $parts[1];
|
||||
}
|
||||
}
|
||||
$model = new static;
|
||||
$model
|
||||
->setDocument(Yii::$app->arango->getDocument(static::class_to_collection(get_called_class()), $id))
|
||||
->setIsNewRecord(false);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function getAttributes($names=null, $except=['_id']){
|
||||
return parent::getAttributes($names, $except);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo функция должна возвращать true/false в зависимости от результата
|
||||
* Но аранга возвращает различный тип данных. Надо написать код
|
||||
*
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
if ($this->_isNewRecord) {
|
||||
// добавляем запись
|
||||
$this->_doc = Document::createFromArray($this->getAttributes());
|
||||
|
||||
$result = intval(Yii::$app->arango->documentHandler()->add(static::class_to_collection(get_called_class()), $this->_doc)) > 0;
|
||||
if ($result) {
|
||||
$this->_isNewRecord = false;
|
||||
}
|
||||
return $result;
|
||||
} else {
|
||||
// патчим!
|
||||
$doc_attributes = array_keys($this->_doc->getAll());
|
||||
|
||||
$attributes = $this->getAttributes();
|
||||
foreach ($attributes as $k=>$v) {
|
||||
$this->_doc->set($k, $v);
|
||||
unset($doc_attributes[$k]);
|
||||
}
|
||||
foreach ($doc_attributes as $key) {
|
||||
if ($key != '_key')
|
||||
unset($this->_doc->$key);
|
||||
}
|
||||
return Yii::$app->arango->documentHandler()->update($this->_doc);
|
||||
}
|
||||
}
|
||||
|
||||
private static function class_to_collection($class)
|
||||
{
|
||||
$parts = explode("\\", $class);
|
||||
return end($parts);
|
||||
}
|
||||
private static function id_to_int($class)
|
||||
{
|
||||
$parts = explode("/", $class);
|
||||
return end($parts);
|
||||
}
|
||||
|
||||
public function setIsNewRecord($state)
|
||||
{
|
||||
$this->_isNewRecord = $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDocument($doc)
|
||||
{
|
||||
$this->_doc = $doc;
|
||||
$all = $this->_doc->getAll();
|
||||
$this->_id = $this->_doc->getInternalId();
|
||||
$this->setAttributes($all, false);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
|
||||
Yii::$app->arango->documentHandler()->deleteById(
|
||||
static::class_to_collection(get_called_class()),
|
||||
static::id_to_int($this->_doc->getInternalId())
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace devgroup\arangodb;
|
||||
|
||||
use yii;
|
||||
use yii\di\Instance;
|
||||
|
||||
use triagens\ArangoDb\Document;
|
||||
|
||||
|
||||
|
||||
class ArangoProvider extends yii\data\ActiveDataProvider
|
||||
{
|
||||
public $arango = 'arango';
|
||||
|
||||
public $collection;
|
||||
|
||||
/**
|
||||
* @var array parameters for example
|
||||
*/
|
||||
public $params = [];
|
||||
|
||||
public $sort = '';
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->arango = Instance::ensure($this->arango, \devgroup\arangodb\ArangoDbConnection::className());
|
||||
if ($this->collection === null) {
|
||||
throw new InvalidConfigException('The "collection" property must be set.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function prepareKeys($models)
|
||||
{
|
||||
|
||||
return array_keys($models);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function prepareModels()
|
||||
{
|
||||
// $sql = $this->sql;
|
||||
// $qb = $this->db->getQueryBuilder();
|
||||
// if (($sort = $this->getSort()) !== false) {
|
||||
// $orderBy = $qb->buildOrderBy($sort->getOrders());
|
||||
// if (!empty($orderBy)) {
|
||||
// $orderBy = substr($orderBy, 9);
|
||||
// if (preg_match('/\s+order\s+by\s+[\w\s,\.]+$/i', $sql)) {
|
||||
// $sql .= ', ' . $orderBy;
|
||||
// } else {
|
||||
// $sql .= ' ORDER BY ' . $orderBy;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (($pagination = $this->getPagination()) !== false) {
|
||||
// $pagination->totalCount = $this->getTotalCount();
|
||||
// $sql .= ' ' . $qb->buildLimit($pagination->getLimit(), $pagination->getOffset());
|
||||
// }
|
||||
|
||||
// return $this->db->createCommand($sql, $this->params)->queryAll();
|
||||
$statement = $this->getBaseStatement();
|
||||
|
||||
if (($pagination = $this->getPagination()) !== false) {
|
||||
$pagination->totalCount = $this->getTotalCount();
|
||||
$statement->setQuery($statement->getQuery() . "\n LIMIT " . $pagination->getOffset() . ", " . $pagination->getLimit());
|
||||
}
|
||||
|
||||
|
||||
$statement->setQuery($statement->getQuery()."\n RETURN a");
|
||||
$cursor = $statement->execute();
|
||||
$data = $cursor->getAll();
|
||||
$result = [];
|
||||
foreach ($data as $doc) {
|
||||
$item = $doc->getAll();
|
||||
foreach ($item as $k=>$v) {
|
||||
if (is_array($item[$k]) || is_object($item[$k])) {
|
||||
$item[$k] = json_encode($v, true);
|
||||
}
|
||||
}
|
||||
$result[$item['_key']] = $item;
|
||||
}
|
||||
if (is_object($pagination)) {
|
||||
$pagination->totalCount = $cursor->getFullCount();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getTotalCount() {
|
||||
$statement = $this->getBaseStatement();
|
||||
$statement->setQuery($statement->getQuery(). "\n LIMIT 1 \n RETURN a");
|
||||
|
||||
|
||||
$cursor = $statement->execute();
|
||||
return $cursor->getFullCount();
|
||||
}
|
||||
|
||||
private function getBaseStatement() {
|
||||
$query = "FOR a in @@collection\n";
|
||||
|
||||
$filter = [];
|
||||
$bindings = ['@collection' => $this->collection];
|
||||
$counter = 0;
|
||||
foreach ($this->params as $k => $v) {
|
||||
$filter[] = " a.@filter_field_$counter == @filter_value_$counter ";
|
||||
$bindings["filter_field_$counter"] = $k;
|
||||
$bindings["filter_value_$counter"] = $v;
|
||||
$counter++;
|
||||
}
|
||||
if (count($filter)>0){
|
||||
$query .= "\nFILTER ".implode(" && ", $filter)."\n";
|
||||
}
|
||||
|
||||
if ($this->sort) {
|
||||
$query .= "\n SORT a." . $this->sort;
|
||||
}
|
||||
|
||||
$statement = $this->arango->statement([
|
||||
'query' => $query,
|
||||
'count' => true,
|
||||
'bindVars' => $bindings,
|
||||
'fullCount' => true,
|
||||
]);
|
||||
return $statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function prepareTotalCount()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -2,18 +2,96 @@
|
|||
|
||||
namespace devgroup\arangodb;
|
||||
|
||||
use \triagens\ArangoDb\ConnectionOptions;
|
||||
use triagens\ArangoDb\CollectionHandler;
|
||||
use triagens\ArangoDb\ConnectionOptions;
|
||||
use triagens\ArangoDb\Document;
|
||||
use triagens\ArangoDb\DocumentHandler;
|
||||
use triagens\ArangoDb\Statement;
|
||||
use triagens\ArangoDb\UpdatePolicy;
|
||||
|
||||
class Connection extends \triagens\ArangoDb\Connection {
|
||||
public function json_encode_wrapper($data, $options = null)
|
||||
use yii\base\Object;
|
||||
|
||||
class Connection extends Object
|
||||
{
|
||||
if ($this->getOption(ConnectionOptions::OPTION_CHECK_UTF8_CONFORM) === true) {
|
||||
self::check_encoding($data);
|
||||
private $connection = null;
|
||||
|
||||
public $connectionOptions = [
|
||||
// server endpoint to connect to
|
||||
ConnectionOptions::OPTION_ENDPOINT => 'tcp://127.0.0.1:8529',
|
||||
// authorization type to use (currently supported: 'Basic')
|
||||
ConnectionOptions::OPTION_AUTH_TYPE => 'Basic',
|
||||
// user for basic authorization
|
||||
ConnectionOptions::OPTION_AUTH_USER => 'root',
|
||||
// password for basic authorization
|
||||
ConnectionOptions::OPTION_AUTH_PASSWD => '',
|
||||
// connection persistence on server. can use either 'Close'
|
||||
// (one-time connections) or 'Keep-Alive' (re-used connections)
|
||||
ConnectionOptions::OPTION_CONNECTION => 'Close',
|
||||
// connect timeout in seconds
|
||||
ConnectionOptions::OPTION_TIMEOUT => 3,
|
||||
// whether or not to reconnect when a keep-alive connection has timed out on server
|
||||
ConnectionOptions::OPTION_RECONNECT => true,
|
||||
// optionally create new collections when inserting documents
|
||||
ConnectionOptions::OPTION_CREATE => true,
|
||||
// optionally create new collections when inserting documents
|
||||
ConnectionOptions::OPTION_UPDATE_POLICY => UpdatePolicy::LAST,
|
||||
];
|
||||
|
||||
/** @var null|CollectionHandler $collectionHandler */
|
||||
private $collectionHandler = null;
|
||||
/** @var null|DocumentHandler $documentHandler */
|
||||
private $documentHandler = null;
|
||||
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$this->connection = new ArangoDbConnection($this->connectionOptions);
|
||||
$this->collectionHandler = new CollectionHandler($this->connection);
|
||||
$this->documentHandler = new DocumentHandler($this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|CollectionHandler
|
||||
*/
|
||||
public function getCollectionHandler()
|
||||
{
|
||||
return $this->collectionHandler;
|
||||
}
|
||||
|
||||
$response = json_encode($data, $options | JSON_FORCE_OBJECT);
|
||||
/**
|
||||
* @param $collectionId
|
||||
* @return \triagens\ArangoDb\Collection
|
||||
*/
|
||||
public function getCollection($collectionId)
|
||||
{
|
||||
return $this->getCollectionHandler()->get($collectionId);
|
||||
}
|
||||
|
||||
return $response;
|
||||
/**
|
||||
* @return null|DocumentHandler
|
||||
*/
|
||||
public function getDocumentHandler()
|
||||
{
|
||||
return $this->documentHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $collectionId
|
||||
* @param $documentId
|
||||
* @return Document
|
||||
*/
|
||||
public function getDocument($collectionId, $documentId)
|
||||
{
|
||||
return $this->getDocumentHandler()->get($collectionId, $documentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return Statement
|
||||
*/
|
||||
public function getStatement($options = [])
|
||||
{
|
||||
return new Statement($this->connection, $options);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace devgroup\arangodb;
|
||||
|
||||
class Exception extends \yii\base\Exception
|
||||
{
|
||||
/**
|
||||
* @return string the user-friendly name of this exception
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'ArangoDB Exception';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace devgroup\arangodb;
|
||||
|
||||
use yii\base\Component;
|
||||
use yii\db\MigrationInterface;
|
||||
use yii\di\Instance;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
abstract class Migration extends Component implements MigrationInterface
|
||||
{
|
||||
/**
|
||||
* @var Connection|string the DB connection object or the application component ID of the DB connection
|
||||
* that this migration should work with.
|
||||
*/
|
||||
public $db = 'arangodb';
|
||||
|
||||
/**
|
||||
* Initializes the migration.
|
||||
* This method will set [[db]] to be the 'db' application component, if it is null.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->db = Instance::ensure($this->db, Connection::className());
|
||||
}
|
||||
|
||||
public function execute($aql, $bindValues = [], $params = [])
|
||||
{
|
||||
echo " > execute AQL: $aql ...";
|
||||
$time = microtime(true);
|
||||
$options = [
|
||||
'query' => $aql,
|
||||
'bindValues' => $bindValues,
|
||||
];
|
||||
$options = ArrayHelper::merge($params, $options);
|
||||
$statement = $this->db->getStatement($options);
|
||||
$statement->execute();
|
||||
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
|
||||
}
|
||||
|
||||
public function insert($collection, $columns)
|
||||
{
|
||||
echo " > insert into $collection ...";
|
||||
$time = microtime(true);
|
||||
$this->db->getDocumentHandler()->save($collection, $columns);
|
||||
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
|
||||
}
|
||||
|
||||
public function update($collection, $columns, $condition = '', $params = [])
|
||||
{
|
||||
echo " > update $collection ...";
|
||||
$time = microtime(true);
|
||||
(new Query())->update($collection, $columns, $condition, $params)->execute();
|
||||
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
|
||||
}
|
||||
|
||||
public function delete($collection, $condition = '', $params = [])
|
||||
{
|
||||
echo " > delete from $collection ...";
|
||||
$time = microtime(true);
|
||||
(new Query())->remove($collection, $condition, $params)->execute();
|
||||
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
|
||||
}
|
||||
|
||||
public function createCollection($collection, $options = [])
|
||||
{
|
||||
echo " > create collection $collection ...";
|
||||
$time = microtime(true);
|
||||
$this->db->getCollectionHandler()->create($collection, $options);
|
||||
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
|
||||
}
|
||||
|
||||
public function dropCollection($collection)
|
||||
{
|
||||
echo " > drop collection $collection ...";
|
||||
$time = microtime(true);
|
||||
$this->db->getCollectionHandler()->drop($collection);
|
||||
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
|
||||
}
|
||||
|
||||
public function truncateCollection($collection)
|
||||
{
|
||||
echo " > truncate collection $collection ...";
|
||||
$time = microtime(true);
|
||||
$this->db->getCollectionHandler()->truncate($collection);
|
||||
echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,606 @@
|
|||
<?php
|
||||
|
||||
namespace devgroup\arangodb;
|
||||
|
||||
use Yii;
|
||||
use yii\base\Component;
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\db\QueryInterface;
|
||||
use yii\helpers\ArrayHelper;
|
||||
use yii\helpers\Json;
|
||||
use yii\helpers\VarDumper;
|
||||
|
||||
use triagens\ArangoDb\Statement;
|
||||
|
||||
class Query extends Component implements QueryInterface
|
||||
{
|
||||
const PARAM_PREFIX = 'qp';
|
||||
|
||||
public $separator = " ";
|
||||
|
||||
protected $conditionBuilders = [
|
||||
'NOT' => 'buildNotCondition',
|
||||
'AND' => 'buildAndCondition',
|
||||
'OR' => 'buildAndCondition',
|
||||
'IN' => 'buildInCondition',
|
||||
'LIKE' => 'buildLikeCondition',
|
||||
];
|
||||
|
||||
protected $conditionMap = [
|
||||
'NOT' => '!',
|
||||
'AND' => '&&',
|
||||
'OR' => '||',
|
||||
'IN' => 'in',
|
||||
'LIKE' => 'LIKE',
|
||||
];
|
||||
|
||||
public $select = [];
|
||||
|
||||
public $from;
|
||||
|
||||
public $where;
|
||||
|
||||
public $limit;
|
||||
|
||||
public $offset;
|
||||
|
||||
public $orderBy;
|
||||
|
||||
public $indexBy;
|
||||
|
||||
public $params = [];
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @param null|Connection $db
|
||||
* @return null|Statement
|
||||
*/
|
||||
private function getStatement($options = [], $db = null)
|
||||
{
|
||||
if ($db === null) {
|
||||
$db = Yii::$app->get('arangodb');
|
||||
}
|
||||
|
||||
return $db->getStatement($options);
|
||||
}
|
||||
|
||||
public function createCommand($options = [])
|
||||
{
|
||||
list ($aql, $params) = $this->buildQuery($this);
|
||||
|
||||
$options = ArrayHelper::merge(
|
||||
$options,
|
||||
[
|
||||
'query' => $aql,
|
||||
'bindVars' => $params,
|
||||
]
|
||||
);
|
||||
|
||||
return $this->getStatement($options);
|
||||
}
|
||||
|
||||
public function update($collection, $columns, $condition, $params)
|
||||
{
|
||||
$clauses = [
|
||||
$this->buildFrom($collection),
|
||||
$this->buildWhere($condition, $params),
|
||||
$this->buildUpdate($collection, $columns),
|
||||
];
|
||||
|
||||
$aql = implode($this->separator, array_filter($clauses));
|
||||
|
||||
$options = ArrayHelper::merge(
|
||||
$params,
|
||||
[
|
||||
'query' => $aql,
|
||||
'bindVars' => $params,
|
||||
]
|
||||
);
|
||||
|
||||
return $this->getStatement($options);
|
||||
}
|
||||
|
||||
public function remove($collection, $condition, $params)
|
||||
{
|
||||
$clauses = [
|
||||
$this->buildFrom($collection),
|
||||
$this->buildWhere($condition, $params),
|
||||
$this->buildRemove($collection),
|
||||
];
|
||||
|
||||
$aql = implode($this->separator, array_filter($clauses));
|
||||
|
||||
$options = ArrayHelper::merge(
|
||||
$params,
|
||||
[
|
||||
'query' => $aql,
|
||||
'bindVars' => $params,
|
||||
]
|
||||
);
|
||||
|
||||
return $this->getStatement($options);
|
||||
}
|
||||
|
||||
protected function buildUpdate($collection, $columns)
|
||||
{
|
||||
return 'UPDATE ' . $collection . 'WITH ' . Json::encode($columns) . ' IN ' . $collection;
|
||||
}
|
||||
|
||||
protected function buildRemove($collection)
|
||||
{
|
||||
return 'REMOVE ' . $collection . ' IN ' . $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $fields
|
||||
* @return $this
|
||||
*/
|
||||
public function select($fields)
|
||||
{
|
||||
$this->select = $fields;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function from($collection)
|
||||
{
|
||||
$this->from = $collection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function buildFrom($collection)
|
||||
{
|
||||
$collection = trim($collection);
|
||||
return $collection ? "FOR $collection IN $collection" : '';
|
||||
}
|
||||
|
||||
public function quoteCollectionName($name)
|
||||
{
|
||||
if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
|
||||
return $name;
|
||||
}
|
||||
if (strpos($name, '.') === false) {
|
||||
return $name;
|
||||
}
|
||||
$parts = explode('.', $name);
|
||||
foreach ($parts as $i => $part) {
|
||||
$parts[$i] = $part;
|
||||
}
|
||||
|
||||
return implode('.', $parts);
|
||||
|
||||
}
|
||||
|
||||
public function quoteColumnName($name)
|
||||
{
|
||||
if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) {
|
||||
return $name;
|
||||
}
|
||||
if (($pos = strrpos($name, '.')) !== false) {
|
||||
$prefix = substr($name, 0, $pos);
|
||||
$prefix = $this->quoteCollectionName($prefix) . '.';
|
||||
$name = substr($name, $pos + 1);
|
||||
} else {
|
||||
$prefix = $this->quoteCollectionName($this->from) . '.';
|
||||
}
|
||||
|
||||
return $prefix . $name;
|
||||
}
|
||||
|
||||
protected function buildWhere($condition, &$params)
|
||||
{
|
||||
$where = $this->buildCondition($condition, $params);
|
||||
|
||||
return $where === '' ? '' : 'FILTER ' . $where;
|
||||
}
|
||||
|
||||
protected function buildCondition($condition, &$params)
|
||||
{
|
||||
if (!is_array($condition)) {
|
||||
return (string) $condition;
|
||||
} elseif (empty($condition)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
|
||||
$operator = strtoupper($condition[0]);
|
||||
if (isset($this->conditionBuilders[$operator])) {
|
||||
$method = $this->conditionBuilders[$operator];
|
||||
array_shift($condition);
|
||||
return $this->$method($operator, $condition, $params);
|
||||
} else {
|
||||
throw new InvalidParamException('Found unknown operator in query: ' . $operator);
|
||||
}
|
||||
} else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
|
||||
return $this->buildHashCondition($condition, $params);
|
||||
}
|
||||
}
|
||||
|
||||
protected function buildHashCondition($condition, &$params)
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($condition as $column => $value) {
|
||||
if (is_array($value) || $value instanceof Query) {
|
||||
// IN condition
|
||||
$parts[] = $this->buildInCondition('IN', [$column, $value], $params);
|
||||
} else {
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->quoteColumnName($column);
|
||||
}
|
||||
if ($value === null) {
|
||||
$parts[] = "$column == null";
|
||||
} else {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$parts[] = "$column==@$phName";
|
||||
$params[$phName] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count($parts) === 1 ? $parts[0] : '(' . implode(') && (', $parts) . ')';
|
||||
}
|
||||
|
||||
protected function buildAndCondition($operator, $operands, &$params)
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($operands as $operand) {
|
||||
if (is_array($operand)) {
|
||||
$operand = $this->buildCondition($operand, $params);
|
||||
}
|
||||
if ($operand !== '') {
|
||||
$parts[] = $operand;
|
||||
}
|
||||
}
|
||||
if (!empty($parts)) {
|
||||
return '(' . implode(") {$this->conditionMap[$operator]} (", $parts) . ')';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
protected function buildNotCondition($operator, $operands, &$params)
|
||||
{
|
||||
if (count($operands) != 1) {
|
||||
throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
|
||||
}
|
||||
|
||||
$operand = reset($operands);
|
||||
if (is_array($operand)) {
|
||||
$operand = $this->buildCondition($operand, $params);
|
||||
}
|
||||
if ($operand === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return "{$this->conditionMap[$operator]} ($operand)";
|
||||
}
|
||||
|
||||
protected function buildInCondition($operator, $operands, &$params)
|
||||
{
|
||||
if (!isset($operands[0], $operands[1])) {
|
||||
throw new Exception("Operator '$operator' requires two operands.");
|
||||
}
|
||||
|
||||
list($column, $values) = $operands;
|
||||
|
||||
if ($values === [] || $column === []) {
|
||||
return $operator === 'IN' ? '0=1' : '';
|
||||
}
|
||||
|
||||
if ($values instanceof Query) {
|
||||
// sub-query
|
||||
list($sql, $params) = $this->buildQuery($values, $params);
|
||||
$column = (array)$column;
|
||||
if (is_array($column)) {
|
||||
foreach ($column as $i => $col) {
|
||||
if (strpos($col, '(') === false) {
|
||||
$column[$i] = $this->quoteColumnName($col);
|
||||
}
|
||||
}
|
||||
return '(' . implode(', ', $column) . ") {$this->conditionMap[$operator]} ($sql)";
|
||||
} else {
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->quoteColumnName($column);
|
||||
}
|
||||
return "$column {$this->conditionMap[$operator]} ($sql)";
|
||||
}
|
||||
}
|
||||
|
||||
$values = (array) $values;
|
||||
|
||||
if (count($column) > 1) {
|
||||
return $this->buildCompositeInCondition($operator, $column, $values, $params);
|
||||
}
|
||||
|
||||
if (is_array($column)) {
|
||||
$column = reset($column);
|
||||
}
|
||||
foreach ($values as $i => $value) {
|
||||
if (is_array($value)) {
|
||||
$value = isset($value[$column]) ? $value[$column] : null;
|
||||
}
|
||||
if ($value === null) {
|
||||
$values[$i] = 'null';
|
||||
} else {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$params[$phName] = $value;
|
||||
$values[$i] = "@$phName";
|
||||
}
|
||||
}
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->quoteColumnName($column);
|
||||
}
|
||||
|
||||
if (count($values) > 1) {
|
||||
return "$column {$this->conditionMap[$operator]} [" . implode(', ', $values) . ']';
|
||||
} else {
|
||||
$operator = $operator === 'IN' ? '==' : '!=';
|
||||
return $column . $operator . reset($values);
|
||||
}
|
||||
}
|
||||
|
||||
protected function buildCompositeInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
$vss = [];
|
||||
foreach ($values as $value) {
|
||||
$vs = [];
|
||||
foreach ($columns as $column) {
|
||||
if (isset($value[$column])) {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$params[$phName] = $value[$column];
|
||||
$vs[] = "@$phName";
|
||||
} else {
|
||||
$vs[] = 'null';
|
||||
}
|
||||
}
|
||||
$vss[] = '(' . implode(', ', $vs) . ')';
|
||||
}
|
||||
foreach ($columns as $i => $column) {
|
||||
if (strpos($column, '(') === false) {
|
||||
$columns[$i] = $this->quoteColumnName($column);
|
||||
}
|
||||
}
|
||||
|
||||
return '(' . implode(', ', $columns) . ") {$this->conditionMap[$operator]} [" . implode(', ', $vss) . ']';
|
||||
}
|
||||
|
||||
protected function buildLikeCondition($operator, $condition, &$params)
|
||||
{
|
||||
if (!(isset($condition[0]) && isset($condition[1]))) {
|
||||
throw new InvalidParamException("You must set 'column' and 'pattern' params");
|
||||
}
|
||||
$caseInsensitive = isset($condition[2]) ? (bool)$condition[2] : false;
|
||||
return $this->conditionMap[$operator]
|
||||
. '('
|
||||
. $this->quoteColumnName($condition[0])
|
||||
. ', "'
|
||||
. $condition[1]
|
||||
. '", '
|
||||
. ($caseInsensitive ? 'TRUE' : 'FALSE')
|
||||
. ')';
|
||||
}
|
||||
|
||||
protected function buildOrderBy($columns)
|
||||
{
|
||||
if (empty($columns)) {
|
||||
return '';
|
||||
}
|
||||
$orders = [];
|
||||
foreach ($columns as $name => $direction) {
|
||||
$orders[] = $this->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
|
||||
}
|
||||
|
||||
return 'SORT ' . implode(', ', $orders);
|
||||
}
|
||||
|
||||
protected function hasLimit($limit)
|
||||
{
|
||||
return is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0;
|
||||
}
|
||||
|
||||
protected function hasOffset($offset)
|
||||
{
|
||||
return is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0';
|
||||
}
|
||||
|
||||
protected function buildLimit($limit, $offset)
|
||||
{
|
||||
$aql = '';
|
||||
if ($this->hasLimit($limit)) {
|
||||
$aql = 'LIMIT ' . $limit;
|
||||
if ($this->hasOffset($offset)) {
|
||||
$aql .= ', ' . $offset;
|
||||
}
|
||||
}
|
||||
|
||||
return $aql;
|
||||
}
|
||||
|
||||
protected function buildSelect($columns, &$params)
|
||||
{
|
||||
if ($columns == null || empty($columns)) {
|
||||
return 'RETURN ' . $this->from;
|
||||
}
|
||||
|
||||
if (!is_array($columns)) {
|
||||
return 'RETURN ' . $columns;
|
||||
}
|
||||
|
||||
$names = '';
|
||||
foreach ($columns as $name => $column) {
|
||||
if (is_int($name)) {
|
||||
$names .= $column . ', ';
|
||||
} else {
|
||||
$names .= "\"$name\": $this->from.$column, ";
|
||||
}
|
||||
}
|
||||
|
||||
return 'RETURN {' . trim($names, ', ') . '}';
|
||||
}
|
||||
|
||||
protected function buildQuery($query = null, $params = [])
|
||||
{
|
||||
$query = isset($query) ? $query : $this;
|
||||
|
||||
if ($query->where === null) {
|
||||
$where = [];
|
||||
} else {
|
||||
$where = $query->where;
|
||||
}
|
||||
|
||||
$params = empty($params) ? $query->params : array_merge($params, $query->params);
|
||||
|
||||
$clauses = [
|
||||
$this->buildFrom($query->from),
|
||||
$this->buildWhere($where, $params),
|
||||
$this->buildOrderBy($query->orderBy, $params),
|
||||
$this->buildLimit($query->limit, $query->offset, $params),
|
||||
$this->buildSelect($query->select, $params),
|
||||
];
|
||||
|
||||
$aql = implode($query->separator, array_filter($clauses));
|
||||
|
||||
return [$aql, $params];
|
||||
}
|
||||
|
||||
public function all($db = null)
|
||||
{
|
||||
$statement = $this->createCommand();
|
||||
$cursor = $statement->execute();
|
||||
return $cursor->getAll();
|
||||
}
|
||||
|
||||
public function one($db = null)
|
||||
{
|
||||
$this->limit(1);
|
||||
$statement = $this->createCommand();
|
||||
$cursor = $statement->execute();
|
||||
$result = $cursor->getAll();
|
||||
return empty($result) ? false : $result[0];
|
||||
}
|
||||
|
||||
public function prepareResult($rows)
|
||||
{
|
||||
if ($this->indexBy === null) {
|
||||
return $rows;
|
||||
}
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
if (is_string($this->indexBy)) {
|
||||
$key = $row[$this->indexBy];
|
||||
} else {
|
||||
$key = call_user_func($this->indexBy, $row);
|
||||
}
|
||||
$result[$key] = $row;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function count($q = '*', $db = null)
|
||||
{
|
||||
$statement = $this->createCommand();
|
||||
$statement->setCount(true);
|
||||
$cursor = $statement->execute();
|
||||
return $cursor->getCount();
|
||||
}
|
||||
|
||||
public function exists($db = null)
|
||||
{
|
||||
$record = $this->one($db);
|
||||
return !empty($record);
|
||||
}
|
||||
|
||||
public function indexBy($column)
|
||||
{
|
||||
$this->indexBy = $column;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where($condition)
|
||||
{
|
||||
$this->where = $condition;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function andWhere($condition)
|
||||
{
|
||||
if ($this->where === null) {
|
||||
$this->where = $condition;
|
||||
} else {
|
||||
$this->where = ['AND', $this->where, $condition];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function orWhere($condition)
|
||||
{
|
||||
if ($this->where === null) {
|
||||
$this->where = $condition;
|
||||
} else {
|
||||
$this->where = ['OR', $this->where, $condition];
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function filterWhere(array $condition)
|
||||
{
|
||||
// TODO: Implement filterWhere() method.
|
||||
}
|
||||
|
||||
public function andFilterWhere(array $condition)
|
||||
{
|
||||
// TODO: Implement andFilterWhere() method.
|
||||
}
|
||||
|
||||
public function orFilterWhere(array $condition)
|
||||
{
|
||||
// TODO: Implement orFilterWhere() method.
|
||||
}
|
||||
|
||||
public function orderBy($columns)
|
||||
{
|
||||
$this->orderBy = $this->normalizeOrderBy($columns);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addOrderBy($columns)
|
||||
{
|
||||
$columns = $this->normalizeOrderBy($columns);
|
||||
if ($this->orderBy === null) {
|
||||
$this->orderBy = $columns;
|
||||
} else {
|
||||
$this->orderBy = array_merge($this->orderBy, $columns);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function limit($limit)
|
||||
{
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function offset($offset)
|
||||
{
|
||||
$this->offset = $offset;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function normalizeOrderBy($columns)
|
||||
{
|
||||
if (is_array($columns)) {
|
||||
return $columns;
|
||||
} else {
|
||||
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
|
||||
$result = [];
|
||||
foreach ($columns as $column) {
|
||||
if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
|
||||
$result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
|
||||
} else {
|
||||
$result[$column] = SORT_ASC;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,14 @@
|
|||
{
|
||||
"name": "Alexander Kozhevnikov",
|
||||
"email": "b37hr3z3n@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Evgeny Dubovitsky",
|
||||
"email": "flynn068@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Pavel Fedotov",
|
||||
"email": "fps.06@mail.ru"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
namespace devgroup\arangodb\console\controllers;
|
||||
|
||||
use devgroup\arangodb\Connection;
|
||||
use devgroup\arangodb\Exception;
|
||||
use devgroup\arangodb\Migration;
|
||||
use devgroup\arangodb\Query;
|
||||
|
||||
use yii;
|
||||
use yii\console\controllers\BaseMigrateController;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
use triagens\ArangoDb\ServerException;
|
||||
|
||||
class MigrateController extends BaseMigrateController
|
||||
{
|
||||
/**
|
||||
* @var string the name of the collection for keeping applied migration information.
|
||||
*/
|
||||
public $migrationCollection = 'migration';
|
||||
/**
|
||||
* @var string the directory storing the migration classes. This can be either
|
||||
* a path alias or a directory.
|
||||
*/
|
||||
public $migrationPath = '@app/migrations/arangodb';
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $templateFile = '@devgroup/arangodb/views/migration.php';
|
||||
/**
|
||||
* @var Connection|string the DB connection object or the application
|
||||
* component ID of the DB connection.
|
||||
*/
|
||||
public $db = 'arangodb';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function options($actionId)
|
||||
{
|
||||
return array_merge(
|
||||
parent::options($actionId),
|
||||
['migrationCollection', 'db'] // global for all actions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked right before an action is to be executed (after all possible filters.)
|
||||
* It checks the existence of the [[migrationPath]].
|
||||
* @param yii\base\Action $action the action to be executed.
|
||||
* @throws Exception if db component isn't configured
|
||||
* @return boolean whether the action should continue to be executed.
|
||||
*/
|
||||
public function beforeAction($action)
|
||||
{
|
||||
if (parent::beforeAction($action)) {
|
||||
if ($action->id !== 'create') {
|
||||
if (is_string($this->db)) {
|
||||
$this->db = \Yii::$app->get($this->db);
|
||||
}
|
||||
if (!$this->db instanceof Connection) {
|
||||
throw new Exception("The 'db' option must refer to the application component ID of a ArangoDB connection.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new migration instance.
|
||||
* @param string $class the migration class name
|
||||
* @return Migration the migration instance
|
||||
*/
|
||||
protected function createMigration($class)
|
||||
{
|
||||
$file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
|
||||
require_once($file);
|
||||
|
||||
return new $class(['db' => $this->db]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function getMigrationHistory($limit)
|
||||
{
|
||||
try {
|
||||
$history = $this->getHistory($limit);
|
||||
} catch (ServerException $ex) {
|
||||
if ($ex->getServerCode() == 1203) {
|
||||
$this->createMigrationHistoryCollection();
|
||||
$history = $this->getHistory($limit);
|
||||
} else {
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
unset($history[self::BASE_MIGRATION]);
|
||||
|
||||
return $history;
|
||||
}
|
||||
|
||||
private function getHistory($limit)
|
||||
{
|
||||
$query = new Query;
|
||||
$rows = $query->select(['version' => 'version', 'apply_time' => 'apply_time'])
|
||||
->from($this->migrationCollection)
|
||||
->orderBy('version DESC')
|
||||
->limit($limit)
|
||||
->all($this->db);
|
||||
$history = ArrayHelper::map($rows, 'version', 'apply_time');
|
||||
unset($history[self::BASE_MIGRATION]);
|
||||
|
||||
return $history;
|
||||
}
|
||||
|
||||
protected function createMigrationHistoryCollection()
|
||||
{
|
||||
echo "Creating migration history collection \"$this->migrationCollection\"...";
|
||||
$this->db->getCollectionHandler()->create($this->migrationCollection);
|
||||
$this->db->getDocumentHandler()->save(
|
||||
$this->migrationCollection,
|
||||
[
|
||||
'version' => self::BASE_MIGRATION,
|
||||
'apply_time' => time(),
|
||||
]
|
||||
);
|
||||
echo "done.\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function addMigrationHistory($version)
|
||||
{
|
||||
$this->db->getDocumentHandler()->save(
|
||||
$this->migrationCollection,
|
||||
[
|
||||
'version' => $version,
|
||||
'apply_time' => time(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function removeMigrationHistory($version)
|
||||
{
|
||||
$this->db->getCollectionHandler()->removeByExample(
|
||||
$this->migrationCollection,
|
||||
[
|
||||
'version' => $version,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* This view is used by console/controllers/MigrateController.php
|
||||
* The following variables are available in this view:
|
||||
*/
|
||||
|
||||
/* @var $className string the new migration class name */
|
||||
echo "<?php\n";
|
||||
?>
|
||||
|
||||
class <?= $className ?> extends \app\components\arangodb\Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
echo "<?= $className ?> cannot be reverted.\n";
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue