Skip to content

Commit

Permalink
Beginings of automatically converting sql functions to their right types
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzo committed Dec 1, 2015
1 parent 34d7259 commit 0485710
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 27 deletions.
10 changes: 8 additions & 2 deletions src/Database/Expression/FunctionExpression.php
Expand Up @@ -15,6 +15,8 @@
namespace Cake\Database\Expression;

use Cake\Database\ExpressionInterface;
use Cake\Database\TypedResultInterface;
use Cake\Database\TypedResultTrait;
use Cake\Database\ValueBinder;

/**
Expand All @@ -25,9 +27,11 @@
*
* @internal
*/
class FunctionExpression extends QueryExpression
class FunctionExpression extends QueryExpression implements TypedResultInterface
{

use TypedResultTrait;

/**
* The name of the function to be constructed when generating the SQL string
*
Expand Down Expand Up @@ -58,10 +62,12 @@ class FunctionExpression extends QueryExpression
* If associative the key would be used as argument when value is 'literal'
* @param array $types associative array of types to be associated with the
* passed arguments
* @param string $returnType The return type of this expression
*/
public function __construct($name, $params = [], $types = [])
public function __construct($name, $params = [], $types = [], $returnType = 'string')
{
$this->_name = $name;
$this->_returnType = $returnType;
parent::__construct($params, $types, ',');
}

Expand Down
48 changes: 26 additions & 22 deletions src/Database/FunctionsBuilder.php
Expand Up @@ -31,11 +31,12 @@ class FunctionsBuilder
* @param string $name the name of the SQL function to constructed
* @param array $params list of params to be passed to the function
* @param array $types list of types for each function param
* @param string $return The return type of the function expression
* @return FunctionExpression
*/
protected function _build($name, $params = [], $types = [])
protected function _build($name, $params = [], $types = [], $return = 'string')
{
return new FunctionExpression($name, $params, $types);
return new FunctionExpression($name, $params, $types, $return);
}

/**
Expand All @@ -45,16 +46,17 @@ protected function _build($name, $params = [], $types = [])
* @param string $name name of the function to build
* @param mixed $expression the function argument
* @param array $types list of types to bind to the arguments
* @param string $return The return type for the function
* @return FunctionExpression
*/
protected function _literalArgumentFunction($name, $expression, $types = [])
protected function _literalArgumentFunction($name, $expression, $types = [], $return = 'string')
{
if (!is_string($expression)) {
$expression = [$expression];
} else {
$expression = [$expression => 'literal'];
}
return $this->_build($name, $expression, $types);
return $this->_build($name, $expression, $types, $return);
}

/**
Expand All @@ -66,7 +68,7 @@ protected function _literalArgumentFunction($name, $expression, $types = [])
*/
public function sum($expression, $types = [])
{
return $this->_literalArgumentFunction('SUM', $expression, $types);
return $this->_literalArgumentFunction('SUM', $expression, $types, 'float');
}

/**
Expand All @@ -78,7 +80,7 @@ public function sum($expression, $types = [])
*/
public function avg($expression, $types = [])
{
return $this->_literalArgumentFunction('AVG', $expression, $types);
return $this->_literalArgumentFunction('AVG', $expression, $types, 'float');
}

/**
Expand All @@ -90,7 +92,7 @@ public function avg($expression, $types = [])
*/
public function max($expression, $types = [])
{
return $this->_literalArgumentFunction('MAX', $expression, $types);
return $this->_literalArgumentFunction('MAX', $expression, $types, current($types) ?: 'string');
}

/**
Expand All @@ -102,7 +104,7 @@ public function max($expression, $types = [])
*/
public function min($expression, $types = [])
{
return $this->_literalArgumentFunction('MIN', $expression, $types);
return $this->_literalArgumentFunction('MIN', $expression, $types, current($types) ?: 'string');
}

/**
Expand All @@ -114,7 +116,7 @@ public function min($expression, $types = [])
*/
public function count($expression, $types = [])
{
return $this->_literalArgumentFunction('COUNT', $expression, $types);
return $this->_literalArgumentFunction('COUNT', $expression, $types, 'integer');
}

/**
Expand All @@ -126,7 +128,7 @@ public function count($expression, $types = [])
*/
public function concat($args, $types = [])
{
return $this->_build('CONCAT', $args, $types);
return $this->_build('CONCAT', $args, $types, 'string');
}

/**
Expand All @@ -138,7 +140,7 @@ public function concat($args, $types = [])
*/
public function coalesce($args, $types = [])
{
return $this->_build('COALESCE', $args, $types);
return $this->_build('COALESCE', $args, $types, current($types) ?: 'string');
}

/**
Expand All @@ -151,7 +153,7 @@ public function coalesce($args, $types = [])
*/
public function dateDiff($args, $types = [])
{
return $this->_build('DATEDIFF', $args, $types);
return $this->_build('DATEDIFF', $args, $types, 'integer');
}

/**
Expand All @@ -177,7 +179,7 @@ public function datePart($part, $expression, $types = [])
*/
public function extract($part, $expression, $types = [])
{
$expression = $this->_literalArgumentFunction('EXTRACT', $expression, $types);
$expression = $this->_literalArgumentFunction('EXTRACT', $expression, $types, 'integer');
$expression->tieWith(' FROM')->add([$part => 'literal'], [], true);
return $expression;
}
Expand All @@ -197,7 +199,7 @@ public function dateAdd($expression, $value, $unit, $types = [])
$value = 0;
}
$interval = $value . ' ' . $unit;
$expression = $this->_literalArgumentFunction('DATE_ADD', $expression, $types);
$expression = $this->_literalArgumentFunction('DATE_ADD', $expression, $types, 'datetime');
$expression->tieWith(', INTERVAL')->add([$interval => 'literal']);
return $expression;
}
Expand All @@ -212,7 +214,7 @@ public function dateAdd($expression, $value, $unit, $types = [])
*/
public function dayOfWeek($expression, $types = [])
{
return $this->_literalArgumentFunction('DAYOFWEEK', $expression, $types);
return $this->_literalArgumentFunction('DAYOFWEEK', $expression, $types, 'integer');
}

