1: <?php
2:
3: namespace RedBeanPHP\QueryWriter;
4:
5: use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
6: use RedBeanPHP\RedException as RedException;
7: use RedBeanPHP\QueryWriter as QueryWriter;
8: use RedBeanPHP\OODBBean as OODBBean;
9: use RedBeanPHP\RedException\SQL as SQLException;
10:
11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
27: abstract class AQueryWriter
28: {
29: 30: 31:
32: private static $sqlFilters = array();
33:
34: 35: 36:
37: private static $flagSQLFilterSafeMode = false;
38:
39: 40: 41:
42: private static $flagNarrowFieldMode = true;
43:
44: 45: 46:
47: public static $renames = array();
48:
49: 50: 51:
52: protected $adapter;
53:
54: 55: 56:
57: protected $defaultValue = 'NULL';
58:
59: 60: 61:
62: protected $quoteCharacter = '';
63:
64: 65: 66:
67: protected $flagUseCache = TRUE;
68:
69: 70: 71:
72: protected $cache = array();
73:
74: 75: 76:
77: protected $maxCacheSizePerType = 20;
78:
79: 80: 81:
82: public $typeno_sqltype = array();
83:
84: 85: 86: 87: 88: 89: 90:
91: public static function canBeTreatedAsInt( $value )
92: {
93: return (bool) ( strval( $value ) === strval( intval( $value ) ) );
94: }
95:
96: 97: 98:
99: public static function getAssocTableFormat( $types )
100: {
101: sort( $types );
102:
103: $assoc = implode( '_', $types );
104:
105: return ( isset( self::$renames[$assoc] ) ) ? self::$renames[$assoc] : $assoc;
106: }
107:
108: 109: 110:
111: public static function renameAssociation( $from, $to = NULL )
112: {
113: if ( is_array( $from ) ) {
114: foreach ( $from as $key => $value ) self::$renames[$key] = $value;
115:
116: return;
117: }
118:
119: self::$renames[$from] = $to;
120: }
121:
122: 123: 124: 125: 126: 127: 128: 129:
130: public static function camelsSnake( $camel )
131: {
132: return strtolower( preg_replace( '/(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])/', '_$1$2', $camel ) );
133: }
134:
135: 136: 137: 138: 139:
140: public static function clearRenames()
141: {
142: self::$renames = array();
143: }
144:
145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164:
165: public static function setNarrowFieldMode( $narrowField )
166: {
167: self::$flagNarrowFieldMode = (boolean) $narrowField;
168: }
169:
170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204:
205: public static function setSQLFilters( $sqlFilters, $safeMode = false )
206: {
207: self::$flagSQLFilterSafeMode = (boolean) $safeMode;
208: self::$sqlFilters = $sqlFilters;
209: }
210:
211: 212: 213: 214: 215: 216: 217: 218:
219: public static function getSQLFilters()
220: {
221: return self::$sqlFilters;
222: }
223:
224: 225: 226: 227: 228: 229: 230: 231: 232:
233: private function getCacheKey( $keyValues )
234: {
235: return json_encode( $keyValues );
236: }
237:
238: 239: 240: 241: 242: 243: 244: 245:
246: private function getCached( $cacheTag, $key )
247: {
248: $sql = $this->adapter->getSQL();
249:
250: if ($this->updateCache()) {
251: if ( isset( $this->cache[$cacheTag][$key] ) ) {
252: return $this->cache[$cacheTag][$key];
253: }
254: }
255:
256: return NULL;
257: }
258:
259: 260: 261: 262: 263: 264: 265: 266: 267:
268: private function updateCache()
269: {
270: $sql = $this->adapter->getSQL();
271: if ( strpos( $sql, '-- keep-cache' ) !== strlen( $sql ) - 13 ) {
272:
273:
274: $this->cache = array();
275: return FALSE;
276: }
277: return TRUE;
278: }
279:
280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297:
298: private function putResultInCache( $cacheTag, $key, $values )
299: {
300: if ( isset( $this->cache[$cacheTag] ) ) {
301: if ( count( $this->cache[$cacheTag] ) > $this->maxCacheSizePerType ) array_shift( $this->cache[$cacheTag] );
302: } else {
303: $this->cache[$cacheTag] = array();
304: }
305:
306: $this->cache[$cacheTag][$key] = $values;
307: }
308:
309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325:
326: private function makeSQLFromConditions( $conditions, &$bindings, $addSql = '' )
327: {
328: reset( $bindings );
329: $firstKey = key( $bindings );
330: $paramTypeIsNum = ( is_numeric( $firstKey ) );
331: $counter = 0;
332:
333: $sqlConditions = array();
334: foreach ( $conditions as $column => $values ) {
335: if ( !count( $values ) ) continue;
336:
337: $sql = $this->esc( $column );
338: $sql .= ' IN ( ';
339:
340: if ( !is_array( $values ) ) $values = array( $values );
341:
342:
343: if ( ctype_digit( implode( '', $values ) ) ) {
344: $sql .= implode( ',', $values ) . ' ) ';
345:
346:
347: $sqlConditions[] = $sql;
348: } else {
349:
350: if ( $paramTypeIsNum ) {
351: $sql .= implode( ',', array_fill( 0, count( $values ), '?' ) ) . ' ) ';
352:
353: array_unshift($sqlConditions, $sql);
354:
355: foreach ( $values as $k => $v ) {
356: $values[$k] = strval( $v );
357:
358: array_unshift( $bindings, $v );
359: }
360: } else {
361:
362: $slots = array();
363:
364: foreach( $values as $k => $v ) {
365: $slot = ':slot'.$counter++;
366: $slots[] = $slot;
367: $bindings[$slot] = strval( $v );
368: }
369:
370: $sql .= implode( ',', $slots ).' ) ';
371: $sqlConditions[] = $sql;
372: }
373: }
374: }
375:
376: $sql = '';
377: if ( is_array( $sqlConditions ) && count( $sqlConditions ) > 0 ) {
378: $sql = implode( ' AND ', $sqlConditions );
379: $sql = " WHERE ( $sql ) ";
380:
381: if ( $addSql ) $sql .= $addSql;
382: } elseif ( $addSql ) {
383: $sql = $addSql;
384: }
385:
386: return $sql;
387: }
388:
389: 390: 391: 392: 393: 394: 395: 396: 397:
398: private function getRelationalTablesAndColumns( $sourceType, $destType, $noQuote = FALSE )
399: {
400: $linkTable = $this->esc( $this->getAssocTable( array( $sourceType, $destType ) ), $noQuote );
401: $sourceCol = $this->esc( $sourceType . '_id', $noQuote );
402:
403: if ( $sourceType === $destType ) {
404: $destCol = $this->esc( $destType . '2_id', $noQuote );
405: } else {
406: $destCol = $this->esc( $destType . '_id', $noQuote );
407: }
408:
409: $sourceTable = $this->esc( $sourceType, $noQuote );
410: $destTable = $this->esc( $destType, $noQuote );
411:
412: return array( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol );
413: }
414:
415: 416: 417: 418: 419: 420: 421: 422: 423:
424: protected function getForeignKeyForTypeProperty( $type, $property )
425: {
426: $property = $this->esc( $property, TRUE );
427:
428: try {
429: $map = $this->getKeyMapForType( $type );
430: } catch ( SQLException $e ) {
431: return NULL;
432: }
433:
434: foreach( $map as $key ) {
435: if ( $key['from'] === $property ) return $key;
436: }
437: return NULL;
438: }
439:
440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462:
463: protected function getKeyMapForType( $type )
464: {
465: return array();
466: }
467:
468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481:
482: protected function makeFKLabel($from, $type, $to)
483: {
484: return "from_{$from}_to_table_{$type}_col_{$to}";
485: }
486:
487: 488: 489: 490: 491: 492: 493:
494: protected function getSQLFilterSnippet( $type )
495: {
496: $existingCols = array();
497: if (self::$flagSQLFilterSafeMode) {
498: $existingCols = $this->getColumns( $type );
499: }
500:
501: $sqlFilters = array();
502: if ( isset( self::$sqlFilters[QueryWriter::C_SQLFILTER_READ][$type] ) ) {
503: foreach( self::$sqlFilters[QueryWriter::C_SQLFILTER_READ][$type] as $property => $sqlFilter ) {
504: if ( !self::$flagSQLFilterSafeMode || isset( $existingCols[$property] ) ) {
505: $sqlFilters[] = $sqlFilter.' AS '.$property.' ';
506: }
507: }
508: }
509: $sqlFilterStr = ( count($sqlFilters) ) ? ( ','.implode( ',', $sqlFilters ) ) : '';
510: return $sqlFilterStr;
511: }
512:
513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528:
529: protected function getParametersForInClause( &$valueList, $otherBindings, $offset = 0 )
530: {
531: if ( is_array( $otherBindings ) && count( $otherBindings ) > 0 ) {
532: reset( $otherBindings );
533:
534: $key = key( $otherBindings );
535:
536: if ( !is_numeric($key) ) {
537: $filler = array();
538: $newList = (!$offset) ? array() : $valueList;
539: $counter = $offset;
540:
541: foreach( $valueList as $value ) {
542: $slot = ':slot' . ( $counter++ );
543: $filler[] = $slot;
544: $newList[$slot] = $value;
545: }
546:
547:
548: $valueList = $newList;
549:
550: return implode( ',', $filler );
551: }
552: }
553:
554: return implode( ',', array_fill( 0, count( $valueList ), '?' ) );
555: }
556:
557: 558: 559: 560: 561: 562: 563: 564: 565: 566:
567: protected function addDataType( $dataTypeID, $SQLDefinition )
568: {
569: $this->typeno_sqltype[ $dataTypeID ] = $SQLDefinition;
570: $this->sqltype_typeno[ $SQLDefinition ] = $dataTypeID;
571: return $this;
572: }
573:
574: 575: 576: 577: 578: 579: 580:
581: protected function getInsertSuffix( $table )
582: {
583: return '';
584: }
585:
586: 587: 588: 589: 590: 591: 592: 593: 594:
595: protected function startsWithZeros( $value )
596: {
597: $value = strval( $value );
598:
599: if ( strlen( $value ) > 1 && strpos( $value, '0' ) === 0 && strpos( $value, '0.' ) !== 0 ) {
600: return TRUE;
601: } else {
602: return FALSE;
603: }
604: }
605:
606: 607: 608: 609: 610: 611: 612: 613: 614: 615:
616: protected function insertRecord( $type, $insertcolumns, $insertvalues )
617: {
618: $default = $this->defaultValue;
619: $suffix = $this->getInsertSuffix( $type );
620: $table = $this->esc( $type );
621:
622: if ( count( $insertvalues ) > 0 && is_array( $insertvalues[0] ) && count( $insertvalues[0] ) > 0 ) {
623:
624: $insertSlots = array();
625: foreach ( $insertcolumns as $k => $v ) {
626: $insertcolumns[$k] = $this->esc( $v );
627:
628: if (isset(self::$sqlFilters['w'][$type][$v])) {
629: $insertSlots[] = self::$sqlFilters['w'][$type][$v];
630: } else {
631: $insertSlots[] = '?';
632: }
633: }
634:
635: $insertSQL = "INSERT INTO $table ( id, " . implode( ',', $insertcolumns ) . " ) VALUES
636: ( $default, " . implode( ',', $insertSlots ) . " ) $suffix";
637:
638: $ids = array();
639: foreach ( $insertvalues as $i => $insertvalue ) {
640: $ids[] = $this->adapter->getCell( $insertSQL, $insertvalue, $i );
641: }
642:
643: $result = count( $ids ) === 1 ? array_pop( $ids ) : $ids;
644: } else {
645: $result = $this->adapter->getCell( "INSERT INTO $table (id) VALUES($default) $suffix" );
646: }
647:
648: if ( $suffix ) return $result;
649:
650: $last_id = $this->adapter->getInsertID();
651:
652: return $last_id;
653: }
654:
655: 656: 657: 658: 659: 660: 661:
662: protected function check( $struct )
663: {
664: if ( !is_string( $struct ) || !preg_match( '/^[a-zA-Z0-9_]+$/', $struct ) ) {
665: throw new RedException( 'Identifier does not conform to RedBeanPHP security policies.' );
666: }
667:
668: return $struct;
669: }
670:
671: 672: 673: 674: 675: 676: 677: 678:
679: public function tableExists( $table )
680: {
681: $tables = $this->getTables();
682:
683: return in_array( $table, $tables );
684: }
685:
686: 687: 688:
689: public function glueSQLCondition( $sql, $glue = NULL )
690: {
691: static $snippetCache = array();
692:
693: if ( trim( $sql ) === '' ) {
694: return $sql;
695: }
696:
697: $key = $glue . '|' . $sql;
698:
699: if ( isset( $snippetCache[$key] ) ) {
700: return $snippetCache[$key];
701: }
702:
703: $lsql = ltrim( $sql );
704:
705: if ( preg_match( '/^(INNER|LEFT|RIGHT|JOIN|AND|OR|WHERE|ORDER|GROUP|HAVING|LIMIT|OFFSET)\s+/i', $lsql ) ) {
706: if ( $glue === QueryWriter::C_GLUE_WHERE && stripos( $lsql, 'AND' ) === 0 ) {
707: $snippetCache[$key] = ' WHERE ' . substr( $lsql, 3 );
708: } else {
709: $snippetCache[$key] = $sql;
710: }
711: } else {
712: $snippetCache[$key] = ( ( $glue === QueryWriter::C_GLUE_AND ) ? ' AND ' : ' WHERE ') . $sql;
713: }
714:
715: return $snippetCache[$key];
716: }
717:
718: 719: 720:
721: public function glueLimitOne( $sql = '')
722: {
723: return ( strpos( strtoupper( $sql ), 'LIMIT' ) === FALSE ) ? ( $sql . ' LIMIT 1 ' ) : $sql;
724: }
725:
726: 727: 728:
729: public function esc( $dbStructure, $dontQuote = FALSE )
730: {
731: $this->check( $dbStructure );
732:
733: return ( $dontQuote ) ? $dbStructure : $this->quoteCharacter . $dbStructure . $this->quoteCharacter;
734: }
735:
736: 737: 738:
739: public function addColumn( $type, $column, $field )
740: {
741: $table = $type;
742: $type = $field;
743: $table = $this->esc( $table );
744: $column = $this->esc( $column );
745:
746: $type = ( isset( $this->typeno_sqltype[$type] ) ) ? $this->typeno_sqltype[$type] : '';
747:
748: $this->adapter->exec( "ALTER TABLE $table ADD $column $type " );
749: }
750:
751: 752: 753:
754: public function updateRecord( $type, $updatevalues, $id = NULL )
755: {
756: $table = $type;
757:
758: if ( !$id ) {
759: $insertcolumns = $insertvalues = array();
760:
761: foreach ( $updatevalues as $pair ) {
762: $insertcolumns[] = $pair['property'];
763: $insertvalues[] = $pair['value'];
764: }
765:
766:
767: return (string) $this->insertRecord( $table, $insertcolumns, array( $insertvalues ) );
768: }
769:
770: if ( $id && !count( $updatevalues ) ) {
771: return $id;
772: }
773:
774: $table = $this->esc( $table );
775: $sql = "UPDATE $table SET ";
776:
777: $p = $v = array();
778:
779: foreach ( $updatevalues as $uv ) {
780:
781: if ( isset( self::$sqlFilters['w'][$type][$uv['property']] ) ) {
782: $p[] = " {$this->esc( $uv["property"] )} = ". self::$sqlFilters['w'][$type][$uv['property']];
783: } else {
784: $p[] = " {$this->esc( $uv["property"] )} = ? ";
785: }
786:
787: $v[] = $uv['value'];
788: }
789:
790: $sql .= implode( ',', $p ) . ' WHERE id = ? ';
791:
792: $v[] = $id;
793:
794: $this->adapter->exec( $sql, $v );
795:
796: return $id;
797: }
798:
799: 800: 801:
802: public function writeJoin( $type, $targetType, $leftRight = 'LEFT' )
803: {
804: if ( $leftRight !== 'LEFT' && $leftRight !== 'RIGHT' && $leftRight !== 'INNER' )
805: throw new RedException( 'Invalid JOIN.' );
806:
807: $table = $this->esc( $type );
808: $targetTable = $this->esc( $targetType );
809: $field = $this->esc( $targetType, TRUE );
810: return " {$leftRight} JOIN {$targetTable} ON {$targetTable}.id = {$table}.{$field}_id ";
811: }
812:
813: 814: 815:
816: public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
817: {
818: $addSql = $this->glueSQLCondition( $addSql, ( count($conditions) > 0) ? QueryWriter::C_GLUE_AND : NULL );
819:
820: $key = NULL;
821: if ( $this->flagUseCache ) {
822: $key = $this->getCacheKey( array( $conditions, $addSql, $bindings, 'select' ) );
823:
824: if ( $cached = $this->getCached( $type, $key ) ) {
825: return $cached;
826: }
827: }
828:
829: $table = $this->esc( $type );
830:
831: $sqlFilterStr = '';
832: if ( count( self::$sqlFilters ) ) {
833: $sqlFilterStr = $this->getSQLFilterSnippet( $type );
834: }
835:
836: $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
837:
838: $fieldSelection = ( self::$flagNarrowFieldMode ) ? "{$table}.*" : '*';
839: $sql = "SELECT {$fieldSelection} {$sqlFilterStr} FROM {$table} {$sql} -- keep-cache";
840:
841: $rows = $this->adapter->get( $sql, $bindings );
842:
843: if ( $this->flagUseCache && $key ) {
844: $this->putResultInCache( $type, $key, $rows );
845: }
846:
847: return $rows;
848: }
849:
850: 851: 852:
853: public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() )
854: {
855: $sql = $this->glueSQLCondition( $addSql, NULL );
856: $table = $this->esc( $type );
857: $sql = "SELECT {$table}.* FROM {$table} {$sql}";
858: return $this->adapter->getCursor( $sql, $bindings );
859: }
860:
861: 862: 863:
864: public function queryRecordRelated( $sourceType, $destType, $linkIDs, $addSql = '', $bindings = array() )
865: {
866: $addSql = $this->glueSQLCondition( $addSql, QueryWriter::C_GLUE_WHERE );
867:
868: list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
869:
870: $key = $this->getCacheKey( array( $sourceType, $destType, implode( ',', $linkIDs ), $addSql, $bindings ) );
871:
872: if ( $this->flagUseCache && $cached = $this->getCached( $destType, $key ) ) {
873: return $cached;
874: }
875:
876: $inClause = $this->getParametersForInClause( $linkIDs, $bindings );
877:
878: $sqlFilterStr = '';
879: if ( count( self::$sqlFilters ) ) {
880: $sqlFilterStr = $this->getSQLFilterSnippet( $destType );
881: }
882:
883: if ( $sourceType === $destType ) {
884: $inClause2 = $this->getParametersForInClause( $linkIDs, $bindings, count( $bindings ) );
885: $sql = "
886: SELECT
887: {$destTable}.* {$sqlFilterStr} ,
888: COALESCE(
889: NULLIF({$linkTable}.{$sourceCol}, {$destTable}.id),
890: NULLIF({$linkTable}.{$destCol}, {$destTable}.id)) AS linked_by
891: FROM {$linkTable}
892: INNER JOIN {$destTable} ON
893: ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) ) OR
894: ( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} IN ($inClause2) )
895: {$addSql}
896: -- keep-cache";
897:
898: $linkIDs = array_merge( $linkIDs, $linkIDs );
899: } else {
900: $sql = "
901: SELECT
902: {$destTable}.* {$sqlFilterStr},
903: {$linkTable}.{$sourceCol} AS linked_by
904: FROM {$linkTable}
905: INNER JOIN {$destTable} ON
906: ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) )
907: {$addSql}
908: -- keep-cache";
909: }
910:
911: $bindings = array_merge( $linkIDs, $bindings );
912:
913: $rows = $this->adapter->get( $sql, $bindings );
914:
915: $this->putResultInCache( $destType, $key, $rows );
916:
917: return $rows;
918: }
919:
920: 921: 922:
923: public function queryRecordLink( $sourceType, $destType, $sourceID, $destID )
924: {
925: list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
926:
927: $key = $this->getCacheKey( array( $sourceType, $destType, $sourceID, $destID ) );
928:
929: if ( $this->flagUseCache && $cached = $this->getCached( $linkTable, $key ) ) {
930: return $cached;
931: }
932:
933: $sqlFilterStr = '';
934: if ( count( self::$sqlFilters ) ) {
935: $sqlFilterStr = $this->getSQLFilterSnippet( $destType );
936: }
937:
938: if ( $sourceTable === $destTable ) {
939: $sql = "SELECT {$linkTable}.* {$sqlFilterStr} FROM {$linkTable}
940: WHERE ( {$sourceCol} = ? AND {$destCol} = ? ) OR
941: ( {$destCol} = ? AND {$sourceCol} = ? ) -- keep-cache";
942: $row = $this->adapter->getRow( $sql, array( $sourceID, $destID, $sourceID, $destID ) );
943: } else {
944: $sql = "SELECT {$linkTable}.* {$sqlFilterStr} FROM {$linkTable}
945: WHERE {$sourceCol} = ? AND {$destCol} = ? -- keep-cache";
946: $row = $this->adapter->getRow( $sql, array( $sourceID, $destID ) );
947: }
948:
949: $this->putResultInCache( $linkTable, $key, $row );
950:
951: return $row;
952: }
953:
954: 955: 956:
957: public function queryTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() )
958: {
959: $assocType = $this->getAssocTable( array( $type, 'tag' ) );
960: $assocTable = $this->esc( $assocType );
961: $assocField = $type . '_id';
962: $table = $this->esc( $type );
963: $slots = implode( ',', array_fill( 0, count( $tagList ), '?' ) );
964: $score = ( $all ) ? count( $tagList ) : 1;
965:
966: $sql = "
967: SELECT {$table}.*, count({$table}.id) FROM {$table}
968: INNER JOIN {$assocTable} ON {$assocField} = {$table}.id
969: INNER JOIN tag ON {$assocTable}.tag_id = tag.id
970: WHERE tag.title IN ({$slots})
971: GROUP BY {$table}.id
972: HAVING count({$table}.id) >= ?
973: {$addSql}
974: ";
975:
976: $bindings = array_merge( $tagList, array( $score ), $bindings );
977: $rows = $this->adapter->get( $sql, $bindings );
978: return $rows;
979: }
980:
981: 982: 983:
984: public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
985: {
986: $addSql = $this->glueSQLCondition( $addSql );
987:
988: $table = $this->esc( $type );
989:
990: $this->updateCache();
991:
992: $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
993: $sql = "SELECT COUNT(*) FROM {$table} {$sql} -- keep-cache";
994:
995: return (int) $this->adapter->getCell( $sql, $bindings );
996: }
997:
998: 999: 1000:
1001: public function queryRecordCountRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() )
1002: {
1003: list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
1004:
1005: $this->updateCache();
1006:
1007: if ( $sourceType === $destType ) {
1008: $sql = "
1009: SELECT COUNT(*) FROM {$linkTable}
1010: INNER JOIN {$destTable} ON
1011: ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? ) OR
1012: ( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} = ? )
1013: {$addSql}
1014: -- keep-cache";
1015:
1016: $bindings = array_merge( array( $linkID, $linkID ), $bindings );
1017: } else {
1018: $sql = "
1019: SELECT COUNT(*) FROM {$linkTable}
1020: INNER JOIN {$destTable} ON
1021: ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? )
1022: {$addSql}
1023: -- keep-cache";
1024:
1025: $bindings = array_merge( array( $linkID ), $bindings );
1026: }
1027:
1028: return (int) $this->adapter->getCell( $sql, $bindings );
1029: }
1030:
1031: 1032: 1033:
1034: public function deleteRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
1035: {
1036: $addSql = $this->glueSQLCondition( $addSql );
1037:
1038: $table = $this->esc( $type );
1039:
1040: $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
1041: $sql = "DELETE FROM {$table} {$sql}";
1042:
1043: $this->adapter->exec( $sql, $bindings );
1044: }
1045:
1046: 1047: 1048:
1049: public function deleteRelations( $sourceType, $destType, $sourceID )
1050: {
1051: list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
1052:
1053: if ( $sourceTable === $destTable ) {
1054: $sql = "DELETE FROM {$linkTable}
1055: WHERE ( {$sourceCol} = ? ) OR
1056: ( {$destCol} = ? )
1057: ";
1058:
1059: $this->adapter->exec( $sql, array( $sourceID, $sourceID ) );
1060: } else {
1061: $sql = "DELETE FROM {$linkTable}
1062: WHERE {$sourceCol} = ? ";
1063:
1064: $this->adapter->exec( $sql, array( $sourceID ) );
1065: }
1066: }
1067:
1068: 1069: 1070:
1071: public function widenColumn( $type, $property, $dataType )
1072: {
1073: if ( !isset($this->typeno_sqltype[$dataType]) ) return FALSE;
1074:
1075: $table = $this->esc( $type );
1076: $column = $this->esc( $property );
1077:
1078: $newType = $this->typeno_sqltype[$dataType];
1079:
1080: $this->adapter->exec( "ALTER TABLE $table CHANGE $column $column $newType " );
1081:
1082: return TRUE;
1083: }
1084:
1085: 1086: 1087:
1088: public function wipe( $type )
1089: {
1090: $table = $this->esc( $type );
1091:
1092: $this->adapter->exec( "TRUNCATE $table " );
1093: }
1094:
1095: 1096: 1097:
1098: public function renameAssocTable( $from, $to = NULL )
1099: {
1100: self::renameAssociation( $from, $to );
1101: }
1102:
1103: 1104: 1105:
1106: public function getAssocTable( $types )
1107: {
1108: return self::getAssocTableFormat( $types );
1109: }
1110:
1111: 1112: 1113: 1114: 1115: 1116: 1117: 1118: 1119:
1120: public function setUseCache( $yesNo )
1121: {
1122: $this->flushCache();
1123:
1124: $this->flagUseCache = (bool) $yesNo;
1125: }
1126:
1127: 1128: 1129: 1130: 1131: 1132: 1133:
1134: public function flushCache( $newMaxCacheSizePerType = NULL )
1135: {
1136: if ( !is_null( $newMaxCacheSizePerType ) && $newMaxCacheSizePerType > 0 ) {
1137: $this->maxCacheSizePerType = $newMaxCacheSizePerType;
1138: }
1139: $count = count( $this->cache, COUNT_RECURSIVE );
1140: $this->cache = array();
1141: return $count;
1142: }
1143:
1144: 1145: 1146: 1147: 1148: 1149: 1150: 1151:
1152: public function safeColumn( $column, $noQuotes = FALSE )
1153: {
1154: return $this->esc( $column, $noQuotes );
1155: }
1156:
1157: 1158: 1159: 1160: 1161: 1162: 1163: 1164:
1165: public function safeTable( $table, $noQuotes = FALSE )
1166: {
1167: return $this->esc( $table, $noQuotes );
1168: }
1169:
1170: 1171: 1172:
1173: public function inferFetchType( $type, $property )
1174: {
1175: $type = $this->esc( $type, TRUE );
1176: $field = $this->esc( $property, TRUE ) . '_id';
1177: $keys = $this->getKeyMapForType( $type );
1178:
1179: foreach( $keys as $key ) {
1180: if (
1181: $key['from'] === $field
1182: ) return $key['table'];
1183: }
1184: return NULL;
1185: }
1186:
1187: 1188: 1189:
1190: public function addUniqueIndex( $type, $properties )
1191: {
1192: return $this->addUniqueConstraint( $type, $properties );
1193: }
1194: }
1195: