1
0
mirror of synced 2025-01-18 06:21:40 +03:00

Fix cache misses using one-to-one inverse side

This commit is contained in:
fabios 2013-12-17 18:00:25 -05:00
parent 8554b04053
commit cf4c805427
11 changed files with 473 additions and 11 deletions

View File

@ -21,7 +21,6 @@
namespace Doctrine\ORM\Cache; namespace Doctrine\ORM\Cache;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Cache\EntityCacheKey; use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -133,7 +132,7 @@ class DefaultEntityHydrator implements EntityHydrator
return null; return null;
} }
$data[$name] = $assoc['fetch'] === ClassMetadata::FETCH_EAGER $data[$name] = $assoc['fetch'] === ClassMetadata::FETCH_EAGER || ($assoc['type'] === ClassMetadata::ONE_TO_ONE && ! $assoc['isOwningSide'])
? $this->uow->createEntity($assocEntry->class, $assocEntry->data, $hints) ? $this->uow->createEntity($assocEntry->class, $assocEntry->data, $hints)
: $this->em->getReference($assocEntry->class, $assocId); : $this->em->getReference($assocEntry->class, $assocId);
} }

View File

@ -100,6 +100,13 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/ */
protected $regionName; protected $regionName;
/**
* Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
*
* @var array
*/
protected $joinedAssociations;
/** /**
* @param \Doctrine\ORM\Persisters\EntityPersister $persister The entity persister to cache. * @param \Doctrine\ORM\Persisters\EntityPersister $persister The entity persister to cache.
* @param \Doctrine\ORM\Cache\Region $region The entity cache region. * @param \Doctrine\ORM\Cache\Region $region The entity cache region.
@ -227,6 +234,42 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $cached; return $cached;
} }
/**
* @param object $entity
*/
private function storeJoinedAssociations($entity)
{
if ($this->joinedAssociations === null) {
$associations = array();
foreach ($this->class->associationMappings as $name => $assoc) {
if (isset($assoc['cache']) &&
($assoc['type'] & ClassMetadata::TO_ONE) &&
($assoc['fetch'] === ClassMetadata::FETCH_EAGER || ! $assoc['isOwningSide'])) {
$associations[] = $name;
}
}
$this->joinedAssociations = $associations;
}
foreach ($this->joinedAssociations as $name) {
$assoc = $this->class->associationMappings[$name];
$assocEntity = $this->class->getFieldValue($entity, $name);
if ($assocEntity === null) {
continue;
}
$assocId = $this->uow->getEntityIdentifier($assocEntity);
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocPersister->storeEntityCache($assocEntity, $assocKey);
}
}
/** /**
* Generates a string of currently query * Generates a string of currently query
* *
@ -417,6 +460,10 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity); $cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity);
$cached = $this->region->put($cacheKey, $cacheEntry); $cached = $this->region->put($cacheKey, $cacheEntry);
if ($cached && ($this->joinedAssociations === null || count($this->joinedAssociations) > 0)) {
$this->storeJoinedAssociations($entity);
}
if ($this->cacheLogger) { if ($this->cacheLogger) {
if ($cached) { if ($cached) {
$this->cacheLogger->entityCachePut($this->regionName, $cacheKey); $this->cacheLogger->entityCachePut($this->regionName, $cacheKey);

View File

@ -2602,6 +2602,18 @@ class UnitOfWork implements PropertyChangedListener
switch (true) { switch (true) {
case ($assoc['type'] & ClassMetadata::TO_ONE): case ($assoc['type'] & ClassMetadata::TO_ONE):
if ( ! $assoc['isOwningSide']) { if ( ! $assoc['isOwningSide']) {
// use the given entity association
if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
$this->originalEntityData[$oid][$field] = $data[$field];
$class->reflFields[$field]->setValue($entity, $data[$field]);
$targetClass->reflFields[$assoc['mappedBy']]->setValue($data[$field], $entity);
continue 2;
}
// Inverse side of x-to-one can never be lazy // Inverse side of x-to-one can never be lazy
$class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity)); $class->reflFields[$field]->setValue($entity, $this->getEntityPersister($assoc['targetEntity'])->loadOneToOneEntity($assoc, $entity));

View File

@ -7,5 +7,5 @@ namespace Doctrine\Tests\Models\Cache;
*/ */
class Beach extends Attraction class Beach extends Attraction
{ {
const CLASSNAME = __CLASS__; const CLASSNAME = __CLASS__;
} }

