1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-19 05:12:41 +01:00

Add functionality to Lisk to only get some columns from the database

Summary:
Added loadColumns, loadColumnsWhere instance methods to Lisk, so when you only
need some fields of your object loaded, you can do so. This will be useful for
places where we fetch a large number of rows, but only care about a few columns.
In that situation, these functions can be used so the db doesn't have to return
as much data.

Test Plan:
Loaded a typeahead to check that the existing lisk functions still work.
Modified typeahead to fetch data using loadColumns instead of loadAll and
checked that it still works.

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, epriestley, nh, jungejason

Differential Revision: 947
This commit is contained in:
Nicholas Harper 2011-10-07 14:28:03 -07:00
parent c29982acb9
commit c3709c56fc

View file

@ -98,9 +98,7 @@
* Note that **Lisk automatically builds getters and setters for all of your * Note that **Lisk automatically builds getters and setters for all of your
* object's properties** via __call(). If you want to add custom behavior to * object's properties** via __call(). If you want to add custom behavior to
* your getters or setters, you can do so by overriding the readField and * your getters or setters, you can do so by overriding the readField and
* writeField methods. Do not implement your own setters and getters, as they * writeField methods.
* will result in an inconsistent object state (and new values won't get
* written to the database).
* *
* Calling save() will persist the object to the database. After calling * Calling save() will persist the object to the database. After calling
* save(), you can call getID() to retrieve the object's ID. * save(), you can call getID() to retrieve the object's ID.
@ -154,6 +152,7 @@ abstract class LiskDAO {
const CONFIG_TIMESTAMPS = 'timestamps'; const CONFIG_TIMESTAMPS = 'timestamps';
const CONFIG_AUX_PHID = 'auxiliary-phid'; const CONFIG_AUX_PHID = 'auxiliary-phid';
const CONFIG_SERIALIZATION = 'col-serialization'; const CONFIG_SERIALIZATION = 'col-serialization';
const CONFIG_PARTIAL_OBJECTS = 'partial-objects';
const SERIALIZATION_NONE = 'id'; const SERIALIZATION_NONE = 'id';
const SERIALIZATION_JSON = 'json'; const SERIALIZATION_JSON = 'json';
@ -164,6 +163,8 @@ abstract class LiskDAO {
const IDS_MANUAL = 'ids-manual'; const IDS_MANUAL = 'ids-manual';
private $__connections = array(); private $__connections = array();
private $__dirtyFields = array();
private $__missingFields = array();
private static $processIsolationLevel = 0; private static $processIsolationLevel = 0;
private static $__checkedClasses = array(); private static $__checkedClasses = array();
@ -178,23 +179,28 @@ abstract class LiskDAO {
$this->$id_key = null; $this->$id_key = null;
} }
$this_class = get_class($this); if ($this->getConfigOption(self::CONFIG_PARTIAL_OBJECTS)) {
if (empty(self::$__checkedClasses[$this_class])) { $this->resetDirtyFields();
self::$__checkedClasses = true;
if (PhabricatorEnv::getEnvConfig('lisk.check_property_methods')) { $this_class = get_class($this);
$class = new ReflectionClass(get_class($this)); if (empty(self::$__checkedClasses[$this_class])) {
$methods = $class->getMethods(); self::$__checkedClasses = true;
$properties = $this->getProperties();
foreach ($methods as $method) { if (PhabricatorEnv::getEnvConfig('lisk.check_property_methods')) {
$name = strtolower($method->getName()); $class = new ReflectionClass(get_class($this));
if (!(strncmp($name, 'get', 3) && strncmp($name, 'set', 3))) { $methods = $class->getMethods();
$name = substr($name, 3); $properties = $this->getProperties();
$declaring_class_name = $method->getDeclaringClass()->getName(); foreach ($methods as $method) {
if (isset($properties[$name]) && $name = strtolower($method->getName());
$declaring_class_name !== 'LiskDAO') { if (!(strncmp($name, 'get', 3) && strncmp($name, 'set', 3))) {
throw new Exception( $name = substr($name, 3);
"Cannot implement method {$method->getName()} in ". $declaring_class_name = $method->getDeclaringClass()->getName();
"{$declaring_class_name}."); if (isset($properties[$name]) &&
$declaring_class_name !== 'LiskDAO') {
throw new Exception(
"Cannot implement method {$method->getName()} in ".
"{$declaring_class_name}.");
}
} }
} }
} }
@ -268,6 +274,16 @@ abstract class LiskDAO {
* This will cause Lisk to JSON-serialize the 'complex' field before it is * This will cause Lisk to JSON-serialize the 'complex' field before it is
* written, and unserialize it when it is read. * written, and unserialize it when it is read.
* *
* CONFIG_PARTIAL_OBJECTS
* Sometimes, it is useful to load only some fields of an object (such as
* when you are loading all objects of a class, but only care about a few
* fields). Turning on this option (by setting it to a truthy value) allows
* users of the class to create/use partial objects, but it comes with some
* side effects: your class cannot override the setters and getters provided
* by Lisk (use readField and writeField instead), and you should not
* directly access or assign protected members of your class (use the getters
* and setters).
*
* *
* @return dictionary Map of configuration options to values. * @return dictionary Map of configuration options to values.
* *
@ -278,6 +294,7 @@ abstract class LiskDAO {
self::CONFIG_OPTIMISTIC_LOCKS => false, self::CONFIG_OPTIMISTIC_LOCKS => false,
self::CONFIG_IDS => self::IDS_AUTOINCREMENT, self::CONFIG_IDS => self::IDS_AUTOINCREMENT,
self::CONFIG_TIMESTAMPS => true, self::CONFIG_TIMESTAMPS => true,
self::CONFIG_PARTIAL_OBJECTS => false,
); );
} }
@ -340,6 +357,20 @@ abstract class LiskDAO {
return $this->loadAllWhere('1 = 1'); return $this->loadAllWhere('1 = 1');
} }
/**
* Loads all objects, but only fetches the specified columns.
*
* @param string Column name.
* @param ... More column names.
* @return dict Dictionary of all objects, keyed by ID.
*
* @task load
*/
public function loadColumns($column1/*, $column2, ... */) {
$columns = func_get_args();
return $this->loadColumnsWhere($columns, '1 = 1');
}
/** /**
* Load all objects which match a WHERE clause. You provide everything after * Load all objects which match a WHERE clause. You provide everything after
@ -356,6 +387,30 @@ abstract class LiskDAO {
* @task load * @task load
*/ */
public function loadAllWhere($pattern/*, $arg, $arg, $arg ... */) { public function loadAllWhere($pattern/*, $arg, $arg, $arg ... */) {
$args = func_get_args();
array_unshift($args, null);
$data = call_user_func_array(
array($this, 'loadRawDataWhere'),
$args);
return $this->loadAllFromArray($data);
}
/**
* Loads selected columns from objects that match a WHERE clause. You must
* provide everything after the WHERE. See loadAllWhere().
*
* @param array List of column names.
* @param string queryfx()-style SQL WHERE clause.
* @param ... Zero or more conversions.
* @return dict Dictionary of matching objecks, keyed by ID.
*
* @task load
*/
public function loadColumnsWhere($columns, $pattern/*, $arg, $arg, ... */) {
if (!$this->getConfigOption(self::CONFIG_PARTIAL_OBJECTS)) {
throw new BadMethodCallException(
"This class does not support partial objects.");
}
$args = func_get_args(); $args = func_get_args();
$data = call_user_func_array( $data = call_user_func_array(
array($this, 'loadRawDataWhere'), array($this, 'loadRawDataWhere'),
@ -378,6 +433,7 @@ abstract class LiskDAO {
*/ */
public function loadOneWhere($pattern/*, $arg, $arg, $arg ... */) { public function loadOneWhere($pattern/*, $arg, $arg, $arg ... */) {
$args = func_get_args(); $args = func_get_args();
array_unshift($args, null);
$data = call_user_func_array( $data = call_user_func_array(
array($this, 'loadRawDataWhere'), array($this, 'loadRawDataWhere'),
$args); $args);
@ -396,7 +452,7 @@ abstract class LiskDAO {
} }
protected function loadRawDataWhere($pattern/*, $arg, $arg, $arg ... */) { protected function loadRawDataWhere($columns, $pattern/*, $arg, $arg ... */) {
$connection = $this->establishConnection('r'); $connection = $this->establishConnection('r');
$lock_clause = ''; $lock_clause = '';
@ -407,10 +463,25 @@ abstract class LiskDAO {
} }
$args = func_get_args(); $args = func_get_args();
$args = array_slice($args, 1); $args = array_slice($args, 2);
$pattern = 'SELECT * FROM %T WHERE '.$pattern.' %Q'; if (!$columns) {
$column = '*';
} else {
$column = '%LC';
$columns[] = $this->getIDKey();
$properties = $this->getProperties();
$this->__missingFields = array_diff_key(
array_flip($properties),
array_flip($columns));
}
$pattern = 'SELECT '.$column.' FROM %T WHERE '.$pattern.' %Q';
array_unshift($args, $this->getTableName()); array_unshift($args, $this->getTableName());
if ($columns) {
array_unshift($args, $columns);
}
array_push($args, $lock_clause); array_push($args, $lock_clause);
array_unshift($args, $pattern); array_unshift($args, $pattern);
@ -733,6 +804,9 @@ abstract class LiskDAO {
$this->willSaveObject(); $this->willSaveObject();
$data = $this->getPropertyValues(); $data = $this->getPropertyValues();
if ($this->getConfigOption(self::CONFIG_PARTIAL_OBJECTS)) {
$data = array_intersect_key($data, $this->__dirtyFields);
}
$this->willWriteData($data); $this->willWriteData($data);
$map = array(); $map = array();
@ -778,6 +852,10 @@ abstract class LiskDAO {
$this->didWriteData(); $this->didWriteData();
if ($this->getConfigOption(self::CONFIG_PARTIAL_OBJECTS)) {
$this->resetDirtyFields();
}
return $this; return $this;
} }
@ -874,6 +952,10 @@ abstract class LiskDAO {
$this->didWriteData(); $this->didWriteData();
if ($this->getConfigOption(self::CONFIG_PARTIAL_OBJECTS)) {
$this->resetDirtyFields();
}
return $this; return $this;
} }
@ -1154,6 +1236,19 @@ abstract class LiskDAO {
} }
} }
/**
* Resets the dirty fields (fields which need to be written on next save/
* update/insert/replace). If this DAO has timestamps, the modified time
* is always a dirty field.
*
* @task util
*/
private function resetDirtyFields() {
$this->__dirtyFields = array();
if ($this->getConfigOption(self::CONFIG_TIMESTAMPS)) {
$this->__dirtyFields['dateModified'] = true;
}
}
/** /**
* Black magic. Builds implied get*() and set*() for all properties. * Black magic. Builds implied get*() and set*() for all properties.
@ -1173,6 +1268,10 @@ abstract class LiskDAO {
if (count($args) !== 0) { if (count($args) !== 0) {
throw new Exception("Getter call should have zero args: {$method}"); throw new Exception("Getter call should have zero args: {$method}");
} }
if ($this->getConfigOption(self::CONFIG_PARTIAL_OBJECTS) &&
isset($this->__missingFields[$property])) {
throw new Exception("Cannot get field that wasn't loaded: {$property}");
}
return $this->readField($property); return $this->readField($property);
} }
@ -1188,6 +1287,11 @@ abstract class LiskDAO {
if ($property == 'ID') { if ($property == 'ID') {
$property = $this->getIDKeyForUse(); $property = $this->getIDKeyForUse();
} }
if ($this->getConfigOption(self::CONFIG_PARTIAL_OBJECTS)) {
// Accept writes to fields that weren't initially loaded
unset($this->__missingFields[$property]);
$this->__dirtyFields[$property] = true;
}
$this->writeField($property, $args[0]); $this->writeField($property, $args[0]);
return $this; return $this;
} }