Skip to content

Commit 21f370b

Browse files
Mark Horningerspam-n-eggs
authored andcommitted
Additional Methods Injection (#9)
* Added Timestampdiff method. * Added UTC_TIMESTAMP * Added time_to_sec function * Got all additional functionality working with additional test. * Minor Refactor
1 parent 7246afc commit 21f370b

File tree

11 files changed

+300
-29
lines changed

11 files changed

+300
-29
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,5 @@
4646

4747
# Embedded web-server pid file
4848
/.web-server-pid
49+
test/_reports
50+
.phplint-cache

phpcs.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?xml version="1.0"?>
22
<ruleset>
3+
<file>.</file>
4+
<exclude-pattern>./vendor/*</exclude-pattern>
35
<!-- 2. General -->
46
<rule ref="Generic.NamingConventions.UpperCaseConstantName"/>
57
<!-- <rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/> -->
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
namespace Mhorninger\MySQLite;
3+
4+
class Constants
5+
{
6+
const FRAC_SECOND = 'Tv';
7+
const SECOND = 'Ts';
8+
const MINUTE = 'Ti';
9+
const HOUR = 'TG';
10+
const DAY = 'j';
11+
const WEEK = 'W';
12+
const MONTH = 'n';
13+
const YEAR = 'y';
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
namespace Mhorninger\MySQLite;
3+
4+
class MethodSubstitutionConstants
5+
{
6+
const UTC_TIMESTAMP = 'UTC_TIMESTAMP()';
7+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Mhorninger\MySQLite\MySQL;
4+
5+
use DateTime;
6+
use Mhorninger\MySQLite\Constants;
7+
8+
trait DateTimeExtended
9+
{
10+
// phpcs:disable
11+
public static function mysql_timestampdiff($timeUnit, $startTimeStamp, $endTimeStamp)
12+
{
13+
//phpcs:enable
14+
if($startTimeStamp != null && is_numeric($startTimeStamp) && $endTimeStamp != null && is_numeric($endTimeStamp)) {
15+
$differenceInt = $endTimeStamp - $startTimeStamp;
16+
if ($timeUnit == Constants::SECOND || $timeUnit = Constants::FRAC_SECOND) {
17+
return $differenceInt;
18+
}
19+
$difference = new DateTime();
20+
$difference->setTimestamp($differenceInt);
21+
return $difference->format("P$timeUnit");
22+
}
23+
return null;
24+
}
25+
26+
//phpcs:disable
27+
public static function mysql_utc_timestamp()
28+
{
29+
//phpcs:enable
30+
$now = new DateTime();
31+
return $now->getTimestamp();
32+
}
33+
34+
//phpcs:disable
35+
public static function mysql_time_to_sec($timeExpression)
36+
{
37+
//phpcs:enable
38+
if ($timeExpression != null) {
39+
if (is_numeric($timeExpression)) {
40+
return $timeExpression;
41+
}
42+
$time = new DateTime($timeExpression);
43+
//Convert to the year zero according to Unix Timestamps.
44+
$time->setDate(1970, 1, 1);
45+
return $time->getTimestamp();
46+
}
47+
return null;
48+
}
49+
}
Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,89 @@
11
<?php
22
namespace Mhorninger\MySQLite;
33

4+
use \PDO;
5+
use \Mhorninger\MySQLite\MySQL\DateTimeExtended;
6+
use \Vectorface\MySQLite\MySQL\Aggregate;
7+
use \Vectorface\MySQLite\MySQL\Comparison;
8+
use \Vectorface\MySQLite\MySQL\DateTime;
9+
use \Vectorface\MySQLite\MySQL\Flow;
10+
use \Vectorface\MySQLite\MySQL\Numeric;
11+
use \Vectorface\MySQLite\MySQL\StringFn;
12+
13+
use ReflectionClass;
14+
use ReflectionMethod;
15+
16+
/**
17+
* MySQLite is the extension Vectorface's MySQLite extension.
18+
* @see \Vectorface\MySQLite\MySQLite
19+
*/
420
class MySQLite extends \Vectorface\MySQLite\MySQLite
521
{
22+
use DateTimeExtended;
23+
24+
/**
25+
* Get information about functions that are meant to be exposed by this class.
26+
*
27+
* @return int[] An associative array composed of function names mapping to accepted parameter counts.
28+
*/
29+
protected static function getPublicMethodData()
30+
{
31+
$data = [];
32+
33+
$ref = new ReflectionClass(__CLASS__);
34+
$methods = $ref->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_STATIC);
35+
foreach ($methods as $method) {
36+
if (strpos($method->name, 'mysql_') !== 0) {
37+
continue;
38+
}
39+
40+
$data[$method->name] = $method->getNumberOfRequiredParameters();
41+
}
42+
43+
return $data;
44+
}
45+
46+
/**
47+
* Add MySQLite compatibility functions to a PDO object.
48+
*
49+
* @param \PDO &$pdo A PDO instance to which the MySQLite compatibility functions should be added.
50+
* @param string[] $fnList A list of functions to create on the SQLite database. (Omit to create all.)
51+
* @return \PDO Returns a reference to the PDO instance passed in to the function.
52+
*/
53+
public static function &createFunctions(\PDO &$pdo, array $fnList = null)
54+
{
55+
if ($pdo->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'sqlite') {
56+
throw new InvalidArgumentException('Expecting a PDO instance using the SQLite driver');
57+
}
58+
59+
foreach (static::getPublicMethodData() as $method => $paramCount) {
60+
static::registerMethod($pdo, $method, $paramCount, $fnList);
61+
}
62+
63+
return $pdo;
64+
}
65+
66+
/**
67+
* Register a method as an SQLite funtion
68+
*
69+
* @param PDO &$pdo A PDO instance to which the MySQLite compatibility functions should be added.
70+
* @param string $method The internal method name.
71+
* @param int $paramCount The suggested parameter count.
72+
* @param string[] $fnList A list of functions to create on the SQLite database, or empty for all.
73+
* @return bool Returns true if the method was registed. False otherwise.
74+
*/
75+
protected static function registerMethod(\PDO &$pdo, $method, $paramCount, array $fnList = null)
76+
{
77+
$function = substr($method, 6); /* Strip 'mysql_' prefix to get the function name. */
78+
79+
/* Skip functions not in the list. */
80+
if (!empty($fnList) && !in_array($function, $fnList)) {
81+
return false;
82+
}
683

7-
}
84+
if ($paramCount) {
85+
return $pdo->sqliteCreateFunction($function, [__CLASS__, $method], $paramCount);
86+
}
87+
return $pdo->sqliteCreateFunction($function, [__CLASS__, $method]);
88+
}
89+
}

