Skip to content
Permalink
Browse files

Batch casting of fields in database results

This adds a new `BatchCastingInterface` to type classes. The idea
is that converting fields in a batch is sometimes faster than doing
them one by one.

I also changed the code of some of the casting functions in the type
classes to avoid waste and use faster primitives.

After all these changes are applied, you can expect the process of casting
fields to be around 10 percent faster than before. It is less than I
was expecting, but it is something, at least for wide tables.
  • Loading branch information...
lorenzo committed Feb 24, 2018
1 parent d118c15 commit 876e70265fe0e6028ef14318a6bca68966b94768
@@ -14,6 +14,7 @@
*/
namespace Cake\Database;
use Cake\Database\Type\BatchCastingInterface;
use Cake\Database\Type\OptionalConvertInterface;
/**
@@ -31,6 +32,16 @@ class FieldTypeConverter
*/
protected $_typeMap;
/**
* An array containing the name of the fields and the Type objects
* each should use when converting them using batching.
*
* @var array
*/
protected $batchingTypeMap;
protected $types;
/**
* The driver object to be used in the type conversion
*
@@ -49,20 +60,50 @@ public function __construct(TypeMap $typeMap, Driver $driver)
$this->_driver = $driver;
$map = $typeMap->toArray();
$types = Type::buildAll();
$result = [];
$simpleMap = $batchingMap = [];
$simpleResult = $batchingResult = [];
foreach ($types as $k => $type) {
if ($type instanceof OptionalConvertInterface && !$type->requiresToPhpCast()) {
unset($types[$k]);
continue;
}
if ($type instanceof BatchCastingInterface) {
$batchingMap[$k] = $type;
continue;
}
$simpleMap[$k] = $type;
}
foreach ($map as $field => $type) {
if (isset($types[$type])) {
$result[$field] = $types[$type];
if (isset($simpleMap[$type])) {
$simpleResult[$field] = $simpleMap[$type];
continue;
}
if (isset($batchingMap[$type])) {
$batchingResult[$type][] = $field;
}
}
$this->_typeMap = $result;
// Using batching when there is onl a couple for the type is actually slower,
// so, let's check for that case here.
foreach ($batchingResult as $type => $fields) {
if (count($fields) > 2) {
continue;
}
foreach ($fields as $f) {
$simpleResult[$f] = $batchingMap[$type];
}
unset($batchingResult[$type]);
}
$this->types = $types;
$this->_typeMap = $simpleResult;
$this->batchingTypeMap = $batchingResult;
}
/**
@@ -74,8 +115,16 @@ public function __construct(TypeMap $typeMap, Driver $driver)
*/
public function __invoke($row)
{
foreach ($this->_typeMap as $field => $type) {
$row[$field] = $type->toPHP($row[$field], $this->_driver);
if (!empty($this->_typeMap)) {
foreach ($this->_typeMap as $field => $type) {
$row[$field] = $type->toPHP($row[$field], $this->_driver);
}
}
if (!empty($this->batchingTypeMap)) {
foreach ($this->batchingTypeMap as $t => $fields) {
$row = $this->types[$t]->manyToPHP($row, $fields, $this->_driver);
}
}
return $row;
@@ -0,0 +1,36 @@
<?php
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 3.6.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\Database\Type;
use Cake\Database\Driver;
/**
* Denotes type objects capable of converting many values from their original
* database representation to php values.
*/
interface BatchCastingInterface
{
/**
* Returns an array of the values converted to the PHP representation of
* this type.
*
* @param array $values The original array of values containing the fields to be casted
* @param array $fields The field keys to cast
* @param \Cake\Database\Driver $driver Object from which database preferences and configuration will be extracted.
* @return array
*/
public function manyToPHP(array $values, array $fields, Driver $driver);
}
@@ -17,6 +17,7 @@
use Cake\Database\Driver;
use Cake\Database\Type;
use Cake\Database\TypeInterface;
use Cake\Database\Type\BatchCastingInterface;
use InvalidArgumentException;
use PDO;
@@ -25,7 +26,7 @@
*
* Use to convert bool data between PHP and the database types.
*/
class BoolType extends Type implements TypeInterface
class BoolType extends Type implements TypeInterface, BatchCastingInterface
{
/**
* Identifier name for this type.
@@ -82,13 +83,47 @@ public function toPHP($value, Driver $driver)
if ($value === null) {
return null;
}
if (is_string($value) && !is_numeric($value)) {
if (!is_numeric($value)) {
return strtolower($value) === 'true';
}
return !empty($value);
}
/**
* {@inheritDoc}
*
* @return array
*/
public function manyToPHP(array $values, array $fields, Driver $driver)
{
foreach ($fields as $field) {
if (!isset($values[$field])) {
continue;
}
if ($values[$field] === '1') {
$values[$field] = true;
continue;
}
if ($values[$field] === '0') {
$values[$field] = false;
continue;
}
$value = $values[$field];
if (!is_numeric($value)) {
$values[$field] = strtolower($value) === 'true';
continue;
}
$values[$field] = !empty($value);
}
return $values;
}
/**
* Get the correct PDO binding type for bool data.
*
@@ -17,6 +17,7 @@
use Cake\Database\Driver;
use Cake\Database\Type;
use Cake\Database\TypeInterface;
use Cake\Database\Type\BatchCastingInterface;
use DateTimeInterface;
use Exception;
use PDO;
@@ -27,7 +28,7 @@
*
* Use to convert datetime instances to strings & back.
*/
class DateTimeType extends Type implements TypeInterface
class DateTimeType extends Type implements TypeInterface, BatchCastingInterface
{
/**
* Identifier name for this type.
@@ -50,6 +51,17 @@ class DateTimeType extends Type implements TypeInterface
*/
public static $dateTimeClass = 'Cake\I18n\Time';
/**
* Whether or not we want to override the time of the converted Time objets
* so the point to the start of the day.
*
* This is mainly there to avoid subclasses re-implement the same functionality.
*
* @var bool
*/
protected $setToDateStart = false;
/**
* String format to use for DateTime parsing
*
@@ -135,13 +147,42 @@ public function toPHP($value, Driver $driver)
return null;
}
if (strpos($value, '.') !== false) {
list($value) = explode('.', $value);
$instance = (clone $this->_datetimeInstance)->modify($value);
if ($this->setToDateStart) {
$instance = $instance->setTime(0, 0, 0);
}
$instance = clone $this->_datetimeInstance;
return $instance;
}
/**
* {@inheritDoc}
*
* @return array
*/
public function manyToPHP(array $values, array $fields, Driver $driver)
{
foreach ($fields as $field) {
if (!isset($values[$field])) {
continue;
}
if (strpos($values[$field], '0000-00-00') === 0) {
$values[$field] = null;
continue;
}
$instance = (clone $this->_datetimeInstance)->modify($values[$field]);
if ($this->setToDateStart) {
$instance = $instance->setTime(0, 0, 0);
}
$values[$field] = $instance;
}
return $instance->modify($value);
return $values;
}
/**
@@ -38,6 +38,14 @@ class DateType extends DateTimeType
*/
protected $_format = 'Y-m-d';
/**
* In this class we want Date objects to have their time
* set to the beginning of the day.
*
* @var bool
*/
protected $setToDateStart = true;
/**
* Change the preferred class name to the FrozenDate implementation.
*
@@ -78,23 +86,6 @@ public function marshal($value)
return $date;
}
/**
* Convert strings into Date instances.
*
* @param string $value The value to convert.
* @param \Cake\Database\Driver $driver The driver instance to convert with.
* @return \Cake\I18n\Date|\DateTime
*/
public function toPHP($value, Driver $driver)
{
$date = parent::toPHP($value, $driver);
if ($date instanceof DateTime) {
$date->setTime(0, 0, 0);
}
return $date;
}
/**
* {@inheritDoc}
*/
@@ -17,6 +17,7 @@
use Cake\Database\Driver;
use Cake\Database\Type;
use Cake\Database\TypeInterface;
use Cake\Database\Type\BatchCastingInterface;
use InvalidArgumentException;
use PDO;
use RuntimeException;
@@ -26,7 +27,7 @@
*
* Use to convert decimal data between PHP and the database types.
*/
class DecimalType extends Type implements TypeInterface
class DecimalType extends Type implements TypeInterface, BatchCastingInterface
{
/**
* Identifier name for this type.
@@ -90,7 +91,7 @@ public function toDatabase($value, Driver $driver)
}
/**
* Convert float values to PHP integers
* Convert float values to PHP floats
*
* @param null|string|resource $value The value to convert.
* @param \Cake\Database\Driver $driver The driver instance to convert with.
@@ -100,10 +101,34 @@ public function toDatabase($value, Driver $driver)
public function toPHP($value, Driver $driver)
{
if ($value === null) {
return null;
return $value;
}
// Using coersion is faster than casting
// @codingStandardsIgnoreStart
return (float)+$value;
// @codingStandardsIgnoreEnd
}
/**
* {@inheritDoc}
*
* @return array
*/
public function manyToPHP(array $values, array $fields, Driver $driver)
{
foreach ($fields as $field) {
if (!isset($values[$field])) {
continue;
}
// Using coersion is faster than casting
// @codingStandardsIgnoreStart
$values[$field] = (float)+$values[$field];
// @codingStandardsIgnoreEnd
}
return (float)$value;
return $values;
}
/**
Oops, something went wrong.

0 comments on commit 876e702

Please sign in to comment.
You can’t perform that action at this time.