View File

@ -33,6 +33,12 @@ class Traveler
*/ */
public $travels; public $travels;
/**
* @Cache
* @OneToOne(targetEntity="TravelerProfile")
*/
protected $profile;
/** /**
* @param string $name * @param string $name
*/ */
@ -62,6 +68,22 @@ class Traveler
$this->name = $name; $this->name = $name;
} }
/**
* @return \Doctrine\Tests\Models\Cache\TravelerProfile
*/
public function getProfile()
{
return $this->profile;
}
/**
* @param \Doctrine\Tests\Models\Cache\TravelerProfile $profile
*/
public function setProfile(TravelerProfile $profile)
{
$this->profile = $profile;
}
public function getTravels() public function getTravels()
{ {
return $this->travels; return $this->travels;
@ -88,4 +110,4 @@ class Traveler
{ {
$this->travels->removeElement($item); $this->travels->removeElement($item);
} }
} }

View File

@ -0,0 +1,66 @@
<?php
namespace Doctrine\Tests\Models\Cache;
/**
* @Entity
* @Table("cache_traveler_profile")
* @Cache("NONSTRICT_READ_WRITE")
*/
class TravelerProfile
{
const CLASSNAME = __CLASS__;
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
/**
* @Column(unique=true)
*/
private $name;
/**
* @OneToOne(targetEntity="TravelerProfileInfo", mappedBy="profile")
* @Cache()
*/
private $info;
public function __construct($name)
{
$this->name = $name;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getName()
{
return $this->name;
}
public function setName($nae)
{
$this->name = $nae;
}
public function getInfo()
{
return $this->info;
}
public function setInfo(TravelerProfileInfo $info)
{
$this->info = $info;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Doctrine\Tests\Models\Cache;
/**
* @Entity
* @Table("cache_traveler_profile_info")
* @Cache("NONSTRICT_READ_WRITE")
*/
class TravelerProfileInfo
{
const CLASSNAME = __CLASS__;
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
/**
* @Column(unique=true)
*/
private $description;
/**
* @Cache()
* @JoinColumn(name="profile_id", referencedColumnName="id")
* @OneToOne(targetEntity="TravelerProfile", inversedBy="info")
*/
private $profile;
public function __construct(TravelerProfile $profile, $description)
{
$this->profile = $profile;
$this->description = $description;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
}
public function getProfile()
{
return $this->profile;
}
public function setProfile(TravelerProfile $profile)
{
$this->profile = $profile;
}
}

View File

@ -26,6 +26,9 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
} catch(\Exception $e) {} } catch(\Exception $e) {}
} }
/**
* @group non-cacheable
*/
public function testEagerLoadOneToOneOwningSide() public function testEagerLoadOneToOneOwningSide()
{ {
$train = new Train(new TrainOwner("Alexander")); $train = new Train(new TrainOwner("Alexander"));
@ -48,6 +51,9 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries)); $this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries));
} }
/**
* @group non-cacheable
*/
public function testEagerLoadOneToOneNullOwningSide() public function testEagerLoadOneToOneNullOwningSide()
{ {
$train = new Train(new TrainOwner("Alexander")); $train = new Train(new TrainOwner("Alexander"));
@ -65,6 +71,9 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries)); $this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries));
} }
/**
* @group non-cacheable
*/
public function testEagerLoadOneToOneInverseSide() public function testEagerLoadOneToOneInverseSide()
{ {
$owner = new TrainOwner("Alexander"); $owner = new TrainOwner("Alexander");
@ -83,6 +92,9 @@ class OneToOneEagerLoadingTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries)); $this->assertEquals($sqlCount + 1, count($this->_sqlLoggerStack->queries));
} }
/**
* @group non-cacheable
*/
public function testEagerLoadOneToOneNullInverseSide() public function testEagerLoadOneToOneNullInverseSide()
{ {
$driver = new TrainDriver("Dagny Taggert"); $driver = new TrainDriver("Dagny Taggert");

View File

@ -8,6 +8,8 @@ use Doctrine\Tests\Models\Cache\Country;
use Doctrine\Tests\Models\Cache\State; use Doctrine\Tests\Models\Cache\State;
use Doctrine\Tests\Models\Cache\City; use Doctrine\Tests\Models\Cache\City;
use Doctrine\Tests\Models\Cache\TravelerProfileInfo;
use Doctrine\Tests\Models\Cache\TravelerProfile;
use Doctrine\Tests\Models\Cache\Traveler; use Doctrine\Tests\Models\Cache\Traveler;
use Doctrine\Tests\Models\Cache\Travel; use Doctrine\Tests\Models\Cache\Travel;
@ -23,13 +25,14 @@ use Doctrine\Tests\Models\Cache\AttractionLocationInfo;
*/ */
abstract class SecondLevelCacheAbstractTest extends OrmFunctionalTestCase abstract class SecondLevelCacheAbstractTest extends OrmFunctionalTestCase
{ {
protected $countries = array(); protected $countries = array();
protected $states = array(); protected $states = array();
protected $cities = array(); protected $cities = array();
protected $travels = array(); protected $travels = array();
protected $travelers = array(); protected $travelers = array();
protected $attractions = array(); protected $attractions = array();
protected $attractionsInfo = array(); protected $attractionsInfo = array();
protected $travelersWithProfile = array();
/** /**
* @var \Doctrine\ORM\Cache * @var \Doctrine\ORM\Cache
@ -118,6 +121,45 @@ abstract class SecondLevelCacheAbstractTest extends OrmFunctionalTestCase
$this->_em->flush(); $this->_em->flush();
} }
protected function loadFixturesTravelersWithProfile()
{
$t1 = new Traveler("Test traveler 1");
$t2 = new Traveler("Test traveler 2");
$p1 = new TravelerProfile("First Traveler Profile");
$p2 = new TravelerProfile("Second Traveler Profile");
$t1->setProfile($p1);
$t2->setProfile($p2);
$this->_em->persist($p1);
$this->_em->persist($p2);
$this->_em->persist($t1);
$this->_em->persist($t2);
$this->travelersWithProfile[] = $t1;
$this->travelersWithProfile[] = $t2;
$this->_em->flush();
}
protected function loadFixturesTravelersProfileInfo()
{
$p1 = $this->travelersWithProfile[0]->getProfile();
$p2 = $this->travelersWithProfile[1]->getProfile();
$i1 = new TravelerProfileInfo($p1, "First Profile Info ...");
$i2 = new TravelerProfileInfo($p2, "Second Profile Info ...");
$p1->setInfo($i1);
$p2->setInfo($i2);
$this->_em->persist($i1);
$this->_em->persist($i2);
$this->_em->persist($p1);
$this->_em->persist($p2);
$this->_em->flush();
}
protected function loadFixturesTravels() protected function loadFixturesTravels()
{ {

View File

@ -0,0 +1,190 @@
<?php
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\Cache\Traveler;
use Doctrine\Tests\Models\Cache\TravelerProfile;
use Doctrine\Tests\Models\Cache\TravelerProfileInfo;
/**
* @group DDC-2183
*/
class SecondLevelCacheOneToOneTest extends SecondLevelCacheAbstractTest
{
public function testPutOneToOneOnUnidirectionalPersist()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->loadFixturesTravelersWithProfile();
$this->_em->clear();
$entity1 = $this->travelersWithProfile[0];
$entity2 = $this->travelersWithProfile[1];
$this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $entity1->getId()));
$this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $entity2->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity1->getProfile()->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity2->getProfile()->getId()));
}
public function testPutOneToOneOnBidirectionalPersist()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->loadFixturesTravelersWithProfile();
$this->loadFixturesTravelersProfileInfo();
$this->_em->clear();
$entity1 = $this->travelersWithProfile[0];
$entity2 = $this->travelersWithProfile[1];
$this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $entity1->getId()));
$this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $entity2->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity1->getProfile()->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity2->getProfile()->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfileInfo::CLASSNAME, $entity1->getProfile()->getInfo()->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfileInfo::CLASSNAME, $entity2->getProfile()->getInfo()->getId()));
}
public function testPutAndLoadOneToOneUnidirectionalRelation()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->loadFixturesTravelersWithProfile();
$this->loadFixturesTravelersProfileInfo();
$this->_em->clear();
$this->cache->evictEntityRegion(Traveler::CLASSNAME);
$this->cache->evictEntityRegion(TravelerProfile::CLASSNAME);
$entity1 = $this->travelersWithProfile[0];
$entity2 = $this->travelersWithProfile[1];
$this->assertFalse($this->cache->containsEntity(Traveler::CLASSNAME, $entity1->getId()));
$this->assertFalse($this->cache->containsEntity(Traveler::CLASSNAME, $entity2->getId()));
$this->assertFalse($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity1->getProfile()->getId()));
$this->assertFalse($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity2->getProfile()->getId()));
$t1 = $this->_em->find(Traveler::CLASSNAME, $entity1->getId());
$t2 = $this->_em->find(Traveler::CLASSNAME, $entity2->getId());
$this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $entity1->getId()));
$this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $entity2->getId()));
// The inverse side its not cached
$this->assertFalse($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity1->getProfile()->getId()));
$this->assertFalse($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity2->getProfile()->getId()));
$this->assertInstanceOf(Traveler::CLASSNAME, $t1);
$this->assertInstanceOf(Traveler::CLASSNAME, $t2);
$this->assertInstanceOf(TravelerProfile::CLASSNAME, $t1->getProfile());
$this->assertInstanceOf(TravelerProfile::CLASSNAME, $t2->getProfile());
$this->assertEquals($entity1->getId(), $t1->getId());
$this->assertEquals($entity1->getName(), $t1->getName());
$this->assertEquals($entity1->getProfile()->getId(), $t1->getProfile()->getId());
$this->assertEquals($entity1->getProfile()->getName(), $t1->getProfile()->getName());
$this->assertEquals($entity2->getId(), $t2->getId());
$this->assertEquals($entity2->getName(), $t2->getName());
$this->assertEquals($entity2->getProfile()->getId(), $t2->getProfile()->getId());
$this->assertEquals($entity2->getProfile()->getName(), $t2->getProfile()->getName());
// its all cached now
$this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $entity1->getId()));
$this->assertTrue($this->cache->containsEntity(Traveler::CLASSNAME, $entity2->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity1->getProfile()->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity1->getProfile()->getId()));
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
// load from cache
$t3 = $this->_em->find(Traveler::CLASSNAME, $entity1->getId());
$t4 = $this->_em->find(Traveler::CLASSNAME, $entity2->getId());
$this->assertInstanceOf(Traveler::CLASSNAME, $t3);
$this->assertInstanceOf(Traveler::CLASSNAME, $t4);
$this->assertInstanceOf(TravelerProfile::CLASSNAME, $t3->getProfile());
$this->assertInstanceOf(TravelerProfile::CLASSNAME, $t4->getProfile());
$this->assertEquals($entity1->getProfile()->getId(), $t3->getProfile()->getId());
$this->assertEquals($entity2->getProfile()->getId(), $t4->getProfile()->getId());
$this->assertEquals($entity1->getProfile()->getName(), $t3->getProfile()->getName());
$this->assertEquals($entity2->getProfile()->getName(), $t4->getProfile()->getName());
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
}
public function testPutAndLoadOneToOneBidirectionalRelation()
{
$this->loadFixturesCountries();
$this->loadFixturesStates();
$this->loadFixturesCities();
$this->loadFixturesTravelersWithProfile();
$this->loadFixturesTravelersProfileInfo();
$this->_em->clear();
$this->cache->evictEntityRegion(Traveler::CLASSNAME);
$this->cache->evictEntityRegion(TravelerProfile::CLASSNAME);
$this->cache->evictEntityRegion(TravelerProfileInfo::CLASSNAME);
$entity1 = $this->travelersWithProfile[0]->getProfile();
$entity2 = $this->travelersWithProfile[1]->getProfile();
$this->assertFalse($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity1->getId()));
$this->assertFalse($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity2->getId()));
$this->assertFalse($this->cache->containsEntity(TravelerProfileInfo::CLASSNAME, $entity1->getInfo()->getId()));
$this->assertFalse($this->cache->containsEntity(TravelerProfileInfo::CLASSNAME, $entity2->getInfo()->getId()));
$p1 = $this->_em->find(TravelerProfile::CLASSNAME, $entity1->getId());
$p2 = $this->_em->find(TravelerProfile::CLASSNAME, $entity2->getId());
$this->assertEquals($entity1->getId(), $p1->getId());
$this->assertEquals($entity1->getName(), $p1->getName());
$this->assertEquals($entity1->getInfo()->getId(), $p1->getInfo()->getId());
$this->assertEquals($entity1->getInfo()->getDescription(), $p1->getInfo()->getDescription());
$this->assertEquals($entity2->getId(), $p2->getId());
$this->assertEquals($entity2->getName(), $p2->getName());
$this->assertEquals($entity2->getInfo()->getId(), $p2->getInfo()->getId());
$this->assertEquals($entity2->getInfo()->getDescription(), $p2->getInfo()->getDescription());
$this->assertTrue($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity1->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfile::CLASSNAME, $entity2->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfileInfo::CLASSNAME, $entity1->getInfo()->getId()));
$this->assertTrue($this->cache->containsEntity(TravelerProfileInfo::CLASSNAME, $entity2->getInfo()->getId()));
$this->_em->clear();
$queryCount = $this->getCurrentQueryCount();
$p3 = $this->_em->find(TravelerProfile::CLASSNAME, $entity1->getId());
$p4 = $this->_em->find(TravelerProfile::CLASSNAME, $entity2->getId());
$this->assertInstanceOf(TravelerProfile::CLASSNAME, $p3);
$this->assertInstanceOf(TravelerProfile::CLASSNAME, $p4);
$this->assertInstanceOf(TravelerProfileInfo::CLASSNAME, $p3->getInfo());
$this->assertInstanceOf(TravelerProfileInfo::CLASSNAME, $p4->getInfo());
$this->assertEquals($entity1->getId(), $p3->getId());
$this->assertEquals($entity1->getName(), $p3->getName());
$this->assertEquals($entity1->getInfo()->getId(), $p3->getInfo()->getId());
$this->assertEquals($entity1->getInfo()->getDescription(), $p3->getInfo()->getDescription());
$this->assertEquals($entity2->getId(), $p4->getId());
$this->assertEquals($entity2->getName(), $p4->getName());
$this->assertEquals($entity2->getInfo()->getId(), $p4->getInfo()->getId());
$this->assertEquals($entity2->getInfo()->getDescription(), $p4->getInfo()->getDescription());
$this->assertEquals($queryCount, $this->getCurrentQueryCount());
}
}