src/Mhorninger/SQLite/Connection.php

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
namespace Mhorninger\SQLite;
3+
4+
use \ReflectionClass;
5+
use Mhorninger\MySQLite\MySQLite;
6+
use Mhorninger\MySQLite\Constants;
7+
use Mhorninger\MySQLite\MethodSubstitutionConstants;
8+
9+
class MySQLiteConnection extends \Illuminate\Database\SQLiteConnection
10+
{
11+
const ESCAPE_CHARS = ['`', '[', '"'];
12+
/**
13+
* Create a new database connection instance.
14+
*
15+
* @param \PDO|\Closure $pdo
16+
* @param string $database
17+
* @param string $tablePrefix
18+
* @param array $config
19+
* @return void
20+
*/
21+
public function __construct($pdo, $database = '', $tablePrefix = '', array $config = [])
22+
{
23+
parent::__construct($pdo, $database, $tablePrefix, $config);
24+
//Make sure the PDO is actually a PDO and not a closure.
25+
$this->pdo = $this->getPdo();
26+
$this->pdo = MySQLite::createFunctions($this->pdo);
27+
}
28+
29+
public function run($query, $bindings, \Closure $callback)
30+
{
31+
$query = $this->scanQueryForConstants($query);
32+
return parent::run($query, $bindings, $callback);
33+
}
34+
35+
private function scanQueryForConstants($query)
36+
{
37+
$reflection = new ReflectionClass(Constants::class);
38+
$constants = $reflection->getConstants();
39+
$placeholders = array_keys($constants);
40+
foreach ($placeholders as $placeholder) {
41+
$query = str_replace($placeholder, "'" . $constants[$placeholder] . "'", $query);
42+
}
43+
$reflection = new ReflectionClass(MethodSubstitutionConstants::class);
44+
$methodConstants = $reflection->getConstants();
45+
$placeholders = array_keys($methodConstants);
46+
foreach ($placeholders as $placeholder) {
47+
$query = str_replace($placeholder, $methodConstants[$placeholder], $query);
48+
}
49+
50+
return $query;
51+
}
52+
}