/**
Expand All @@ -239,23 +241,23 @@ public function weekday($expression, $types = [])
public function now($type = 'datetime')
{
if ($type === 'datetime') {
return $this->_build('NOW');
return $this->_build('NOW')->returnType('datetime');
}
if ($type === 'date') {
return $this->_build('CURRENT_DATE');
return $this->_build('CURRENT_DATE')->returnType('date');
}
if ($type === 'time') {
return $this->_build('CURRENT_TIME');
return $this->_build('CURRENT_TIME')->returnType('time');
}
}

/**
* Magic method dispatcher to create custom SQL function calls
*
* @param string $name the SQL function name to construct
* @param array $args list with up to 2 arguments, first one being an array with
* parameters for the SQL function and second one a list of types to bind to those
* params
* @param array $args list with up to 3 arguments, first one being an array with
* parameters for the SQL function, the second one a list of types to bind to those
* params, and the third one the return type of the function
* @return \Cake\Database\Expression\FunctionExpression
*/
public function __call($name, $args)
Expand All @@ -265,8 +267,10 @@ public function __call($name, $args)
return $this->_build($name);
case 1:
return $this->_build($name, $args[0]);
default:
case 2:
return $this->_build($name, $args[0], $args[1]);
default:
return $this->_build($name, $args[0], $args[1], $args[2]);
}
}
}
24 changes: 21 additions & 3 deletions tests/TestCase/Database/FunctionsBuilderTest.php
Expand Up @@ -46,6 +46,9 @@ public function testArbitrary()
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals('MyFunc', $function->name());
$this->assertEquals('MyFunc(b)', $function->sql(new ValueBinder));

$function = $this->functions->MyFunc(['b'], ['string'], 'integer');
$this->assertEquals('integer', $function->returnType());
}

