/** * Add a item to the queue for persitance on flush * * @param $trancation * @param $onFailure * * @return bool */ public function flush($transaction = null, $onFailure = self::FLUSH_ABORT, $throwExceptions = true) { if (is_null($transaction)) { $transaction = self::TRANSACTIONS_ALL; } if (is_null($onFailure)) { $onFailure = self::FLUSH_ABORT; } $response = new Response(); $exceptions = []; $haveAnyTransactionsFailed = false; // simulate $simulate = $this->debug & self::DEBUG_SIMULATE; // profiler $profiler = new Profiler("Record manager"); $profiler->log(); // debugging query show $debugQuery = null; if ($this->debug & self::DEBUG_SHOW_QUERY) { $debugQuery = function ($e, $event, $data) { echo $data . "\n"; }; $this->db->debug->on(Debug::INFO, $debugQuery); } // trap all throw exceptions so that we can properly unattach the $db->debug->on( Pg::QueryPassed $listener ) try { // iterate selected transactions foreach ($this->getQueue($transaction, true, true) as $transaction => $queue) { unset($this->queue[$transaction]); $taskResponse = null; $hasTransactionFailed = false; $beginTransaction = new Query("BEGIN TRANSACTION READ WRITE; /* %transaction:% */"); $beginTransaction->transaction = $transaction; $this->db->query($beginTransaction); $this->eventEmitter->emit(EventEmitter::TRANSACTION_START, $this, $transaction, $queue); // loop over our tasks foreach ($queue as $task) { // are we continuing to process? if ($hasTransactionFailed or $haveAnyTransactionsFailed && $onFailure === self::FLUSH_ABORT) { $taskResponse = Response::ABORTED; } else { try { $taskResponse = $task->execute($this->db, $simulate) ? Response::SUCCESS : Response::FAILED; } catch (\Exception $e) { // It'd be nice if we could just test for Query exceptions here but unfortunately PHPUnit dicks pretty hard with exception types. // @Mr Beale. I realise this violates the "only handle exceptions that you understand" rule but I can't find a way round this. Pete $taskResponse = $e; $exceptions[] = $e; } } // transaction success if ($taskResponse !== Response::SUCCESS) { $hasTransactionFailed = true; $haveAnyTransactionsFailed = true; } $response->add($transaction, $task, $taskResponse); } // end or rollback transaction $endTransaction = new Query("%commitOrRollback:% TRANSACTION; /* %transaction:% */"); $endTransaction->commitOrRollback = new Raw($hasTransactionFailed ? 'ROLLBACK' : 'COMMIT'); $endTransaction->transaction = $transaction; $this->db->query($endTransaction); // response rollback if ($hasTransactionFailed) { $response->rollback($transaction); } $profiler->log($transaction); } // if we've gathered any exceptions throw them here if ($throwExceptions and $exceptions) { throw $exceptions[0]; } $response->profilerAssign($profiler); } catch (\Exception $e) { $this->db->debug->removeListener(Pg::QUERY_PARSED, $debugQuery); throw $e; } return $response; }
/** * See, http://www.postgresql.org/docs/8.4/interactive/catalog-pg-constraint.html * @return bool. Populate the static variables $this->references $this->isReferencedBy */ private function preloadReferences() { // References defined in pg_catalog $sql = 'SELECT * FROM dev."vAttributeReferences" WHERE "isInherited" = false'; $relationships = $this->db->query(new Query($sql)); foreach ($relationships->fetch() as $relationship) { $fk = $this->instancesPersisted[$relationship['fk_key']]; $pk = $this->instancesPersisted[$relationship['pk_key']]; if (!$fk or !$pk) { d_pr($relationship); die; } // slower alternatives. They all do the same thing // $fk = $this->persistedGet()->findOneByKey( $relationship['fk_key'] ); // $pk = $this->persistedGet()->findOneByKey( $relationship['pk_key'] ); // $fk = $this->find( $relationship['fk_key'] ); // $pk = $this->find( $relationship['pk_key'] ); $fk->addReference($pk); } // look for normality defined relationships $profiler = new Profiler(__FUNCTION__); $profiler->log(); foreach (EntityRelation::r()->findAll() as $relation) { $tags = $relation->getNormalityTags(); if (isset($tags['references'])) { foreach ($tags['references'] as $reference) { $reference = array_map('trim', explode('=', $reference)); $fk = $this->findByRelation($relation)->findOneByName($reference[0]); $pk = $this->findByIdentifier($reference[1]); // go the database to get child records, using the Relation repo methods causes a infinite preload loop $query = new Query("SELECT oid::text || '.' || %attnum:int%::text FROM dev.relationDescendants( %oid:int%::oid ) as _ ( oid );", array('attnum' => $pk->get('attnum'), 'oid' => $pk->getRelation()->get('oid'))); foreach ($this->db->query($query)->fetch() as $pk_key) { $fk->addReference($this->find($pk_key)); } } } } // echo $profiler->log()->output(); }
/** * See, http://www.postgresql.org/docs/8.4/interactive/catalog-pg-constraint.html * @return bool. Populate the static variables $this->references $this->isReferencedBy */ private function loadReferences() { // References defined in pg_catalog $sql = new Raw('SELECT * FROM dev."vAttributeReferences" WHERE "isInherited" = false'); $sql = new Raw(<<<SQL -- Based on. http://code.google.com/p/pgutils/ but very heavily modified -- Potentially inheritance aware/compensating. Be careful. Use the unit tests. WITH "vRelationDescendants" as ( SELECT c.oid, c.relname, ( WITH RECURSIVE q( oid ) AS ( SELECT crd.oid FROM pg_class crd WHERE crd.oid = c.oid UNION ALL SELECT i.inhrelid FROM q INNER JOIN pg_inherits i ON q.oid = i.inhparent ) SELECT oid FROM q WHERE oid = c.oid ) as childoid FROM pg_class c INNER JOIN pg_namespace n ON n.oid = c.relnamespace ORDER BY n.nspname, c.relname ), "vAttributeReferences" as ( SELECT fkr.oid <> fkrd.oid AS "isInheritedSource", pkr.oid <> pkrd.oid AS "isInheritedTarget", ( fkr.oid <> fkrd.oid OR pkr.oid <> pkrd.oid ) as "isInherited", fkn.nspname AS fk_namespace, fkr.relname AS fk_relation, fkr.oid AS fk_oid, fka.attname AS fk_column, fka.attnum as "fk_attnum", -- initByData fkr.oid::text || '.' || fka.attnum::text AS fk_key, ( EXISTS ( SELECT pg_index.indexrelid, pg_index.indrelid, pg_index.indkey, pg_index.indclass, pg_index.indnatts, pg_index.indisunique, pg_index.indisprimary, pg_index.indisclustered, pg_index.indexprs, pg_index.indpred FROM pg_index WHERE pg_index.indrelid = fkr.oid AND pg_index.indkey[0] = fka.attnum ) ) AS fk_indexed, pkn.nspname AS pk_namespace, pkr.relname AS pk_relation, pkr.oid AS pk_oid, pka.attname AS pk_column, pka.attnum as "pk_attnum", -- initByData pkr.oid::text || '.' || pka.attnum::text AS pk_key, ( EXISTS ( SELECT pg_index.indexrelid, pg_index.indrelid, pg_index.indkey, pg_index.indclass, pg_index.indnatts, pg_index.indisunique, pg_index.indisprimary, pg_index.indisclustered, pg_index.indexprs, pg_index.indpred FROM pg_index WHERE pg_index.indrelid = pkr.oid AND pg_index.indkey[0] = pka.attnum ) ) AS pk_indexed, c.confupdtype::text || c.confdeltype::text AS ud, cn.nspname AS c_namespace, c.conname AS c_name FROM ( ( ( ( ( ( ( pg_constraint c JOIN pg_namespace cn ON cn.oid = c.connamespace ) INNER JOIN "vRelationDescendants" as fkrd ON c.conrelid = fkrd.oid INNER JOIN pg_class fkr ON fkr.oid = fkrd.childoid ) JOIN pg_namespace fkn ON fkn.oid = fkr.relnamespace ) JOIN pg_attribute fka ON fka.attrelid = c.conrelid AND fka.attnum = ANY (c.conkey) ) INNER JOIN "vRelationDescendants" as pkrd ON c.confrelid = pkrd.oid INNER JOIN pg_class pkr ON pkr.oid = pkrd.childoid ) JOIN pg_namespace pkn ON pkn.oid = pkr.relnamespace ) JOIN pg_attribute pka ON pka.attrelid = c.confrelid AND pka.attnum = ANY (c.confkey) ) WHERE c.contype = 'f'::"char" AND fkn.nspname = ANY( current_schemas(false) ) AND pkn.nspname = ANY( current_schemas(false) ) ) SELECT * FROM "vAttributeReferences" WHERE "isInherited" = false SQL ); $relationships = $this->db->query($sql); foreach ($relationships as $relationship) { $fk = $this->pgAttributes->findOneByKey($relationship['fk_key']); $pk = $this->pgAttributes->findOneByKey($relationship['pk_key']); if (!$fk or !$pk) { d_pr($relationship); die; } $fk->addReference($pk); } // look for normality defined relationships $profiler = new Profiler(__FUNCTION__); $profiler->log(); foreach ($this->pgClasses as $relation) { $tags = $relation->getTags(); if (isset($tags['references'])) { foreach ($tags['references'] as $reference) { $reference = array_map('trim', explode('=', $reference)); $reference[1] = explode('.', $reference[1]); $fk = $relation->getAttributeByName($reference[0]); // referencing $pkTable = $this->pgClasses->findOneByName($reference[1][0]); $pk = $pkTable->getAttributeByName($reference[1][1]); $fk->addReference($pk); } } } // echo $profiler->log()->output(); }
/** * {@inheritDoc} * @param string $qty * @param array $filterComponents * @param int Bitmask fo class constants * @return Bond\Container */ public function findByFilterComponents($qty, array $filterComponents, $source = null) { $source = $this->findByFilterComponentsSourceSetup($source); $output = $this->makeNewContainer(); $profiler = new Profiler(__FUNCTION__); $profiler->log("setup"); if ($source & self::UNPERSISTED) { $output->add($this->findByFilterComponentsMultiton($filterComponents, $source)); } $profiler->log("multiton"); if ($source & self::PERSISTED) { $cannotMatch = function ($entity) { return $entity instanceof Base ? $entity->isNew() : false; }; // If all of the filter components can't match - that is return true - there isn't any point in going to the db $checkDatabase = !\Bond\array_check(function ($filterComponent) use($cannotMatch) { return $filterComponent->getCannotMatch($cannotMatch); }, $filterComponents, false); if ($checkDatabase) { $output->add($this->findByFilterComponentsDatabase($filterComponents, $source)); } } $profiler->log("database"); return $this->findByFilterComponentsFormatReturnValue($qty, $output); }