src/Mhorninger/SQLite/MySQLiteServiceProvider.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function boot()
2626
public function register()
2727
{
2828
Connection::resolverFor('sqlite', function ($connection, $database, $prefix, $config) {
29-
return new Connection($connection, $database, $prefix, $config);
29+
return new MySQLiteConnection($connection, $database, $prefix, $config);
3030
});
3131
}
3232
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
namespace Mhorninger\MySQLite;
3+
4+
use PDO;
5+
use DateTime;
6+
use DateInterval;
7+
use PHPUnit\Framework\TestCase;
8+
use Mhorninger\SQLite\MySQLiteConnection;
9+
10+
class InjectedMethodTest extends TestCase
11+
{
12+
private $conn = null;
13+
public function setUp()
14+
{
15+
//The PDO is not necessary to have right now, so we're not going to define it.
16+
$pdo = new PDO("sqlite::memory:", null, null, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
17+
18+
//Set up the connection.
19+
$this->conn = new MySQLiteConnection($pdo);
20+
}
21+
22+
public function testMysqlTimestampDiffSecond()
23+
{
24+
$now = new DateTime();
25+
$plusOneSecond = clone $now;
26+
$plusOneSecond->add(new DateInterval("PT1S"));
27+
$nowTimestamp = $now->getTimestamp();
28+
$plusOneSecondTimestamp = $plusOneSecond->getTimeStamp();
29+
$query = "select TIMESTAMPDIFF(SECOND, $nowTimestamp, $plusOneSecondTimestamp) AS value";
30+
$result = $this->conn->selectOne($query);
31+
$this->assertEquals(1, $result->value);
32+
}
33+
34+
public function testGetUTCTimestamp()
35+
{
36+
$query = "SELECT UTC_TIMESTAMP as value";
37+
$result = $this->conn->selectOne($query);
38+
$now = new DateTime();
39+
$expected = $now->getTimestamp();
40+
$this->assertEqualsWithDelta($expected, $result->value, 1);
41+
}
42+
43+
public function testTimeToSec()
44+
{
45+
//Queries taken directly from MySQL Documentation.
46+
$query = "SELECT TIME_TO_SEC('22:23:00') as value";
47+
$result = $this->conn->selectOne($query);
48+
$expected = 80580;
49+
$this->assertEquals($expected, $result->value);
50+
$query = "SELECT TIME_TO_SEC('00:39:38') as value";
51+
$result = $this->conn->selectOne($query);
52+
$expected = 2378;
53+
$this->assertEquals($expected, $result->value);
54+
}
55+
56+
public function testTimeToSecWithBadData()
57+
{
58+
//I have seen this happen and was absolutely perplexed as to how we got there.
59+
$query = "SELECT TIME_TO_SEC(5) as value";
60+
$result = $this->conn->selectOne($query);
61+
$expected = 5;
62+
$this->assertEquals($expected, $result->value);
63+
}
64+
65+
public function testTimeToSecWithNullData()
66+
{
67+
//I have seen this happen and was absolutely perplexed as to how we got there.
68+
$query = "SELECT TIME_TO_SEC(NULL) as value";
69+
$result = $this->conn->selectOne($query);
70+
$this->assertNull($result->value);
71+
}
72+
}

0 commit comments

Comments
 (0)