00001 <?php
00003
00004
00005
00006
00007
00008
00010
00030 class FLEA_Db_TableLink
00031 {
00040 var $name;
00041
00047 var $tableClass;
00048
00054 var $foreignKey;
00055
00061 var $mappingName;
00062
00068 var $oneToOne;
00069
00075 var $type;
00076
00082 var $sort;
00083
00089 var $conditions;
00090
00096 var $fields = '*';
00097
00103 var $limit = null;
00104
00112 var $enabled = true;
00113
00119 var $countOnly = false;
00120
00126 var $counterCache = null;
00127
00133 var $linkRead = true;
00134
00140 var $linkCreate = true;
00141
00147 var $linkUpdate = true;
00148
00154 var $linkRemove = true;
00155
00161 var $linkRemoveFillValue = 0;
00162
00168 var $saveAssocMethod = 'save';
00169
00175 var $mainTDG;
00176
00182 var $assocTDG = null;
00183
00189 var $_req = array(
00190 'name',
00191 'tableClass',
00192 'mappingName',
00193 );
00194
00200 var $_optional = array(
00201 'foreignKey',
00202 'sort',
00203 'conditions',
00204 'fields',
00205 'limit',
00206 'enabled',
00207 'countOnly',
00208 'counterCache',
00209 'linkRead',
00210 'linkCreate',
00211 'linkUpdate',
00212 'linkRemove',
00213 'linkRemoveFillValue',
00214 'saveAssocMethod',
00215 );
00216
00222 var $qforeignKey;
00223
00229 var $dbo;
00230
00236 var $assocTDGObjectId;
00237
00243 var $init = false;
00244
00257 function FLEA_Db_TableLink($define, $type, & $mainTDG)
00258 {
00259 static $defaultDsnId = null;
00260
00261
00262 foreach ($this->_req as $key) {
00263 if (!isset($define[$key]) || $define[$key] == '') {
00264 FLEA::loadClass('FLEA_Db_Exception_MissingLinkOption');
00265 return __THROW(new FLEA_Db_Exception_MissingLinkOption($key));
00266 } else {
00267 $this->{$key} = $define[$key];
00268 }
00269 }
00270
00271 foreach ($this->_optional as $key) {
00272 if (isset($define[$key])) {
00273 $this->{$key} = $define[$key];
00274 }
00275 }
00276 $this->type = $type;
00277 $this->mainTDG =& $mainTDG;
00278 $this->dbo =& $this->mainTDG->getDBO();
00279 $dsnid = $this->dbo->dsn['id'];
00280
00281 if (is_null($defaultDsnId)) {
00282 $defaultDSN = FLEA::getAppInf('dbDSN');
00283 if ($defaultDSN) {
00284 $defaultDSN = FLEA::parseDSN($defaultDSN);
00285 $defaultDsnId = $defaultDSN['id'];
00286 } else {
00287 $defaultDsnId = -1;
00288 }
00289 }
00290 if ($dsnid == $defaultDsnId) {
00291 $this->assocTDGObjectId = null;
00292 } else {
00293 $this->assocTDGObjectId = "{$this->tableClass}-{$dsnid}";
00294 }
00295 }
00296
00306 function & createLink($define, $type, & $mainTDG)
00307 {
00308 static $typeMap = array(
00309 HAS_ONE => 'FLEA_Db_HasOneLink',
00310 BELONGS_TO => 'FLEA_Db_BelongsToLink',
00311 HAS_MANY => 'FLEA_Db_HasManyLink',
00312 MANY_TO_MANY => 'FLEA_Db_ManyToManyLink',
00313 );
00314 static $instances = array();
00315
00316
00317 if (!isset($typeMap[$type])) {
00318 FLEA::loadClass('FLEA_Db_Exception_InvalidLinkType');
00319 return __THROW(new FLEA_Db_Exception_InvalidLinkType($type));
00320 }
00321
00322
00323 if (!isset($define['tableClass'])) {
00324 FLEA::loadClass('FLEA_Db_Exception_MissingLinkOption');
00325 return __THROW(new FLEA_Db_Exception_MissingLinkOption('tableClass'));
00326 }
00327
00328 if (!isset($define['mappingName'])) {
00329 $define['mappingName'] = $define['tableClass'];
00330 }
00331
00332 if (!isset($define['name'])) {
00333 $define['name'] = $define['mappingName'];
00334 }
00335
00336
00337
00338 if ($type == MANY_TO_MANY) {
00339 if (!isset($define['joinTable']) && !isset($define['joinTableClass'])) {
00340 FLEA::loadClass('FLEA_Db_Exception_MissingLinkOption');
00341 return __THROW(new FLEA_Db_Exception_MissingLinkOption('joinTable'));
00342 }
00343 }
00344
00345 $instances[$define['name']] =& new $typeMap[$type]($define, $type, $mainTDG);
00346 return $instances[$define['name']];
00347 }
00348
00357 function getMiddleTableName($table1, $table2)
00358 {
00359 if (strcmp($table1, $table2) < 0) {
00360 return $this->dbo->dsn['prefix'] . "{$table1}_{$table2}";
00361 } else {
00362 return $this->dbo->dsn['prefix'] . "{$table2}_{$table1}";
00363 }
00364 }
00365
00374 function saveAssocData(& $row, $pkv)
00375 {
00376 FLEA::loadClass('FLEA_Exception_NotImplemented');
00377 return __THROW(new FLEA_Exception_NotImplemented('saveAssocData()', 'FLEA_Db_TableLink'));
00378 }
00379
00383 function init()
00384 {
00385 if ($this->init) { return; }
00386 if (FLEA::isRegistered($this->assocTDGObjectId)) {
00387 $this->assocTDG =& FLEA::registry($this->assocTDGObjectId);
00388 } else {
00389 if ($this->assocTDGObjectId) {
00390 FLEA::loadClass($this->tableClass);
00391 $this->assocTDG =& new $this->tableClass(array('dbo' => & $this->dbo));
00392 FLEA::register($this->assocTDG, $this->assocTDGObjectId);
00393 } else {
00394 $this->assocTDG =& FLEA::getSingleton($this->tableClass);
00395 }
00396 }
00397 $this->init = true;
00398 }
00399
00409 function calcCount(& $assocRowset, $mappingName, $in)
00410 {
00411 FLEA::loadClass('FLEA_Exception_NotImplemented');
00412 return __THROW(new FLEA_Exception_NotImplemented('calcCount()', 'FLEA_Db_TableLink'));
00413 }
00414
00423 function _getFindSQLBase($sql, $in)
00424 {
00425 if ($in) {
00426 $sql .= " WHERE {$this->qforeignKey} {$in}";
00427 }
00428 if ($this->conditions) {
00429 if (is_array($this->conditions)) {
00430 $conditions = FLEA_Db_SqlHelper::parseConditions($this->conditions, $this->assocTDG);
00431 if (is_array($conditions)) {
00432 $conditions = $conditions[0];
00433 }
00434 } else {
00435 $conditions =& $this->conditions;
00436 }
00437 if ($conditions) {
00438 $sql .= " AND {$conditions}";
00439 }
00440 }
00441 if ($this->sort && $this->countOnly == false) {
00442 $sql .= " ORDER BY {$this->sort}";
00443 }
00444
00445 return $sql;
00446 }
00447
00455 function _saveAssocDataBase(& $row)
00456 {
00457 switch (strtolower($this->saveAssocMethod)) {
00458 case 'create':
00459 return $this->assocTDG->create($row);
00460 case 'update':
00461 return $this->assocTDG->update($row);
00462 case 'replace':
00463 return $this->assocTDG->replace($row);
00464 default:
00465 return $this->assocTDG->save($row);
00466 }
00467 }
00468 }
00469
00477 class FLEA_Db_HasOneLink extends FLEA_Db_TableLink
00478 {
00479 var $oneToOne = true;
00480
00490 function FLEA_Db_HasOneLink($define, $type, & $mainTDG)
00491 {
00492 parent::FLEA_Db_TableLink($define, $type, $mainTDG);
00493 }
00494
00502 function getFindSQL($in)
00503 {
00504 if (!$this->init) { $this->init(); }
00505 $fields = $this->qforeignKey . ' AS ' . $this->mainTDG->pka . ', ' . $this->dbo->qfields($this->fields, $this->assocTDG->fullTableName, $this->assocTDG->schema);
00506 $sql = "SELECT {$fields} FROM {$this->assocTDG->qtableName} ";
00507 return parent::_getFindSQLBase($sql, $in);
00508 }
00509
00518 function saveAssocData(& $row, $pkv)
00519 {
00520 if (empty($row)) { return true; }
00521 if (!$this->init) { $this->init(); }
00522 $row[$this->foreignKey] = $pkv;
00523 return $this->_saveAssocDataBase($row);
00524 }
00525
00533 function deleteByForeignKey($qpkv)
00534 {
00535 if (!$this->init) { $this->init(); }
00536 $conditions = "{$this->qforeignKey} = {$qpkv}";
00537 if ($this->linkRemove) {
00538 return $this->assocTDG->removeByConditions($conditions);
00539 } else {
00540 return $this->assocTDG->updateField($conditions, $this->foreignKey, $this->linkRemoveFillValue);
00541 }
00542 }
00543
00547 function init()
00548 {
00549 parent::init();
00550 if (is_null($this->foreignKey)) {
00551 $this->foreignKey = $this->mainTDG->primaryKey;
00552 }
00553 $this->qforeignKey = $this->dbo->qfield($this->foreignKey, $this->assocTDG->fullTableName, $this->assocTDG->schema);
00554 }
00555
00565 function calcCount(& $assocRowset, $mappingName, $in)
00566 {
00567 if (!$this->init) { $this->init(); }
00568 $sql = "SELECT {$this->qforeignKey} AS pid, COUNT({$this->qforeignKey}) AS c FROM {$this->assocTDG->qtableName} ";
00569 $sql = parent::_getFindSQLBase($sql, $in);
00570 $sql .= " GROUP BY {$this->qforeignKey}";
00571
00572 $r = $this->dbo->execute($sql);
00573 while ($row = $this->dbo->fetchAssoc($r)) {
00574 $assocRowset[$row['pid']][$mappingName] = (int)$row['c'];
00575 }
00576 $this->dbo->freeRes($r);
00577 }
00578 }
00579
00587 class FLEA_Db_BelongsToLink extends FLEA_Db_TableLink
00588 {
00589 var $oneToOne = true;
00590
00600 function FLEA_Db_BelongsToLink($define, $type, & $mainTDG)
00601 {
00602 $this->linkUpdate = $this->linkCreate = $this->linkRemove = false;
00603 parent::FLEA_Db_TableLink($define, $type, $mainTDG);
00604 }
00605
00613 function getFindSQL($in)
00614 {
00615 if (!$this->init) { $this->init(); }
00616 $fields = $this->mainTDG->qpk . ' AS ' . $this->mainTDG->pka . ', ' . $this->dbo->qfields($this->fields, $this->assocTDG->fullTableName, $this->assocTDG->schema);
00617
00618 $sql = "SELECT {$fields} FROM {$this->assocTDG->qtableName} LEFT JOIN {$this->mainTDG->qtableName} ON {$this->mainTDG->qpk} {$in} WHERE {$this->qforeignKey} = {$this->assocTDG->qpk} ";
00619 $in = '';
00620 return parent::_getFindSQLBase($sql, $in);
00621 }
00622
00631 function saveAssocData(& $row, $pkv)
00632 {
00633 if (empty($row)) { return true; }
00634 if (!$this->init) { $this->init(); }
00635 return $this->_saveAssocDataBase($row);
00636 }
00637
00641 function init()
00642 {
00643 parent::init();
00644 if (is_null($this->foreignKey)) {
00645 $this->foreignKey = $this->assocTDG->primaryKey;
00646 }
00647 $this->qforeignKey = $this->dbo->qfield($this->foreignKey, $this->mainTDG->fullTableName, $this->mainTDG->schema);
00648 }
00649 }
00650
00658 class FLEA_Db_HasManyLink extends FLEA_Db_HasOneLink
00659 {
00660 var $oneToOne = false;
00661
00670 function saveAssocData(& $row, $pkv)
00671 {
00672 if (empty($row)) { return true; }
00673 if (!$this->init) { $this->init(); }
00674
00675 foreach ($row as $arow) {
00676 if (!is_array($arow)) { continue; }
00677 $arow[$this->foreignKey] = $pkv;
00678 if (!$this->_saveAssocDataBase($arow)) {
00679 return false;
00680 }
00681 }
00682 return true;
00683 }
00684 }
00685
00693 class FLEA_Db_ManyToManyLink extends FLEA_Db_TableLink
00694 {
00700 var $oneToOne = false;
00701
00707 var $joinTableIsEntity = false;
00708
00714 var $joinTDG = null;
00715
00721 var $joinTable = null;
00722
00728 var $qjoinTable = null;
00729
00735 var $assocForeignKey = null;
00736
00742 var $qassocForeignKey = null;
00743
00749 var $joinTableClass = null;
00750
00760 function FLEA_Db_ManyToManyLink($define, $type, & $mainTDG)
00761 {
00762 $this->_optional[] = 'joinTable';
00763 $this->_optional[] = 'joinTableClass';
00764 $this->_optional[] = 'assocForeignKey';
00765 parent::FLEA_Db_TableLink($define, $type, $mainTDG);
00766
00767 if ($this->joinTableClass != '') {
00768 $this->joinTableIsEntity = true;
00769 }
00770 }
00771
00779 function getFindSQL($in)
00780 {
00781 static $joinFields = array();
00782
00783 if (!$this->init) { $this->init(); }
00784
00785 $fields = $this->qforeignKey . ' AS ' . $this->mainTDG->pka . ', ' . $this->assocTDG->qfields($this->fields);
00786
00787 if ($this->joinTableIsEntity) {
00788 if (!isset($joinFields[$this->joinTDG->fullTableName])) {
00789 $f = '';
00790 foreach ($this->joinTDG->meta as $field) {
00791 $f .= ', ' . $this->joinTDG->qfield($field['name']) . ' AS _join_' . $field['name'];
00792 }
00793 $joinFields[$this->joinTDG->fullTableName] = $f;
00794 }
00795 $fields .= $joinFields[$this->joinTDG->fullTableName];
00796
00797 $sql = "SELECT {$fields} FROM {$this->joinTDG->qtableName} INNER JOIN {$this->assocTDG->qtableName} ON {$this->assocTDG->qpk} = {$this->qassocForeignKey} ";
00798 } else {
00799 $sql = "SELECT {$fields} FROM {$this->qjoinTable} INNER JOIN {$this->assocTDG->qtableName} ON {$this->assocTDG->qpk} = {$this->qassocForeignKey} ";
00800 }
00801
00802 return parent::_getFindSQLBase($sql, $in);
00803 }
00804
00813 function saveAssocData(& $row, $pkv)
00814 {
00815 if (!$this->init) { $this->init(); }
00816 $apkvs = array();
00817 $entityRowset = array();
00818
00819 foreach ($row as $arow) {
00820 if (!is_array($arow)) {
00821 $apkvs[] = $arow;
00822 continue;
00823 }
00824
00825 if (!isset($arow[$this->assocTDG->primaryKey])) {
00826
00827 $newrowid = $this->assocTDG->create($arow);
00828 if ($newrowid == false) {
00829 return false;
00830 }
00831 $apkv = $newrowid;
00832 } else {
00833 $apkv = $arow[$this->assocTDG->primaryKey];
00834 }
00835 $apkvs[] = $apkv;
00836
00837 if ($this->joinTableIsEntity && isset($arow['#JOIN#'])) {
00838 $entityRowset[$apkv] =& $arow['#JOIN#'];
00839 }
00840 }
00841
00842
00843 $qpkv = $this->dbo->qstr($pkv);
00844 $sql = "SELECT {$this->qassocForeignKey} FROM {$this->qjoinTable} WHERE {$this->qforeignKey} = {$qpkv} ";
00845 $existsMiddle = (array)$this->dbo->getCol($sql);
00846
00847
00848 $insertAssoc = array_diff($apkvs, $existsMiddle);
00849 $removeAssoc = array_diff($existsMiddle, $apkvs);
00850
00851 if ($this->joinTableIsEntity) {
00852 $insertEntityRowset = array();
00853 foreach ($insertAssoc as $assocId) {
00854 if (isset($entityRowset[$assocId])) {
00855 $row = $entityRowset[$assocId];
00856 } else {
00857 $row = array();
00858 }
00859 $row[$this->foreignKey] = $pkv;
00860 $row[$this->assocForeignKey] = $assocId;
00861 $insertEntityRowset[] = $row;
00862 }
00863 if ($this->joinTDG->createRowset($insertEntityRowset) === false) {
00864 return false;
00865 }
00866 } else {
00867 $sql = "INSERT INTO {$this->qjoinTable} ({$this->qforeignKey}, {$this->qassocForeignKey}) VALUES ({$qpkv}, ";
00868 foreach ($insertAssoc as $assocId) {
00869 if (!$this->dbo->execute($sql . $this->dbo->qstr($assocId) . ')')) {
00870 return false;
00871 }
00872 }
00873 }
00874
00875
00876 if ($this->joinTableIsEntity) {
00877 $conditions = array($this->foreignKey => $pkv);
00878 foreach ($removeAssoc as $assocId) {
00879 $conditions[$this->assocForeignKey] = $assocId;
00880 if ($this->joinTDG->removeByConditions($conditions) === false) {
00881 return false;
00882 }
00883 }
00884 } else {
00885 $sql = "DELETE FROM {$this->qjoinTable} WHERE {$this->qforeignKey} = {$qpkv} AND {$this->qassocForeignKey} = ";
00886 foreach ($removeAssoc as $assocId) {
00887 if (!$this->dbo->execute($sql . $this->dbo->qstr($assocId))) {
00888 return false;
00889 }
00890 }
00891 }
00892
00893 if ($this->counterCache) {
00894 $sql = "UPDATE {$this->mainTDG->qtableName} SET {$this->counterCache} = (SELECT COUNT(*) FROM {$this->qjoinTable} WHERE {$this->qforeignKey} = {$qpkv}) WHERE {$this->mainTDG->qpk} = {$qpkv}";
00895 $this->mainTDG->dbo->execute($sql);
00896 }
00897
00898 return true;
00899 }
00900
00908 function deleteMiddleTableDataByMainForeignKey($qpkv)
00909 {
00910 if (!$this->init) { $this->init(); }
00911 $sql = "DELETE FROM {$this->qjoinTable} WHERE {$this->qforeignKey} = {$qpkv} ";
00912 return $this->dbo->execute($sql);
00913 }
00914
00922 function deleteMiddleTableDataByAssocForeignKey($pkv)
00923 {
00924 if (!$this->init) { $this->init(); }
00925 $qpkv = $this->dbo->qstr($pkv);
00926 $sql = "DELETE FROM {$this->qjoinTable} WHERE {$this->qassocForeignKey} = {$qpkv} ";
00927 return $this->dbo->execute($sql);
00928 }
00929
00933 function init()
00934 {
00935 parent::init();
00936 if ($this->joinTableClass) {
00937 $this->joinTDG =& FLEA::getSingleton($this->joinTableClass);
00938 $this->joinTable = $this->joinTDG->tableName;
00939 $joinSchema = $this->joinTDG->schema;
00940 } else {
00941 $joinSchema = $this->mainTDG->schema;
00942 }
00943 if (is_null($this->joinTable)) {
00944 $this->joinTable = $this->getMiddleTableName($this->mainTDG->tableName, $this->assocTableName);
00945 }
00946 if (is_null($this->foreignKey)) {
00947 $this->foreignKey = $this->mainTDG->primaryKey;
00948 }
00949 $this->joinTable = $this->dbo->dsn['prefix'] . $this->joinTable;
00950 $this->qjoinTable = $this->dbo->qtable($this->joinTable, $joinSchema);
00951 $this->qforeignKey = $this->dbo->qfield($this->foreignKey, $this->joinTable, $joinSchema);
00952 if (is_null($this->assocForeignKey)) {
00953 $this->assocForeignKey = $this->assocTDG->primaryKey;
00954 }
00955 $this->qassocForeignKey = $this->dbo->qfield($this->assocForeignKey, $this->joinTable, $this->mainTDG->schema);
00956 }
00957 }
00958
00966 class FLEA_Db_SqlHelper
00967 {
00976 function parseConditions($conditions, & $table)
00977 {
00978
00979 if (is_null($conditions)) { return null; }
00980
00981
00982 if (is_numeric($conditions)) {
00983 return "{$table->qpk} = {$conditions}";
00984 }
00985
00986
00987 if (is_string($conditions)) {
00988 return $conditions;
00989 }
00990
00991
00992 if (!is_array($conditions)) {
00993 return null;
00994 }
00995
00996 $where = '';
00997 $linksWhere = array();
00998 $expr = '';
00999
01000 foreach ($conditions as $offset => $cond) {
01001 $expr = 'AND';
01005 if (is_string($offset)) {
01006 if (!is_array($cond)) {
01007
01008 $cond = array($offset, $cond);
01009 } else {
01010 if (strtolower($offset) == 'in()') {
01011 if (count($cond) == 1 && is_array(reset($cond)) && is_string(key($cond))) {
01012 $tmp = $table->qfield(key($cond)) . ' IN (' . implode(',', array_map(array(& $table->dbo, 'qstr'), reset($cond))). ')';
01013 } else {
01014 $tmp = $table->qpk . ' IN (' . implode(',', array_map(array(& $table->dbo, 'qstr'), $cond)). ')';
01015 }
01016 $cond = array('', $tmp, '', $expr, true);
01017 } else {
01018
01019 array_unshift($cond, $offset);
01020 }
01021 }
01022 } elseif (is_int($offset)) {
01023 if (!is_array($cond)) {
01024
01025 $cond = array('', $cond, '', $expr, true);
01026 }
01027 } else {
01028 continue;
01029 }
01030
01031 if (!isset($cond[0])) { continue; }
01032 if (!isset($cond[2])) { $cond[2] = '='; }
01033 if (!isset($cond[3])) { $cond[3] = $expr; }
01034 if (!isset($cond[4])) { $cond[4] = false; }
01035
01036 list($field, $value, $op, $expr, $isCommand) = $cond;
01037
01038 $str = '';
01039 do {
01040 if (strpos($field, '.') !== false) {
01041 list($scheme, $field) = explode('.', $field);
01042 $linkname = strtoupper($scheme);
01043 if (isset($table->links[$linkname])) {
01044 $linksWhere[$linkname][] = array($field, $value, $op, $expr, $isCommand);
01045 break;
01046 } else {
01047 $field = "{$scheme}.{$field}";
01048 }
01049 }
01050
01051 if (!$isCommand) {
01052 $field = $table->qfield($field);
01053 $value = $table->dbo->qstr($value);
01054 $str = "{$field} {$op} {$value} {$expr} ";
01055 } else {
01056 $str = "{$value} {$expr} ";
01057 }
01058 } while (false);
01059
01060 $where .= $str;
01061 }
01062
01063 $where = substr($where, 0, - (strlen($expr) + 2));
01064 if (empty($linksWhere)) {
01065 return $where;
01066 } else {
01067 return array($where, $linksWhere);
01068 }
01069 }
01070
01076 function dumpLog(& $log)
01077 {
01078 foreach ($log as $ix => $sql) {
01079 dump($sql, 'SQL ' . ($ix + 1));
01080 }
01081 }
01082 }