/**
Expand All @@ -58,6 +61,7 @@ public function testSum()
$function = $this->functions->sum('total');
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals('SUM(total)', $function->sql(new ValueBinder));
$this->assertEquals('float', $function->returnType());
}

/**
Expand All @@ -70,6 +74,7 @@ public function testAvg()
$function = $this->functions->avg('salary');
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals('AVG(salary)', $function->sql(new ValueBinder));
$this->assertEquals('float', $function->returnType());
}

/**
Expand All @@ -79,9 +84,10 @@ public function testAvg()
*/
public function testMAX()
{
$function = $this->functions->max('created');
$function = $this->functions->max('created', ['datetime']);
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals('MAX(created)', $function->sql(new ValueBinder));
$this->assertEquals('datetime', $function->returnType());
}

/**
Expand All @@ -91,9 +97,10 @@ public function testMAX()
*/
public function testMin()
{
$function = $this->functions->min('created');
$function = $this->functions->min('created', ['date']);
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals('MIN(created)', $function->sql(new ValueBinder));
$this->assertEquals('date', $function->returnType());
}

/**
Expand All @@ -106,6 +113,7 @@ public function testCount()
$function = $this->functions->count('*');
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals('COUNT(*)', $function->sql(new ValueBinder));
$this->assertEquals('integer', $function->returnType());
}

/**
Expand All @@ -118,6 +126,7 @@ public function testConcat()
$function = $this->functions->concat(['title' => 'literal', ' is a string']);
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals("CONCAT(title, :c0)", $function->sql(new ValueBinder));
$this->assertEquals('string', $function->returnType());
}

/**
Expand All @@ -127,9 +136,10 @@ public function testConcat()
*/
public function testCoalesce()
{
$function = $this->functions->coalesce(['NULL' => 'literal', '1', '2']);
$function = $this->functions->coalesce(['NULL' => 'literal', '1', 'a'], ['a' => 'date']);
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals("COALESCE(NULL, :c0, :c1)", $function->sql(new ValueBinder));
$this->assertEquals('date', $function->returnType());
}

/**
Expand All @@ -142,14 +152,17 @@ public function testNow()
$function = $this->functions->now();
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals("NOW()", $function->sql(new ValueBinder));
$this->assertEquals('datetime', $function->returnType());

$function = $this->functions->now('date');
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals("CURRENT_DATE()", $function->sql(new ValueBinder));
$this->assertEquals('date', $function->returnType());

$function = $this->functions->now('time');
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals("CURRENT_TIME()", $function->sql(new ValueBinder));
$this->assertEquals('time', $function->returnType());
}

/**
Expand All @@ -162,10 +175,12 @@ public function testExtract()
$function = $this->functions->extract('day', 'created');
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals("EXTRACT(day FROM created)", $function->sql(new ValueBinder));
$this->assertEquals('integer', $function->returnType());

$function = $this->functions->datePart('year', 'modified');
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals("EXTRACT(year FROM modified)", $function->sql(new ValueBinder));
$this->assertEquals('integer', $function->returnType());
}

/**
Expand All @@ -178,6 +193,7 @@ public function testDateAdd()
$function = $this->functions->dateAdd('created', -3, 'day');
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals("DATE_ADD(created, INTERVAL -3 day)", $function->sql(new ValueBinder));
$this->assertEquals('datetime', $function->returnType());
}

/**
Expand All @@ -190,9 +206,11 @@ public function testDayOfWeek()
$function = $this->functions->dayOfWeek('created');
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals("DAYOFWEEK(created)", $function->sql(new ValueBinder));
$this->assertEquals('integer', $function->returnType());

$function = $this->functions->weekday('created');
$this->assertInstanceOf('Cake\Database\Expression\FunctionExpression', $function);
$this->assertEquals("DAYOFWEEK(created)", $function->sql(new ValueBinder));
$this->assertEquals('integer', $function->returnType());
}
}

0 comments on commit 0485710

Please sign in to comment.