diff --git a/core/DB/DB.class.php b/core/DB/DB.class.php
index 9b59e88637..8847e7d6f2 100644
--- a/core/DB/DB.class.php
+++ b/core/DB/DB.class.php
@@ -415,14 +415,6 @@ public function setEncoding($encoding)
return $this;
}
- public function registerUncacher(UncacherBase $uncacher)
- {
- $uncacher->uncache();
- if ($this->inTransaction()) {
- $this->getUncacher()->merge($uncacher);
- }
- }
-
/**
* @param string $savepointName
* @return DB
@@ -434,7 +426,7 @@ private function addSavepoint($savepointName)
$this->savepointList[$savepointName] = true;
return $this;
- }
+ }
/**
* @param string $savepointName
@@ -452,13 +444,21 @@ private function dropSavepoint($savepointName)
private function checkSavepointExist($savepointName)
{
return isset($this->savepointList[$savepointName]);
- }
+ }
private function assertSavePointName($savepointName)
{
Assert::isEqual(1, preg_match('~^[A-Za-z][A-Za-z0-9]*$~iu', $savepointName));
}
+ public function registerUncacher(UncacherBase $uncacher)
+ {
+ $uncacher->uncache();
+ if ($this->inTransaction()) {
+ $this->getUncacher()->merge($uncacher);
+ }
+ }
+
/**
* @return UncachersPool
*/
@@ -470,8 +470,9 @@ private function getUncacher()
private function triggerUncacher()
{
if ($this->uncacher) {
- $this->uncacher->uncache();
+ $uncacher = $this->uncacher;
$this->uncacher = null;
+ $uncacher->uncache();
}
}
}
diff --git a/core/Exceptions/DeadlockException.class.php b/core/Exceptions/DeadlockException.class.php
new file mode 100644
index 0000000000..a4e8175b98
--- /dev/null
+++ b/core/Exceptions/DeadlockException.class.php
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/core/OSQL/FromTable.class.php b/core/OSQL/FromTable.class.php
index fe5fee2566..ade028ee22 100644
--- a/core/OSQL/FromTable.class.php
+++ b/core/OSQL/FromTable.class.php
@@ -15,7 +15,7 @@
* @ingroup OSQL
* @ingroup Module
**/
- final class FromTable implements Aliased, SQLTableName
+ final class FromTable implements Aliased, SQLTableName, SQLRealTableName
{
private $table = null;
private $alias = null;
@@ -85,5 +85,10 @@ public function getTable()
{
return $this->alias ? $this->alias : $this->table;
}
+
+ public function getRealTable()
+ {
+ return $this->table;
+ }
}
?>
\ No newline at end of file
diff --git a/core/OSQL/Joiner.class.php b/core/OSQL/Joiner.class.php
index 2df9c56a21..6a96001c4b 100644
--- a/core/OSQL/Joiner.class.php
+++ b/core/OSQL/Joiner.class.php
@@ -99,6 +99,10 @@ public function getLastTable()
return null;
}
+ public function getTables() {
+ return $this->from ?: array();
+ }
+
public function toDialectString(Dialect $dialect)
{
$fromString = null;
diff --git a/core/OSQL/SQLBaseJoin.class.php b/core/OSQL/SQLBaseJoin.class.php
index 00468053b6..a2791df2ad 100644
--- a/core/OSQL/SQLBaseJoin.class.php
+++ b/core/OSQL/SQLBaseJoin.class.php
@@ -12,7 +12,7 @@
/**
* @ingroup OSQL
**/
- abstract class SQLBaseJoin implements SQLTableName, Aliased
+ abstract class SQLBaseJoin implements SQLTableName, SQLRealTableName, Aliased
{
protected $subject = null;
protected $alias = null;
@@ -35,6 +35,11 @@ public function getTable()
return $this->alias ? $this->alias : $this->subject;
}
+ public function getRealTable()
+ {
+ return $this->subject;
+ }
+
protected function baseToString(Dialect $dialect, $logic = null)
{
return
diff --git a/core/OSQL/SQLRealTableName.class.php b/core/OSQL/SQLRealTableName.class.php
new file mode 100644
index 0000000000..c144c2b3b5
--- /dev/null
+++ b/core/OSQL/SQLRealTableName.class.php
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/core/OSQL/SelectQuery.class.php b/core/OSQL/SelectQuery.class.php
index c72300b568..5097dd90d8 100644
--- a/core/OSQL/SelectQuery.class.php
+++ b/core/OSQL/SelectQuery.class.php
@@ -99,6 +99,11 @@ public function hasJoinedTable($table)
return $this->joiner->hasJoinedTable($table);
}
+ public function getJoinedTables()
+ {
+ return $this->joiner->getTables();
+ }
+
/**
* @return SelectQuery
**/
diff --git a/main/DAOs/Uncachers/UncacherTaggableDaoWorker.class.php b/main/DAOs/Uncachers/UncacherTaggableDaoWorker.class.php
index 1f32c95d49..5729cfc382 100644
--- a/main/DAOs/Uncachers/UncacherTaggableDaoWorker.class.php
+++ b/main/DAOs/Uncachers/UncacherTaggableDaoWorker.class.php
@@ -19,14 +19,15 @@ class UncacherTaggableDaoWorker implements UncacherBase
/**
* @return UncacherTaggableDaoWorker
*/
- public static function create($className, $idKey, $tags, TaggableDaoWorker $worker)
+ public static function create($className, $idKey, array $tags = array())
{
- return new self($className, $idKey, $tags, $worker);
+ return new self($className, $idKey, $tags);
}
- public function __construct($className, $idKey, $tags, TaggableDaoWorker $worker)
+ public function __construct($className, $idKey, array $tags = array())
{
- $this->classNameMap[$className] = array(array($idKey), $tags, $worker);
+ $idKeyList = $idKey ? array($idKey) : array();
+ $this->classNameMap[$className] = array($idKeyList, $tags);
}
/**
@@ -49,14 +50,18 @@ public function merge(UncacherBase $uncacher)
public function uncache()
{
foreach ($this->classNameMap as $className => $uncaches) {
- list($idKeys, $tags, $worker) = $uncaches;
- /* @var $worker TaggableDaoWorker */
+ list($idKeys, $tags) = $uncaches;
+ $dao = ClassUtils::callStaticMethod("$className::dao");
+ /* @var $dao StorableDAO */
+ $worker = Cache::worker($dao);
+ Assert::isInstance($worker, 'TaggableDaoWorker');
+
$worker->expireTags($tags);
foreach ($idKeys as $key)
- Cache::me()->mark($className)->delete($idKey);
+ Cache::me()->mark($className)->delete($key);
- ClassUtils::callStaticMethod("$className::dao")->uncacheLists();
+ $dao->uncacheLists();
}
}
diff --git a/main/DAOs/Uncachers/UncacherTaggableDaoWorkerTags.class.php b/main/DAOs/Uncachers/UncacherTaggableDaoWorkerTags.class.php
new file mode 100644
index 0000000000..5f8ce03048
--- /dev/null
+++ b/main/DAOs/Uncachers/UncacherTaggableDaoWorkerTags.class.php
@@ -0,0 +1,76 @@
+classNameMap[$className] = $tags;
+ }
+
+ /**
+ * @return array
+ */
+ public function getClassNameMap()
+ {
+ return $this->classNameMap;
+ }
+ /**
+ * @param $uncacher UncacherTaggableDaoWorkerTags same as self class
+ * @return BaseUncacher (this)
+ */
+ public function merge(UncacherBase $uncacher)
+ {
+ Assert::isInstance($uncacher, 'UncacherTaggableDaoWorkerTags');
+ return $this->mergeSelf($uncacher);
+ }
+
+ public function uncache()
+ {
+ foreach ($this->classNameMap as $className => $tags) {
+ $dao = ClassUtils::callStaticMethod("$className::dao");
+ /* @var $dao StorableDAO */
+ $worker = Cache::worker($dao);
+ Assert::isInstance($worker, 'TaggableDaoWorker');
+
+ $worker->expireTags($tags);
+ }
+ }
+
+ private function mergeSelf(UncacherTaggableDaoWorkerTags $uncacher) {
+ foreach ($uncacher->getClassNameMap() as $className => $tags) {
+ if (!isset($this->classNameMap[$className])) {
+ $this->classNameMap[$className] = $tags;
+ } else {
+ //merging idkeys
+ $this->classNameMap[$className] = ArrayUtils::mergeUnique(
+ $this->classNameMap[$className],
+ $tags
+ );
+ }
+ }
+ return $this;
+ }
+ }
+?>
\ No newline at end of file
diff --git a/main/DAOs/Workers/TaggableDaoWorker.class.php b/main/DAOs/Workers/TaggableDaoWorker.class.php
new file mode 100644
index 0000000000..e8838b4a31
--- /dev/null
+++ b/main/DAOs/Workers/TaggableDaoWorker.class.php
@@ -0,0 +1,445 @@
+updateTags($tags);
+
+ return $this;
+ }
+
+ /// cachers
+ //@{
+ protected function cacheByQuery(
+ SelectQuery $query,
+ /* Identifiable */ $object,
+ $expires = Cache::EXPIRES_FOREVER
+ )
+ {
+ $key = $this->makeQueryKey($query, self::SUFFIX_QUERY);
+
+ Cache::me()->
+ mark($this->className)->
+ set(
+ $key,
+ array(
+ 'tags' => $this->getTagsForQuery($query),
+ 'data' => $object,
+ ),
+ $expires
+ );
+
+// SemaphorePool::me()->free(self::LOCK_PREFIX.$key);
+
+ return $object;
+ }
+
+ protected function cacheById(
+ Identifiable $object, $expires = Cache::EXPIRES_FOREVER
+ )
+ {
+ if ($expires !== Cache::DO_NOT_CACHE) {
+
+ Cache::me()->
+ mark($this->className)->
+ set(
+ $this->makeIdKey($object->getId()),
+ array(
+ 'tags' => $this->getTagsForObject($object),
+ 'data' => $object,
+ ),
+ $expires
+ );
+ }
+
+ return $object;
+ }
+
+ protected function cacheListByQuery(
+ SelectQuery $query,
+ /* array || Cache::NOT_FOUND */ $array,
+ $expires = Cache::EXPIRES_FOREVER
+ )
+ {
+ if ($array !== Cache::NOT_FOUND) {
+ Assert::isArray($array);
+ Assert::isTrue(current($array) instanceof Identifiable);
+ }
+
+ $key = $this->makeQueryKey($query, self::SUFFIX_LIST);
+
+ Cache::me()->
+ mark($this->className)->
+ set(
+ $key,
+ array(
+ 'tags' => $this->getTagsForQuery($query),
+ 'data' => $array,
+ ),
+ $expires
+ );
+
+// SemaphorePool::me()->free(self::LOCK_PREFIX.$key);
+
+ return $array;
+ }
+
+ protected function cacheNullById($id, $expires = Cache::EXPIRES_FOREVER)
+ {
+ return
+ Cache::me()->
+ mark($this->className)->
+ add(
+ $this->makeIdKey($id),
+ array(
+ 'tags' => $this->getTagsForNullObject($id),
+ 'data' => Cache::NOT_FOUND,
+ ),
+ $expires
+ );
+ }
+ //@}
+
+ /// getters
+ //@{
+ public function getCachedById($id)
+ {
+ $result =
+ Cache::me()->
+ mark($this->className)->
+ get($this->makeIdKey($id));
+
+ if ($this->checkValid($result))
+ return $result['data'];
+
+ return null;
+ }
+
+ public function getListByIds(array $ids, $expires = Cache::EXPIRES_FOREVER)
+ {
+ $list = array();
+ $toFetch = array();
+ $prefixed = array();
+
+ $proto = $this->dao->getProtoClass();
+
+ $proto->beginPrefetch();
+
+ // dupes, if any, will be resolved later @ ArrayUtils::regularizeList
+ $ids = array_unique($ids);
+
+ foreach ($ids as $id)
+ $prefixed[$id] = $this->makeIdKey($id);
+
+ if (
+ $cachedList
+ = Cache::me()->mark($this->className)->getList($prefixed)
+ ) {
+ foreach ($cachedList as $cached) {
+ if ($this->checkValid($cached)) {
+ $cached = $cached['data'];
+ if ($cached && ($cached !== Cache::NOT_FOUND)) {
+ $list[] = $this->dao->completeObject($cached);
+
+ unset($prefixed[$cached->getId()]);
+ }
+ }
+ }
+ }
+
+ $toFetch += array_keys($prefixed);
+
+ if ($toFetch) {
+ $remainList = array();
+
+ foreach ($toFetch as $id) {
+ try {
+ $remainList[] = $this->getById($id);
+ } catch (ObjectNotFoundException $e) {/*_*/}
+ }
+
+ $list = array_merge($list, $remainList);
+ }
+
+ $proto->endPrefetch($list);
+
+ return $list;
+ }
+ //@}
+
+ /// uncachers
+ //@{
+ public function uncacheById($id)
+ {
+ return $this->registerUncacher($this->getUncacherById($id));
+ }
+
+ /**
+ * @return UncacherBase
+ */
+ public function getUncacherById($id)
+ {
+ $className = $this->className;
+ $idKey = $this->makeIdKey($id);
+
+ try {
+ $object = $this->dao->getById($id);
+ $tags = self::$handler->getUncacheObjectTags($object, $className);
+ } catch (ObjectNotFoundException $e) {
+ $tags = array();
+ }
+
+ return UncacherTaggableDaoWorker::create($className, $idKey, $tags);
+ }
+
+ public function uncacheByIds($ids)
+ {
+ if (empty($ids))
+ return;
+
+ $uncacher = $this->getUncacherById(array_shift($ids));
+
+ foreach ($ids as $id)
+ $uncacher->merge($this->getUncacherById($id));
+
+ return $this->registerUncacher($uncacher);
+ }
+
+ public function uncacheLists()
+ {
+ $tags = self::$handler->getDefaultTags($this->className);
+ return $this->registerUncacher(
+ UncacherTaggableDaoWorkerTags::create($this->className, $tags)
+ );
+ }
+
+ //@}
+
+ /// internal helpers
+ //@{
+ protected function gentlyGetByKey($key)
+ {
+ $result =
+ Cache::me()->mark($this->className)->get($key);
+
+ if ($this->checkValid($result)) {
+ return $result['data'];
+ }
+
+// $pool = SemaphorePool::me();
+//
+// if (!$pool->get(self::LOCK_PREFIX.$key)) {
+// if ($result && isset($result['data'])) {
+// return $result['data'];
+// } else {
+// for ($msec = 0; $msec <= self::LOCK_TIMEOUT; $msec += 200) {
+// usleep(200*1000);
+// if ($pool->get(self::LOCK_PREFIX.$key)) {
+// $result =
+// Cache::me()->mark($this->className)->get($key);
+//
+// $pool->free(self::LOCK_PREFIX.$key);
+//
+// if ($this->checkValid($result)) {
+// return $result['data'];
+// } else {
+// // лока уже нет, а кэш не перестроился
+// continue;
+// }
+// }
+// }
+// // не дождались снятия лока
+// throw new DeadLockException(
+// "Cache deadlock. {$this->className} QueryKey={$key}"
+// );
+// }
+// }
+
+ return null;
+ }
+
+ protected function checkValid($item)
+ {
+ return
+ $item
+ && isset($item['data'])
+ && isset($item['tags'])
+ && $this->checkTagVersions($item['tags']);
+ }
+
+ /**
+ * узнает список тегов которые используются в запросе,
+ */
+ protected function getTagsForQuery(SelectQuery $query)
+ {
+ if (self::$customTags) {
+ $tags = self::$customTags;
+ } else {
+ $tags = self::$handler->getQueryTags($query, $this->className);
+ }
+
+ $tagList = array();
+ foreach ($tags as $tag) {
+ $tagList[$tag] = 0;
+ }
+
+ return $this->getTagVersions($tagList);
+ }
+
+ protected function getTagsForNullObject($id)
+ {
+ $tags = self::$handler->getNullObjectTags($id, $this->className);
+ $tagList = array();
+ foreach ($tags as $tag) {
+ $tagList[$tag] = 0;
+ }
+
+ return $this->getTagVersions($tagList);
+ }
+
+ protected function getTagVersions(/*array*/ $tags)
+ {
+ $time = microtime(true);
+ $tagsToFetch = array_keys($tags);
+
+ if (
+ !$result =
+ Cache::me()->
+ mark(self::TAG_VERSIONS)->
+ getList($tagsToFetch)
+ ) {
+ $result = array();
+ }
+
+ $fetchedTags = array();
+ foreach ($tagsToFetch as $tag) {
+ $fetched = false;
+ foreach ($result as $key => $value) {
+ if (strpos($key, $tag) !== false) {
+ $fetched = true;
+ $fetchedTags[$tag] = $value;
+ }
+ }
+ if (!$fetched) {
+ Cache::me()->
+ mark(self::TAG_VERSIONS)->
+ set(
+ $tag,
+ $time,
+ Cache::EXPIRES_FOREVER
+ );
+
+ $fetchedTags[$tag] = $time;
+ }
+ }
+
+ return $fetchedTags;
+ }
+
+ /**
+ * проверяет версии тегов
+ */
+ protected function checkTagVersions(/*array*/ $tags)
+ {
+ $tagVersions = $this->getTagVersions($tags);
+ if ($tagVersions == $tags) {
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function updateTagVersions(IdentifiableObject $object)
+ {
+ $tags = self::$handler->getUncacheObjectTags($object, $this->className);
+
+ $this->updateTags($tags);
+
+ return true;
+ }
+
+ protected function getTagsForObject(IdentifiableObject $object)
+ {
+ $tags = self::$handler->getCacheObjectTags($object, $this->className);
+ $tagList = array();
+ foreach ($tags as $tag) {
+ $tagList[$tag] = 0;
+ }
+
+ return $this->getTagVersions($tagList);
+ }
+
+ protected function updateTags($tags)
+ {
+ $time = microtime(true);
+ foreach ($tags as $tag) {
+ Cache::me()->
+ mark(self::TAG_VERSIONS)->
+ set(
+ $tag,
+ $time,
+ Cache::EXPIRES_FOREVER
+ );
+ }
+
+ return true;
+ }
+
+ protected function makeIdKey($id)
+ {
+ return parent::makeIdKey($id).self::KEY_POSTFIX;
+ }
+
+ protected function makeQueryKey(SelectQuery $query, $suffix)
+ {
+ return parent::makeQueryKey($query, $suffix).self::KEY_POSTFIX;
+ }
+ //@}
+ }
+?>
\ No newline at end of file
diff --git a/main/DAOs/Workers/TaggableHandler.class.php b/main/DAOs/Workers/TaggableHandler.class.php
new file mode 100644
index 0000000000..02c5ace6ed
--- /dev/null
+++ b/main/DAOs/Workers/TaggableHandler.class.php
@@ -0,0 +1,30 @@
+
\ No newline at end of file
diff --git a/main/DAOs/Workers/TaggableLayerHandler.class.php b/main/DAOs/Workers/TaggableLayerHandler.class.php
new file mode 100644
index 0000000000..98c21a4542
--- /dev/null
+++ b/main/DAOs/Workers/TaggableLayerHandler.class.php
@@ -0,0 +1,42 @@
+getDefaultTags($className);
+ }
+
+ public function getDefaultTags($className)
+ {
+ return array($className);
+ }
+ }
+?>
\ No newline at end of file
diff --git a/main/DAOs/Workers/TaggableSmartHandler.class.php b/main/DAOs/Workers/TaggableSmartHandler.class.php
new file mode 100644
index 0000000000..be72b493dc
--- /dev/null
+++ b/main/DAOs/Workers/TaggableSmartHandler.class.php
@@ -0,0 +1,240 @@
+getTagByClassAndId($className, $object->getId()));
+ }
+
+ public function getUncacheObjectTags(IdentifiableObject $object, $className)
+ {
+ $tags = $this->getCacheObjectTags($object, $className);
+ $tags = array_merge($this->getDefaultTags($className), $tags);
+
+ foreach ($this->getLinkProperties($className) as $name => $property) {
+ /* @var $property LightMetaProperty */
+ if ($name == 'id') {
+ continue;
+ }
+ if ($property->getClassName()) {
+ if ($property->getRelationId() == MetaRelation::ONE_TO_ONE) {
+ if ($property->getFetchStrategyId() == FetchStrategy::LAZY) {
+ if ($linkedObjectId = $object->{$property->getGetter().'Id'}()) {
+ $tags[] = $this->getTagByClassAndId($property->getClassName(), $linkedObjectId);
+ }
+ } elseif ($property->getFetchStrategyId()) {
+ if (
+ ($linkedObject = $object->{$property->getGetter()}())
+ && $linkedObject instanceof IdentifiableObject
+ && $linkedObjectId = $linkedObject->getId()
+ ) {
+ $tags[] = $this->getTagByClassAndId($property->getClassName(), $linkedObjectId);
+ }
+ }
+ } elseif ($property->getRelationId() == MetaRelation::MANY_TO_MANY) {
+ $daoHelper = $object->{$property->getGetter()}();
+ /* @var $daoHelper ManyToManyLinked */
+ $tags[] = $daoHelper->getHelperTable();
+ }
+ }
+ }
+
+ return $tags;
+ }
+
+ public function getQueryTags(SelectQuery $query, $className)
+ {
+ $columns = $this->getLinkObjectColumnListByClass($className);
+
+ $tagList = array();
+ if ($query->getTablesCount() > 1 || !$this->isLazy($className)) {
+ foreach ($query->getJoinedTables() as $table) {
+ /* @var $table SQLRealTableName */
+ $tagList[] = $table->getRealTable();
+ }
+ } else {
+ foreach ($query->getWhere() as $whereObject) {
+ if ($whereObject instanceof BinaryExpression) {
+ if ($tag = $this->getTagByBinaryExpression($whereObject, $query, $className, $columns)) {
+ $tagList[] = $tag;
+ }
+ }
+ if ($whereObject instanceof LogicalChain) {
+ foreach ($whereObject->getChain() as $logic) {
+ if ($logic instanceof BinaryExpression) {
+ if ($tag = $this->getTagByBinaryExpression($logic, $query, $className, $columns)) {
+ $tagList[] = $tag;
+ }
+ }
+ }
+ }
+ }
+
+ if (empty($tagList)) {
+ $tagList = $this->getDefaultTags($className);
+ }
+ }
+
+ return $tagList;
+ }
+
+ public function getNullObjectTags($id, $className)
+ {
+ $tags = $this->getDefaultTags($className);
+ $tags[] = $this->getTagByClassAndId($className, $id);
+
+ return $tags;
+ }
+
+ public function getDefaultTags($className)
+ {
+ return array($this->getTableByClassName($className));
+ }
+
+ protected function getTagByClassAndId($className, $id)
+ {
+ return $this->getTableByClassName($className).self::ID_POSTFIX.$id;
+ }
+
+ protected final function getTagByBinaryExpression(BinaryExpression $expression, SelectQuery $query, &$className, &$columns)
+ {
+ $tag = null;
+
+ if ($expression->getLogic() == BinaryExpression::EQUALS) {
+ $first = $expression->getLeft();
+ $second = $expression->getRight();
+ if ($second instanceof DBField || $first instanceof DBValue) {
+ $first = $expression->getRight();
+ $second = $expression->getLeft();
+ }
+
+ $columnClassName = null;
+ $idValue = null;
+ if (
+ $first instanceof DBField
+ && isset($columns[$first->getField()])
+ ) {
+ if ($first->getTable() === null) {
+ $table = $query->getFirstTable();
+ } elseif ($first->getTable() instanceof FromTable) {
+ $table = $first->getTable();
+ }
+ if ($table instanceof FromTable) {
+ $table = $table->getTable();
+ }
+
+ if ($table !== null && $table == $this->getTableByClassName($className)) {
+ $columnClassName = $columns[$first->getField()];
+ }
+ } elseif (is_string($first) && $first && isset($columns[$first])) {
+ $table = $query->getFirstTable();
+ if ($table instanceof FromTable) {
+ $table = $table->getTable();
+ }
+ if ($table !== null && $table == $this->getTableByClassName($className)) {
+ $columnClassName = $columns[$first];
+ }
+ }
+
+ if ($second instanceof DBValue) {
+ $idValue = $second->getValue();
+ } elseif ((is_integer($second) || is_string($second)) && $second) {
+ $idValue = $second;
+ }
+
+ if ($columnClassName && $idValue) {
+ $tag = $this->getTagByClassAndId($columnClassName, $idValue);
+ }
+ }
+
+ return $tag;
+ }
+
+ protected function getLinkObjectColumnListByClass($className)
+ {
+ static $result = array();
+ if (!isset($result[$className])) {
+ $columnList = array();
+ foreach ($this->getLinkProperties($className) as $property) {
+ /* @var $property LightMetaProperty */
+ if ($property->getRelationId() == MetaRelation::ONE_TO_ONE || $property->getName() == 'id') {
+ $columnList[$property->getColumnName()] = $property->getClassName();
+ }
+ }
+ $result[$className] = $columnList;
+ }
+ return $result[$className];
+ }
+
+ protected function getTableByClassName($className) {
+ if (ClassUtils::isInstanceOf($className, 'DAOConnected')) {
+ return ClassUtils::callStaticMethod("{$className}::dao")->getTable();
+ } else {
+ return $className.'|className|';
+ }
+ }
+
+ protected function isLazy($className)
+ {
+ foreach ($this->getLinkProperties($className) as $property) {
+ if (
+ $property->getRelationId() == MetaRelation::ONE_TO_ONE
+ && ($property->getFetchStrategyId() == FetchStrategy::CASCADE
+ || $property->getFetchStrategyId() == FetchStrategy::JOIN)
+ ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ protected function getLinkProperties($className) {
+ $propertyList = array();
+ foreach ($this->getPropertiesByClassName($className) as $name => $property) {
+ if ($property instanceof InnerMetaProperty) {
+ $propertyList = array_merge(
+ $propertyList,
+ $this->getLinkProperties($property->getClassName())
+ );
+ } elseif ($property instanceof LightMetaProperty) {
+ switch ($property->getType()) {
+ case 'identifier':
+ case 'identifierList':
+ case 'integerIdentifier':
+ case 'integerIdentifierList':
+ case 'scalarIdentifier':
+ case 'scalarIdentifierList':
+ if ($property->getClassName()) {
+ $propertyList[] = $property;
+ }
+ break;
+ }
+ }
+ }
+ return $propertyList;
+ }
+
+ protected function getPropertiesByClassName($className)
+ {
+ $proto = ClassUtils::callStaticMethod($className.'::proto');
+
+ return $proto->getPropertyList();
+ }
+ }
+?>
\ No newline at end of file
diff --git a/main/UnifiedContainer/UnifiedContainer.class.php b/main/UnifiedContainer/UnifiedContainer.class.php
index ee7d33a5bd..882189dd61 100644
--- a/main/UnifiedContainer/UnifiedContainer.class.php
+++ b/main/UnifiedContainer/UnifiedContainer.class.php
@@ -330,6 +330,7 @@ public function save()
$this->clones = array();
$this->syncClones();
+ $this->parent->dao()->uncacheById($this->getParentObject()->getId());
$this->dao->uncacheLists();
return $this;
diff --git a/meta/builders/AutoClassBuilder.class.php b/meta/builders/AutoClassBuilder.class.php
index f0995ec7bb..656dc5330a 100644
--- a/meta/builders/AutoClassBuilder.class.php
+++ b/meta/builders/AutoClassBuilder.class.php
@@ -42,13 +42,13 @@ public static function build(MetaClass $class)
if (!self::doPropertyBuild($class, $property, $isNamed))
continue;
- $out .=
- "protected \${$property->getName()} = "
- ."{$property->getType()->getDeclaration()};\n";
-
if ($property->getFetchStrategyId() == FetchStrategy::LAZY) {
$out .=
"protected \${$property->getName()}Id = null;\n";
+ } else {
+ $out .=
+ "protected \${$property->getName()} = "
+ ."{$property->getType()->getDeclaration()};\n";
}
}
diff --git a/meta/types/ObjectType.class.php b/meta/types/ObjectType.class.php
index fa90a9b6ec..ebbec77327 100644
--- a/meta/types/ObjectType.class.php
+++ b/meta/types/ObjectType.class.php
@@ -114,18 +114,16 @@ public function {$methodName}()
{$classHint}
public function {$methodName}()
{
- if (!\$this->{$name} && \$this->{$name}Id) {
- \$this->{$name} = {$fetchObjectString};
+ if (\$this->{$name}Id !== null) {
+ return {$fetchObjectString};
}
- return \$this->{$name};
+ return null;
}
public function {$methodName}Id()
{
- return \$this->{$name}
- ? \$this->{$name}->getId()
- : \$this->{$name}Id;
+ return \$this->{$name}Id;
}
EOT;
@@ -229,7 +227,6 @@ public function {$methodName}({$property->getType()->getClassName()} \${$name})
**/
public function {$methodName}({$this->className} \${$name})
{
- \$this->{$name} = \${$name};
\$this->{$name}Id = \${$name}->getId();
return \$this;
@@ -240,7 +237,6 @@ public function {$methodName}({$this->className} \${$name})
**/
public function {$methodName}Id(\$id)
{
- \$this->{$name} = null;
\$this->{$name}Id = \$id;
return \$this;
@@ -307,7 +303,6 @@ public function {$methodName}()
**/
public function {$methodName}()
{
- \$this->{$name} = null;
\$this->{$name}Id = null;
return \$this;
diff --git a/test/config.inc.php.tpl b/test/config.inc.php.tpl
index 36184d8f65..90497f26e2 100644
--- a/test/config.inc.php.tpl
+++ b/test/config.inc.php.tpl
@@ -48,10 +48,11 @@
$daoWorkers = array(
'NullDaoWorker', 'CommonDaoWorker', 'SmartDaoWorker', 'VoodooDaoWorker',
- 'CacheDaoWorker', 'VoodooDaoWorker', 'SmartDaoWorker', 'CommonDaoWorker', 'NullDaoWorker'
+ 'CacheDaoWorker', 'TaggableDaoWorker', 'VoodooDaoWorker', 'SmartDaoWorker', 'CommonDaoWorker', 'NullDaoWorker'
);
VoodooDaoWorker::setDefaultHandler('CacheSegmentHandler');
+ TaggableDaoWorker::setHandler('TaggableSmartHandler');
define('__LOCAL_DEBUG__', true);
?>
diff --git a/test/core/TaggableSmartHandlerTest.class.php b/test/core/TaggableSmartHandlerTest.class.php
new file mode 100644
index 0000000000..0f03379c5e
--- /dev/null
+++ b/test/core/TaggableSmartHandlerTest.class.php
@@ -0,0 +1,118 @@
+assertEquals(
+ array(TestLazy::dao()->getTable()),
+ $this->spawnHandler()->getQueryTags($criteria->toSelectQuery(), 'TestLazy')
+ );
+ }
+
+ public function testTableLazyListById()
+ {
+ $criteria = Criteria::create(TestLazy::dao())
+ ->add(Expression::eq('id', DBValue::create('33')));
+
+ $expectTags = array(
+ TestLazy::dao()->getTable().TaggableSmartHandler::ID_POSTFIX.'33',
+ );
+ $this->assertEquals(
+ $expectTags,
+ $this->spawnHandler()->getQueryTags($criteria->toSelectQuery(), 'TestLazy')
+ );
+ }
+
+ public function testTableLazyListByCities()
+ {
+ $criteria = Criteria::create(TestLazy::dao())
+ ->add(Expression::eq('city', DBValue::create('1')))
+ ->add(Expression::eq(DBValue::create('2'), 'cityOptional.id'));
+
+ $expectTags = array(
+ TestCity::dao()->getTable().TaggableSmartHandler::ID_POSTFIX.'1',
+ TestCity::dao()->getTable().TaggableSmartHandler::ID_POSTFIX.'2',
+ );
+ $this->assertEquals(
+ $expectTags,
+ $this->spawnHandler()->getQueryTags($criteria->toSelectQuery(), 'TestLazy')
+ );
+ }
+
+ public function testTableLazyListByCityName()
+ {
+ $criteria = Criteria::create(TestLazy::dao())
+ ->add(Expression::eq('city.name', 'Moscow'));
+
+ $expectTags = array(
+ TestLazy::dao()->getTable(),
+ TestCity::dao()->getTable(),
+ );
+ $this->assertEquals(
+ $expectTags,
+ $this->spawnHandler()->getQueryTags($criteria->toSelectQuery(), 'TestLazy')
+ );
+ }
+
+ public function testTableUserListFetchedWithJoins()
+ {
+ $criteria = Criteria::create(TestUser::dao());
+
+ $expectTags = array(
+ TestUser::dao()->getTable(),
+ TestCity::dao()->getTable(),
+ TestCity::dao()->getTable(),
+ TestCity::dao()->getTable(),
+ );
+ $this->assertEquals(
+ $expectTags,
+ $this->spawnHandler()->getQueryTags($criteria->toSelectQuery(), 'TestUser')
+ );
+ }
+
+ public function testTableLazyThroughValueObject()
+ {
+ $criteria = Criteria::create(TestUserWithContact::dao())
+ ->add(Expression::eq('contacts.city.id', DBValue::create('3')));
+
+ $expectTags = array(
+ TestCity::dao()->getTable().TaggableSmartHandler::ID_POSTFIX.'3',
+ );
+
+ $this->assertEquals(
+ $expectTags,
+ $this->spawnHandler()->getQueryTags($criteria->toSelectQuery(), 'TestUserWithContact')
+ );
+
+ }
+
+ public function testTableLazyListInCities()
+ {
+ $this->markTestIncomplete('Not implemented feature, but you can ;)');
+
+ $criteria = Criteria::create(TestLazy::dao())
+ ->add(Expression::in('city', array('1', '2', '42')));
+
+ $expectTags = array(
+ TestCity::dao()->getTable().TaggableSmartHandler::ID_POSTFIX.'1',
+ TestCity::dao()->getTable().TaggableSmartHandler::ID_POSTFIX.'2',
+ TestCity::dao()->getTable().TaggableSmartHandler::ID_POSTFIX.'42',
+ );
+ $this->assertEquals(
+ $expectTags,
+ $this->spawnHandler()->getQueryTags($criteria->toSelectQuery(), 'TestLazy')
+ );
+ }
+
+ /**
+ * @return TaggableSmartHandler
+ */
+ private function spawnHandler()
+ {
+ return new TaggableSmartHandler();
+ }
+ }
+?>
\ No newline at end of file
diff --git a/test/meta/config.meta.xml b/test/meta/config.meta.xml
index ba98dc7840..adae22d362 100644
--- a/test/meta/config.meta.xml
+++ b/test/meta/config.meta.xml
@@ -117,7 +117,7 @@
-
+
diff --git a/test/misc/DAOTest.class.php b/test/misc/DAOTest.class.php
index d19949a603..f078f3b662 100644
--- a/test/misc/DAOTest.class.php
+++ b/test/misc/DAOTest.class.php
@@ -871,10 +871,9 @@ public function testLazy()
$this->create();
$parent = TestParentObject::create();
- $child = TestChildObject::create()->setParent($parent);
-
$parent->dao()->add($parent);
+ $child = TestChildObject::create()->setParent($parent);
$child->dao()->add($child);
$this->assertEquals(
diff --git a/test/runme.sh b/test/runme.sh
index b3224506df..036b9cf352 100755
--- a/test/runme.sh
+++ b/test/runme.sh
@@ -3,7 +3,7 @@ cd `dirname $0`
if [ "$*" = '' ]
then
- ARGS='--repeat 9 AllTests'
+ ARGS='--repeat 10 AllTests'
else
ARGS="$*"
fi