View File

@ -171,6 +171,8 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
'Doctrine\Tests\Models\Cache\State', 'Doctrine\Tests\Models\Cache\State',
'Doctrine\Tests\Models\Cache\City', 'Doctrine\Tests\Models\Cache\City',
'Doctrine\Tests\Models\Cache\Traveler', 'Doctrine\Tests\Models\Cache\Traveler',
'Doctrine\Tests\Models\Cache\TravelerProfileInfo',
'Doctrine\Tests\Models\Cache\TravelerProfile',
'Doctrine\Tests\Models\Cache\Travel', 'Doctrine\Tests\Models\Cache\Travel',
'Doctrine\Tests\Models\Cache\Attraction', 'Doctrine\Tests\Models\Cache\Attraction',
'Doctrine\Tests\Models\Cache\Restaurant', 'Doctrine\Tests\Models\Cache\Restaurant',
@ -325,6 +327,8 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
$conn->executeUpdate('DELETE FROM cache_attraction'); $conn->executeUpdate('DELETE FROM cache_attraction');
$conn->executeUpdate('DELETE FROM cache_travel'); $conn->executeUpdate('DELETE FROM cache_travel');
$conn->executeUpdate('DELETE FROM cache_traveler'); $conn->executeUpdate('DELETE FROM cache_traveler');
$conn->executeUpdate('DELETE FROM cache_traveler_profile_info');
$conn->executeUpdate('DELETE FROM cache_traveler_profile');
$conn->executeUpdate('DELETE FROM cache_city'); $conn->executeUpdate('DELETE FROM cache_city');
$conn->executeUpdate('DELETE FROM cache_state'); $conn->executeUpdate('DELETE FROM cache_state');
$conn->executeUpdate('DELETE FROM cache_country'); $conn->executeUpdate('DELETE FROM cache_country');