| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  | <?php | 
					
						
							|  |  |  | /* | 
					
						
							|  |  |  |  * StatusNet - the distributed open-source microblogging tool | 
					
						
							|  |  |  |  * Copyright (C) 2010, StatusNet, Inc. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  |  * the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  |  * (at your option) any later version. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  |  * GNU Affero General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  |  * along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Wrapper for Memcached_DataObject which knows its own schema definition. | 
					
						
							|  |  |  |  * Builds its own damn settings from a schema definition. | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2010-08-16 15:14:16 -07:00
										 |  |  |  * @author Brion Vibber <brion@status.net> | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2010-08-16 15:14:16 -07:00
										 |  |  | abstract class Managed_DataObject extends Memcached_DataObject | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  | { | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The One True Thingy that must be defined and declared. | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2013-09-21 18:44:05 +02:00
										 |  |  |     public static function schemaDef() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         throw new MethodNotImplementedException(__METHOD__); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-12 19:12:13 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get an instance by key | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string $k Key to use to lookup (usually 'id' for this class) | 
					
						
							|  |  |  |      * @param mixed  $v Value to lookup | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return get_called_class() object if found, or null for no hits | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2013-08-18 13:04:58 +02:00
										 |  |  |     static function getKV($k,$v=NULL) | 
					
						
							| 
									
										
										
										
											2013-08-12 19:12:13 +02:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2013-08-18 15:42:51 +02:00
										 |  |  |         return parent::getClassKV(get_called_class(), $k, $v); | 
					
						
							| 
									
										
										
										
											2013-08-12 19:12:13 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-18 15:42:51 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get an instance by compound key | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * This is a utility method to get a single instance with a given set of | 
					
						
							|  |  |  |      * key-value pairs. Usually used for the primary key for a compound key; thus | 
					
						
							|  |  |  |      * the name. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param array $kv array of key-value mappings | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return get_called_class() object if found, or null for no hits | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2013-08-29 10:27:39 +02:00
										 |  |  |     static function pkeyGet(array $kv) | 
					
						
							| 
									
										
										
										
											2013-08-18 15:42:51 +02:00
										 |  |  |     { | 
					
						
							|  |  |  |         return parent::pkeyGetClass(get_called_class(), $kv); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2013-08-12 19:12:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 21:51:56 +02:00
										 |  |  |     static function pkeyCols() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return parent::pkeyColsClass(get_called_class()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-29 10:38:11 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get multiple items from the database by key | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string  $keyCol    name of column for key | 
					
						
							|  |  |  |      * @param array   $keyVals   key values to fetch | 
					
						
							|  |  |  |      * @param boolean $skipNulls return only non-null results? | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array Array of objects, in order | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  | 	static function multiGet($keyCol, array $keyVals, $skipNulls=true) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 	    return parent::multiGetClass(get_called_class(), $keyCol, $keyVals, $skipNulls); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-29 10:13:07 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get multiple items from the database by key | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string  $keyCol    name of column for key | 
					
						
							|  |  |  |      * @param array   $keyVals   key values to fetch | 
					
						
							|  |  |  |      * @param array   $otherCols Other columns to hold fixed | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array Array mapping $keyVals to objects, or null if not found | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  | 	static function pivotGet($keyCol, array $keyVals, array $otherCols=array()) | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 	    return parent::pivotGetClass(get_called_class(), $keyCol, $keyVals, $otherCols); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-18 21:02:33 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get a multi-instance object | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * This is a utility method to get multiple instances with a given set of | 
					
						
							| 
									
										
										
										
											2013-09-21 16:55:18 +02:00
										 |  |  |      * values for a specific column. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param string $keyCol  key column name | 
					
						
							|  |  |  |      * @param array  $keyVals array of key values | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return get_called_class() object with multiple instances if found, | 
					
						
							|  |  |  |      *         Exception is thrown when no entries are found. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     static function listFind($keyCol, array $keyVals) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return parent::listFindClass(get_called_class(), $keyCol, $keyVals); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							| 
									
										
										
										
											2013-10-01 11:37:59 +02:00
										 |  |  |      * Get a multi-instance object separated into an array | 
					
						
							| 
									
										
										
										
											2013-09-21 16:55:18 +02:00
										 |  |  |      * | 
					
						
							|  |  |  |      * This is a utility method to get multiple instances with a given set of | 
					
						
							| 
									
										
										
										
											2013-08-18 21:02:33 +02:00
										 |  |  |      * values for a specific key column. Usually used for the primary key when | 
					
						
							| 
									
										
										
										
											2013-09-21 16:55:18 +02:00
										 |  |  |      * multiple values are desired. Result is an array. | 
					
						
							| 
									
										
										
										
											2013-08-18 21:02:33 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2013-08-29 10:27:39 +02:00
										 |  |  |      * @param string $keyCol  key column name | 
					
						
							|  |  |  |      * @param array  $keyVals array of key values | 
					
						
							| 
									
										
										
										
											2013-08-18 21:02:33 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2013-09-21 16:55:18 +02:00
										 |  |  |      * @return array with an get_called_class() object for each $keyVals entry | 
					
						
							| 
									
										
										
										
											2013-08-18 21:02:33 +02:00
										 |  |  |      * | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2013-08-29 10:27:39 +02:00
										 |  |  |     static function listGet($keyCol, array $keyVals) | 
					
						
							| 
									
										
										
										
											2013-08-18 21:02:33 +02:00
										 |  |  |     { | 
					
						
							|  |  |  |         return parent::listGetClass(get_called_class(), $keyCol, $keyVals); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * get/set an associative array of table columns | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @access public | 
					
						
							|  |  |  |      * @return array (associative) | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2013-10-14 18:18:11 +02:00
										 |  |  |     public function table() | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2013-08-20 09:43:51 +02:00
										 |  |  |         $table = static::schemaDef(); | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  |         return array_map(array($this, 'columnBitmap'), $table['fields']); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * get/set an  array of table primary keys | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Key info is pulled from the table definition array. | 
					
						
							|  |  |  |      *  | 
					
						
							|  |  |  |      * @access private | 
					
						
							|  |  |  |      * @return array | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     function keys() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return array_keys($this->keyTypes()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get a sequence key | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Returns the first serial column defined in the table, if any. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @access private | 
					
						
							|  |  |  |      * @return array (column,use_native,sequence_name) | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function sequenceKey() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2013-10-14 18:18:11 +02:00
										 |  |  |         $table = static::schemaDef(); | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  |         foreach ($table['fields'] as $name => $column) { | 
					
						
							|  |  |  |             if ($column['type'] == 'serial') { | 
					
						
							|  |  |  |                 // We have a serial/autoincrement column.
 | 
					
						
							|  |  |  |                 // Declare it to be a native sequence!
 | 
					
						
							|  |  |  |                 return array($name, true, false); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // No sequence key on this table.
 | 
					
						
							|  |  |  |         return array(false, false, false); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Return key definitions for DB_DataObject and Memcache_DataObject. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * DB_DataObject needs to know about keys that the table has; this function
 | 
					
						
							|  |  |  |      * defines them. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array key definitions | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     function keyTypes() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2013-10-14 18:18:11 +02:00
										 |  |  |         $table = static::schemaDef(); | 
					
						
							| 
									
										
										
										
											2011-03-21 15:04:32 -07:00
										 |  |  |         $keys = array(); | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (!empty($table['unique keys'])) { | 
					
						
							|  |  |  |             foreach ($table['unique keys'] as $idx => $fields) { | 
					
						
							|  |  |  |                 foreach ($fields as $name) { | 
					
						
							|  |  |  |                     $keys[$name] = 'U'; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!empty($table['primary key'])) { | 
					
						
							|  |  |  |             foreach ($table['primary key'] as $name) { | 
					
						
							|  |  |  |                 $keys[$name] = 'K'; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return $keys; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Build the appropriate DB_DataObject bitfield map for this field. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param array $column | 
					
						
							|  |  |  |      * @return int | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     function columnBitmap($column) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2010-08-16 15:28:00 -07:00
										 |  |  |         $type = $column['type']; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // For quoting style...
 | 
					
						
							|  |  |  |         $intTypes = array('int', | 
					
						
							|  |  |  |                           'integer', | 
					
						
							|  |  |  |                           'float', | 
					
						
							|  |  |  |                           'serial', | 
					
						
							|  |  |  |                           'numeric'); | 
					
						
							|  |  |  |         if (in_array($type, $intTypes)) { | 
					
						
							|  |  |  |             $style = DB_DATAOBJECT_INT; | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             $style = DB_DATAOBJECT_STR; | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-16 15:28:00 -07:00
										 |  |  |         // Data type formatting style...
 | 
					
						
							|  |  |  |         $formatStyles = array('blob' => DB_DATAOBJECT_BLOB, | 
					
						
							|  |  |  |                               'text' => DB_DATAOBJECT_TXT, | 
					
						
							|  |  |  |                               'date' => DB_DATAOBJECT_DATE, | 
					
						
							|  |  |  |                               'time' => DB_DATAOBJECT_TIME, | 
					
						
							|  |  |  |                               'datetime' => DB_DATAOBJECT_DATE | DB_DATAOBJECT_TIME, | 
					
						
							|  |  |  |                               'timestamp' => DB_DATAOBJECT_MYSQLTIMESTAMP); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (isset($formatStyles[$type])) { | 
					
						
							|  |  |  |             $style |= $formatStyles[$type]; | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-16 15:28:00 -07:00
										 |  |  |         // Nullable?
 | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  |         if (!empty($column['not null'])) { | 
					
						
							| 
									
										
										
										
											2010-08-16 15:28:00 -07:00
										 |  |  |             $style |= DB_DATAOBJECT_NOTNULL; | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-16 15:28:00 -07:00
										 |  |  |         return $style; | 
					
						
							| 
									
										
										
										
											2010-08-16 14:02:31 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2011-08-26 11:37:45 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     function links() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $links = array(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-14 18:18:11 +02:00
										 |  |  |         $table = static::schemaDef(); | 
					
						
							| 
									
										
										
										
											2011-08-26 11:37:45 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         foreach ($table['foreign keys'] as $keyname => $keydef) { | 
					
						
							|  |  |  |             if (count($keydef) == 2 && is_string($keydef[0]) && is_array($keydef[1]) && count($keydef[1]) == 1) { | 
					
						
							| 
									
										
										
										
											2011-09-07 21:45:49 -07:00
										 |  |  |                 if (isset($keydef[1][0])) { | 
					
						
							|  |  |  |                     $links[$keydef[1][0]] = $keydef[0].':'.$keydef[1][1]; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2011-08-26 11:37:45 -04:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return $links; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2011-09-28 18:32:43 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Return a list of all primary/unique keys / vals that will be used for | 
					
						
							|  |  |  |      * caching. This will understand compound unique keys, which | 
					
						
							|  |  |  |      * Memcached_DataObject doesn't have enough info to handle properly. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return array of strings | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     function _allCacheKeys() | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2013-10-14 18:18:11 +02:00
										 |  |  |         $table = static::schemaDef(); | 
					
						
							| 
									
										
										
										
											2011-09-28 18:32:43 -07:00
										 |  |  |         $ckeys = array(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!empty($table['unique keys'])) { | 
					
						
							| 
									
										
											  
											
												Further fixes to Managed_DataObject::_allCacheKeys(): now uses self::multicacheKey() to generate the (possibly compound) keys, which makes it match the order of the keys used when calling pkeyGet().
This should resolve the issues darkip was reporting with user_im_prefs entries returning null immediately after insertion (seen with memcached off, so it was happening even with the built-in in-process cache in the Cache base class).
What was happening was that the initial pkeyGet() would end up saving a negative cache entry under the form with the fields sorted in the key, as via multicacheKey():
    'statusnet:blaguette:user_im_prefs:screenname,transport:brionv,sms' => 'N;'
then we'd do an insert() on the new entry, saving cache entries for the non-sorted key names returned by _allCacheKeys():
    'statusnet:blaguette:user_im_prefs:transport,screenname:sms,brionv' => 'O...'
    'statusnet:blaguette:user_im_prefs:user_id,transport:1234,sms' => 'O...'
but the next query via pkeyGet() still saw the negative lookup cache from before, and came back with null.
Now, _allCacheKeys() sorts the fields in the keys by using the same key-builder function, and queries pick up the same thing you just inserted. :)
											
										 
											2011-09-29 15:21:52 -07:00
										 |  |  |             $keyNames = $table['unique keys']; | 
					
						
							|  |  |  |             foreach ($keyNames as $idx => $fields) { | 
					
						
							| 
									
										
										
										
											2011-09-28 18:32:43 -07:00
										 |  |  |                 $val = array(); | 
					
						
							|  |  |  |                 foreach ($fields as $name) { | 
					
						
							| 
									
										
											  
											
												Further fixes to Managed_DataObject::_allCacheKeys(): now uses self::multicacheKey() to generate the (possibly compound) keys, which makes it match the order of the keys used when calling pkeyGet().
This should resolve the issues darkip was reporting with user_im_prefs entries returning null immediately after insertion (seen with memcached off, so it was happening even with the built-in in-process cache in the Cache base class).
What was happening was that the initial pkeyGet() would end up saving a negative cache entry under the form with the fields sorted in the key, as via multicacheKey():
    'statusnet:blaguette:user_im_prefs:screenname,transport:brionv,sms' => 'N;'
then we'd do an insert() on the new entry, saving cache entries for the non-sorted key names returned by _allCacheKeys():
    'statusnet:blaguette:user_im_prefs:transport,screenname:sms,brionv' => 'O...'
    'statusnet:blaguette:user_im_prefs:user_id,transport:1234,sms' => 'O...'
but the next query via pkeyGet() still saw the negative lookup cache from before, and came back with null.
Now, _allCacheKeys() sorts the fields in the keys by using the same key-builder function, and queries pick up the same thing you just inserted. :)
											
										 
											2011-09-29 15:21:52 -07:00
										 |  |  |                     $val[$name] = self::valueString($this->$name); | 
					
						
							| 
									
										
										
										
											2011-09-28 18:32:43 -07:00
										 |  |  |                 } | 
					
						
							| 
									
										
											  
											
												Further fixes to Managed_DataObject::_allCacheKeys(): now uses self::multicacheKey() to generate the (possibly compound) keys, which makes it match the order of the keys used when calling pkeyGet().
This should resolve the issues darkip was reporting with user_im_prefs entries returning null immediately after insertion (seen with memcached off, so it was happening even with the built-in in-process cache in the Cache base class).
What was happening was that the initial pkeyGet() would end up saving a negative cache entry under the form with the fields sorted in the key, as via multicacheKey():
    'statusnet:blaguette:user_im_prefs:screenname,transport:brionv,sms' => 'N;'
then we'd do an insert() on the new entry, saving cache entries for the non-sorted key names returned by _allCacheKeys():
    'statusnet:blaguette:user_im_prefs:transport,screenname:sms,brionv' => 'O...'
    'statusnet:blaguette:user_im_prefs:user_id,transport:1234,sms' => 'O...'
but the next query via pkeyGet() still saw the negative lookup cache from before, and came back with null.
Now, _allCacheKeys() sorts the fields in the keys by using the same key-builder function, and queries pick up the same thing you just inserted. :)
											
										 
											2011-09-29 15:21:52 -07:00
										 |  |  |                 $ckeys[] = self::multicacheKey($this->tableName(), $val); | 
					
						
							| 
									
										
										
										
											2011-09-28 18:32:43 -07:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!empty($table['primary key'])) { | 
					
						
							|  |  |  |             $fields = $table['primary key']; | 
					
						
							|  |  |  |             $val = array(); | 
					
						
							|  |  |  |             foreach ($fields as $name) { | 
					
						
							| 
									
										
											  
											
												Further fixes to Managed_DataObject::_allCacheKeys(): now uses self::multicacheKey() to generate the (possibly compound) keys, which makes it match the order of the keys used when calling pkeyGet().
This should resolve the issues darkip was reporting with user_im_prefs entries returning null immediately after insertion (seen with memcached off, so it was happening even with the built-in in-process cache in the Cache base class).
What was happening was that the initial pkeyGet() would end up saving a negative cache entry under the form with the fields sorted in the key, as via multicacheKey():
    'statusnet:blaguette:user_im_prefs:screenname,transport:brionv,sms' => 'N;'
then we'd do an insert() on the new entry, saving cache entries for the non-sorted key names returned by _allCacheKeys():
    'statusnet:blaguette:user_im_prefs:transport,screenname:sms,brionv' => 'O...'
    'statusnet:blaguette:user_im_prefs:user_id,transport:1234,sms' => 'O...'
but the next query via pkeyGet() still saw the negative lookup cache from before, and came back with null.
Now, _allCacheKeys() sorts the fields in the keys by using the same key-builder function, and queries pick up the same thing you just inserted. :)
											
										 
											2011-09-29 15:21:52 -07:00
										 |  |  |                 $val[$name] = self::valueString($this->$name); | 
					
						
							| 
									
										
										
										
											2011-09-28 18:32:43 -07:00
										 |  |  |             } | 
					
						
							| 
									
										
											  
											
												Further fixes to Managed_DataObject::_allCacheKeys(): now uses self::multicacheKey() to generate the (possibly compound) keys, which makes it match the order of the keys used when calling pkeyGet().
This should resolve the issues darkip was reporting with user_im_prefs entries returning null immediately after insertion (seen with memcached off, so it was happening even with the built-in in-process cache in the Cache base class).
What was happening was that the initial pkeyGet() would end up saving a negative cache entry under the form with the fields sorted in the key, as via multicacheKey():
    'statusnet:blaguette:user_im_prefs:screenname,transport:brionv,sms' => 'N;'
then we'd do an insert() on the new entry, saving cache entries for the non-sorted key names returned by _allCacheKeys():
    'statusnet:blaguette:user_im_prefs:transport,screenname:sms,brionv' => 'O...'
    'statusnet:blaguette:user_im_prefs:user_id,transport:1234,sms' => 'O...'
but the next query via pkeyGet() still saw the negative lookup cache from before, and came back with null.
Now, _allCacheKeys() sorts the fields in the keys by using the same key-builder function, and queries pick up the same thing you just inserted. :)
											
										 
											2011-09-29 15:21:52 -07:00
										 |  |  |             $ckeys[] = self::multicacheKey($this->tableName(), $val); | 
					
						
							| 
									
										
										
										
											2011-09-28 18:32:43 -07:00
										 |  |  |         } | 
					
						
							|  |  |  |         return $ckeys; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2014-03-06 14:21:44 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-02-18 12:15:46 +01:00
										 |  |  |     public function escapedTableName() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         return common_database_tablename($this->tableName()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 22:17:40 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Returns an object by looking at the primary key column(s). | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Will require all primary key columns to be defined in an associative array | 
					
						
							|  |  |  |      * and ignore any keys which are not part of the primary key. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Will NOT accept NULL values as part of primary key. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param   array   $vals       Must match all primary key columns for the dataobject. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return  Managed_DataObject  of the get_called_class() type | 
					
						
							|  |  |  |      * @throws  NoResultException   if no object with that primary key | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     static function getByPK(array $vals) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $classname = get_called_class(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $pkey = static::pkeyCols(); | 
					
						
							|  |  |  |         if (is_null($pkey)) { | 
					
						
							|  |  |  |             throw new ServerException("Failed to get primary key columns for class '{$classname}'"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $object = new $classname(); | 
					
						
							|  |  |  |         foreach ($pkey as $col) { | 
					
						
							|  |  |  |             if (!array_key_exists($col, $vals)) { | 
					
						
							| 
									
										
										
										
											2015-10-10 22:20:53 +02:00
										 |  |  |                 throw new ServerException("Missing primary key column '{$col}' for ".get_called_class()." among provided keys: ".implode(',', array_keys($vals))); | 
					
						
							| 
									
										
										
										
											2015-06-04 22:17:40 +02:00
										 |  |  |             } elseif (is_null($vals[$col])) { | 
					
						
							|  |  |  |                 throw new ServerException("NULL values not allowed in getByPK for column '{$col}'"); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             $object->$col = $vals[$col]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (!$object->find(true)) { | 
					
						
							|  |  |  |             throw new NoResultException($object); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return $object; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-10-12 17:48:23 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Returns an object by looking at given unique key columns. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Will NOT accept NULL values for a unique key column. Ignores non-key values. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param   array   $vals       All array keys which are set must be non-null. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return  Managed_DataObject  of the get_called_class() type | 
					
						
							|  |  |  |      * @throws  NoResultException   if no object with that primary key | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     static function getByKeys(array $vals) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $classname = get_called_class(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $object = new $classname(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $keys = $object->keys(); | 
					
						
							|  |  |  |         if (is_null($keys)) { | 
					
						
							|  |  |  |             throw new ServerException("Failed to get key columns for class '{$classname}'"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         foreach ($keys as $col) { | 
					
						
							|  |  |  |             if (!array_key_exists($col, $vals)) { | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } elseif (is_null($vals[$col])) { | 
					
						
							| 
									
										
										
										
											2015-12-31 12:41:02 +01:00
										 |  |  |                 throw new ServerException("NULL values not allowed in getByKeys for column '{$col}'"); | 
					
						
							| 
									
										
										
										
											2015-10-12 17:48:23 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             $object->$col = $vals[$col]; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (!$object->find(true)) { | 
					
						
							|  |  |  |             throw new NoResultException($object); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return $object; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 22:17:40 +02:00
										 |  |  |     static function getByID($id) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (empty($id)) { | 
					
						
							| 
									
										
										
										
											2016-01-03 22:56:48 +01:00
										 |  |  |             throw new EmptyIdException(get_called_class()); | 
					
						
							| 
									
										
										
										
											2015-06-04 22:17:40 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         // getByPK throws exception if id is null
 | 
					
						
							|  |  |  |         // or if the class does not have a single 'id' column as primary key
 | 
					
						
							|  |  |  |         return static::getByPK(array('id' => $id)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-03-06 14:21:44 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Returns an ID, checked that it is set and reasonably valid | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * If this dataobject uses a special id field (not 'id'), just | 
					
						
							|  |  |  |      * implement your ID getting method in the child class. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @return int ID of dataobject | 
					
						
							|  |  |  |      * @throws Exception (when ID is not available or not set yet) | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getID() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // FIXME: Make these exceptions more specific (their own classes)
 | 
					
						
							|  |  |  |         if (!isset($this->id)) { | 
					
						
							|  |  |  |             throw new Exception('No ID set.'); | 
					
						
							|  |  |  |         } elseif (empty($this->id)) { | 
					
						
							|  |  |  |             throw new Exception('Empty ID for object! (not inserted yet?).'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-08 12:21:46 +01:00
										 |  |  |         return intval($this->id); | 
					
						
							| 
									
										
										
										
											2014-03-06 14:21:44 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-01-25 11:58:35 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // 'update' won't write key columns, so we have to do it ourselves.
 | 
					
						
							| 
									
										
										
										
											2015-01-25 12:07:26 +01:00
										 |  |  |     // This also automatically calls "update" _before_ it sets the keys.
 | 
					
						
							| 
									
										
										
										
											2015-01-25 13:13:01 +01:00
										 |  |  |     // FIXME: This only works with single-column primary keys so far! Beware!
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * @param DB_DataObject &$orig  Must be "instanceof" $this | 
					
						
							|  |  |  |      * @param string         $pid   Primary ID column (no escaping is done on column name!) | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2016-01-28 16:42:59 +01:00
										 |  |  |     public function updateWithKeys(Managed_DataObject $orig, $pid=null) | 
					
						
							| 
									
										
										
										
											2015-01-25 11:58:35 +01:00
										 |  |  |     { | 
					
						
							|  |  |  |         if (!$orig instanceof $this) { | 
					
						
							|  |  |  |             throw new ServerException('Tried updating a DataObject with a different class than itself.'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-28 16:42:59 +01:00
										 |  |  |         if ($this->N <1) { | 
					
						
							|  |  |  |             throw new ServerException('DataObject must be the result of a query (N>=1) before updateWithKeys()'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-25 12:29:28 +01:00
										 |  |  |         // do it in a transaction
 | 
					
						
							|  |  |  |         $this->query('BEGIN'); | 
					
						
							| 
									
										
										
										
											2015-01-25 12:07:26 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-25 11:58:35 +01:00
										 |  |  |         $parts = array(); | 
					
						
							|  |  |  |         foreach ($this->keys() as $k) { | 
					
						
							|  |  |  |             if (strcmp($this->$k, $orig->$k) != 0) { | 
					
						
							|  |  |  |                 $parts[] = $k . ' = ' . $this->_quote($this->$k); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (count($parts) == 0) { | 
					
						
							| 
									
										
										
										
											2015-01-25 12:45:26 +01:00
										 |  |  |             // No changes to keys, it's safe to run ->update(...)
 | 
					
						
							|  |  |  |             if ($this->update($orig) === false) { | 
					
						
							|  |  |  |                 common_log_db_error($this, 'UPDATE', __FILE__); | 
					
						
							|  |  |  |                 // rollback as something bad occurred
 | 
					
						
							|  |  |  |                 $this->query('ROLLBACK'); | 
					
						
							| 
									
										
										
										
											2015-06-06 19:35:10 +02:00
										 |  |  |                 throw new ServerException("Could not UPDATE non-keys for {$this->tableName()}"); | 
					
						
							| 
									
										
										
										
											2015-01-25 12:45:26 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2015-01-25 13:13:01 +01:00
										 |  |  |             $orig->decache(); | 
					
						
							|  |  |  |             $this->encache(); | 
					
						
							| 
									
										
										
										
											2015-02-08 15:17:50 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             // commit our db transaction since we won't reach the COMMIT below
 | 
					
						
							|  |  |  |             $this->query('COMMIT'); | 
					
						
							| 
									
										
										
										
											2015-02-08 15:33:00 +01:00
										 |  |  |             // @FIXME return true only if something changed (otherwise 0)
 | 
					
						
							| 
									
										
										
										
											2015-01-25 11:58:35 +01:00
										 |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-01-28 16:42:59 +01:00
										 |  |  |         if ($pid === null) { | 
					
						
							|  |  |  |             $schema = static::schemaDef(); | 
					
						
							|  |  |  |             $pid = $schema['primary key']; | 
					
						
							|  |  |  |             unset($schema); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         $pidWhere = array(); | 
					
						
							|  |  |  |         foreach((array)$pid as $pidCol) {  | 
					
						
							|  |  |  |             $pidWhere[] = sprintf('%1$s = %2$s', $pidCol, $this->_quote($orig->$pidCol)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (empty($pidWhere)) { | 
					
						
							|  |  |  |             throw new ServerException('No primary ID column(s) set for updateWithKeys'); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $qry = sprintf('UPDATE %1$s SET %2$s WHERE %3$s', | 
					
						
							| 
									
										
										
										
											2015-01-25 13:13:01 +01:00
										 |  |  |                             common_database_tablename($this->tableName()), | 
					
						
							|  |  |  |                             implode(', ', $parts), | 
					
						
							| 
									
										
										
										
											2016-01-28 16:42:59 +01:00
										 |  |  |                             implode(' AND ', $pidWhere)); | 
					
						
							| 
									
										
										
										
											2015-01-25 13:13:01 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-01-25 11:58:35 +01:00
										 |  |  |         $result = $this->query($qry); | 
					
						
							| 
									
										
										
										
											2015-01-25 12:29:28 +01:00
										 |  |  |         if ($result === false) { | 
					
						
							|  |  |  |             common_log_db_error($this, 'UPDATE', __FILE__); | 
					
						
							|  |  |  |             // rollback as something bad occurred
 | 
					
						
							|  |  |  |             $this->query('ROLLBACK'); | 
					
						
							| 
									
										
										
										
											2015-06-06 19:35:10 +02:00
										 |  |  |             throw new ServerException("Could not UPDATE key fields for {$this->tableName()}"); | 
					
						
							| 
									
										
										
										
											2015-01-25 11:58:35 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-01-25 12:29:28 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Update non-keys too, if the previous endeavour worked.
 | 
					
						
							| 
									
										
										
										
											2015-01-25 12:45:26 +01:00
										 |  |  |         // The ->update call uses "$this" values for keys, that's why we can't do this until
 | 
					
						
							|  |  |  |         // the keys are updated (because they might differ from $orig and update the wrong entries).
 | 
					
						
							| 
									
										
										
										
											2015-01-25 12:29:28 +01:00
										 |  |  |         if ($this->update($orig) === false) { | 
					
						
							|  |  |  |             common_log_db_error($this, 'UPDATE', __FILE__); | 
					
						
							|  |  |  |             // rollback as something bad occurred
 | 
					
						
							|  |  |  |             $this->query('ROLLBACK'); | 
					
						
							| 
									
										
										
										
											2015-06-06 19:35:10 +02:00
										 |  |  |             throw new ServerException("Could not UPDATE non-keys for {$this->tableName()}"); | 
					
						
							| 
									
										
										
										
											2015-01-25 12:29:28 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-01-25 13:13:01 +01:00
										 |  |  |         $orig->decache(); | 
					
						
							| 
									
										
										
										
											2015-01-25 12:29:28 +01:00
										 |  |  |         $this->encache(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // commit our db transaction
 | 
					
						
							|  |  |  |         $this->query('COMMIT'); | 
					
						
							| 
									
										
										
										
											2015-02-08 15:33:00 +01:00
										 |  |  |         // @FIXME return true only if something changed (otherwise 0)
 | 
					
						
							| 
									
										
										
										
											2015-01-25 11:58:35 +01:00
										 |  |  |         return $result; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-02-19 18:59:28 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     static public function beforeSchemaUpdate() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // NOOP
 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-10-10 21:31:26 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     static function newUri(Profile $actor, Managed_DataObject $object, $created=null) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         if (is_null($created)) { | 
					
						
							|  |  |  |             $created = common_sql_now(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return TagURI::mint(strtolower(get_called_class()).':%d:%s:%d:%s', | 
					
						
							|  |  |  |                                         $actor->getID(), | 
					
						
							|  |  |  |                                         ActivityUtils::resolveUri($object->getObjectType(), true), | 
					
						
							|  |  |  |                                         $object->getID(), | 
					
						
							|  |  |  |                                         common_date_iso8601($created)); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2016-02-10 03:37:27 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     protected function onInsert() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // NOOP by default
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     protected function onUpdate($dataObject=false) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         // NOOP by default
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function insert() | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $this->onInsert(); | 
					
						
							|  |  |  |         return parent::insert(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function update($dataObject=false) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         $this->onUpdate($dataObject); | 
					
						
							|  |  |  |         return parent::update($dataObject); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2013-08-12 19:12:13 +02:00
										 |  |  | } |