Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connect to test DB lazily from TestCase #1032

Merged
merged 7 commits into from
Jul 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/Persistence/Sql/Expression.php
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,9 @@ final public static function castFloatToString(float $value): string
$precisionBackup = ini_get('precision');
ini_set('precision', '-1');
try {
return (string) $value;
$valueStr = (string) $value;

return $valueStr === (string) (int) $value ? $valueStr . '.0' : $valueStr;
} finally {
ini_set('precision', $precisionBackup);
}
Expand All @@ -696,7 +698,13 @@ private function castGetValue($v): ?string
} elseif (is_int($v)) {
return (string) $v;
} elseif (is_float($v)) {
return self::castFloatToString($v);
$res = self::castFloatToString($v);
// most DB drivers fetch float as string
if (str_ends_with($res, '.0')) {
$res = substr($res, 0, -2);
}

return $res;
}

// for PostgreSQL/Oracle CLOB/BLOB datatypes and PDO driver
Expand Down
2 changes: 1 addition & 1 deletion src/Persistence/Sql/Oracle/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Connection extends BaseConnection

protected static function createDbalEventManager(): EventManager
{
$evm = new EventManager();
$evm = parent::createDbalEventManager();

// setup connection globalization to use standard datetime format incl. microseconds support
// and make comparison of character types case insensitive
Expand Down
24 changes: 24 additions & 0 deletions src/Persistence/Sql/Sqlite/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,32 @@
namespace Atk4\Data\Persistence\Sql\Sqlite;

use Atk4\Data\Persistence\Sql\Connection as BaseConnection;
use Doctrine\Common\EventManager;
use Doctrine\Common\EventSubscriber;
use Doctrine\DBAL\Event\ConnectionEventArgs;
use Doctrine\DBAL\Events;

class Connection extends BaseConnection
{
protected $queryClass = Query::class;

protected static function createDbalEventManager(): EventManager
{
$evm = parent::createDbalEventManager();

// setup connection to always check foreign keys
$evm->addEventSubscriber(new class() implements EventSubscriber {
public function getSubscribedEvents(): array
{
return [Events::postConnect];
}

public function postConnect(ConnectionEventArgs $args): void
{
$args->getConnection()->executeStatement('PRAGMA foreign_keys = 1');
}
});

return $evm;
}
}
107 changes: 52 additions & 55 deletions src/Schema/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
use Atk4\Core\Phpunit\TestCase as BaseTestCase;
use Atk4\Data\Model;
use Atk4\Data\Persistence;
use Doctrine\DBAL\Logging\SQLLogger;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
Expand All @@ -25,64 +23,25 @@ abstract class TestCase extends BaseTestCase
/** @var Migrator[] */
private $createdMigrators = [];

protected function setUp(): void
/**
* @return static|null
*/
public static function getTestFromBacktrace()
{
parent::setUp();

$this->db = Persistence::connect($_ENV['DB_DSN'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD']);

if ($this->getDatabasePlatform() instanceof SqlitePlatform) {
$this->getConnection()->expr(
'PRAGMA foreign_keys = 1'
)->executeStatement();
}
if ($this->getDatabasePlatform() instanceof MySQLPlatform) {
$this->getConnection()->expr(
'SET SESSION auto_increment_increment = 1, SESSION auto_increment_offset = 1'
)->executeStatement();
foreach (debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT) as $frame) {
if (($frame['object'] ?? null) instanceof static) {
return $frame['object']; // @phpstan-ignore-line https://github.com/phpstan/phpstan/issues/7639
}
}

$this->getConnection()->getConnection()->getConfiguration()->setSQLLogger(
null ?? new class($this) implements SQLLogger { // @phpstan-ignore-line
/** @var \WeakReference<TestCase> */
private $testCaseWeakRef;

public function __construct(TestCase $testCase)
{
$this->testCaseWeakRef = \WeakReference::create($testCase);
}

public function startQuery($sql, array $params = null, array $types = null): void
{
if (!$this->testCaseWeakRef->get()->debug) {
return;
}
return null;
}

echo "\n" . $sql . (substr($sql, -1) !== ';' ? ';' : '') . "\n"
. (is_array($params) && count($params) > 0 ? substr(print_r(array_map(function ($v) {
if ($v === null) {
$v = 'null';
} elseif (is_bool($v)) {
$v = $v ? 'true' : 'false';
} elseif (is_float($v) && (string) $v === (string) (int) $v) {
$v = $v . '.0';
} elseif (is_string($v)) {
if (strlen($v) > 4096) {
$v = '*long string* (length: ' . strlen($v) . ' bytes, sha256: ' . hash('sha256', $v) . ')';
} else {
$v = '\'' . $v . '\'';
}
}

return $v;
}, $params), true), 6) : '') . "\n";
}
protected function setUp(): void
{
parent::setUp();

public function stopQuery(): void
{
}
}
);
$this->db = new TestSqlPersistence();
}

protected function tearDown(): void
Expand Down Expand Up @@ -113,6 +72,38 @@ protected function getDatabasePlatform(): AbstractPlatform
return $this->getConnection()->getDatabasePlatform();
}

protected function logQuery(string $sql, array $params, array $types): void
{
if (!$this->debug) {
return;
}

$lines = [$sql . (substr($sql, -1) !== ';' ? ';' : '')];
if (count($params) > 0) {
$lines[] = '/*';
foreach ($params as $k => $v) {
if ($v === null) {
$vStr = 'null';
} elseif (is_bool($v)) {
$vStr = $v ? 'true' : 'false';
} elseif (is_int($v)) {
$vStr = $v;
} else {
if (strlen($v) > 4096) {
$vStr = '*long string* (length: ' . strlen($v) . ' bytes, sha256: ' . hash('sha256', $v) . ')';
} else {
$vStr = '\'' . str_replace('\'', '\'\'', $v) . '\'';
}
}

$lines[] = ' [' . $k . '] => ' . $vStr;
}
$lines[] = '*/';
}

echo "\n" . implode("\n", $lines) . "\n\n";
}

private function convertSqlFromSqlite(string $sql): string
{
$platform = $this->getDatabasePlatform();
Expand All @@ -126,6 +117,12 @@ function ($matches) use ($platform) {

$str = substr(preg_replace('~\\\\(.)~s', '$1', $matches[0]), 1, -1);
if (substr($matches[0], 0, 1) === '"') {
// keep info queries from DBAL in double quotes
// https://github.com/doctrine/dbal/blob/3.3.7/src/Connection.php#L1298
if (in_array($str, ['START TRANSACTION', 'COMMIT', 'ROLLBACK'], true)) {
return $matches[0];
}

return $platform->quoteSingleIdentifier($str);
}

Expand Down
54 changes: 54 additions & 0 deletions src/Schema/TestSqlPersistence.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace Atk4\Data\Schema;

use Atk4\Data\Persistence;
use Doctrine\DBAL\Logging\SQLLogger;
use Doctrine\DBAL\Platforms\MySQLPlatform;

/**
* SQL persistence with lazy connect and SQL logger.
*
* @internal
*/
final class TestSqlPersistence extends Persistence\Sql
{
public function __construct()
{
}

public function getConnection(): Persistence\Sql\Connection
{
\Closure::bind(function () {
if ($this->_connection === null) {
$connection = Persistence::connect($_ENV['DB_DSN'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'])->_connection; // @phpstan-ignore-line
$this->_connection = $connection;

if ($connection->getDatabasePlatform() instanceof MySQLPlatform) {
$connection->expr(
'SET SESSION auto_increment_increment = 1, SESSION auto_increment_offset = 1'
)->executeStatement();
}

$connection->getConnection()->getConfiguration()->setSQLLogger(
// @phpstan-ignore-next-line SQLLogger is deprecated
null ?? new class() implements SQLLogger {
public function startQuery($sql, array $params = null, array $types = null): void
{
$test = TestCase::getTestFromBacktrace();
\Closure::bind(fn () => $test->logQuery($sql, $params ?? [], $types ?? []), null, TestCase::class)();
}

public function stopQuery(): void
{
}
}
);
}
}, $this, Persistence\Sql::class)();

return parent::getConnection();
}
}
9 changes: 4 additions & 5 deletions src/Util/DeepCopy.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,16 +289,15 @@ protected function _copy(Model $source, Model $destination, array $references, a
return $destination;
} catch (DeepCopyException $e) {
throw $e;
} catch (\Atk4\Core\Exception $e) {
$this->debug('noticed a problem');
} catch (\Exception $e) {
$this->debug('model copy failed');

throw (new DeepCopyException('Problem cloning model', 0, $e))
throw (new DeepCopyException('Model copy failed', 0, $e))
->addMoreInfo('source', $source)
->addMoreInfo('source_info', $source->__debugInfo())
->addMoreInfo('source_data', $source->get())
->addMoreInfo('destination', $destination)
->addMoreInfo('destination_info', $destination->__debugInfo())
->addMoreInfo('depth', $e->getParams()['field'] ?? '?');
->addMoreInfo('destination_info', $destination->__debugInfo());
}
}
}
2 changes: 1 addition & 1 deletion tests/BusinessModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ public function testExampleFromDoc(): void
$this->assertSame(1000, $m->get('salary'));
$this->assertFalse($m->_isset('salary'));

// Next we load record from $db
// next we load record from $db
$dataRef = &$m->getDataRef();
$dataRef = ['salary' => 2000];
$this->assertSame(2000, $m->get('salary'));
Expand Down
6 changes: 3 additions & 3 deletions tests/ConditionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
namespace Atk4\Data\Tests;

use Atk4\Core\Phpunit\TestCase;
use Atk4\Data\Exception;
use Atk4\Data\Model;

class ConditionTest extends TestCase
{
public function testException1(): void
public function testUnexistingFieldException(): void
{
// not existing field in condition
$m = new Model();
$m->addField('name');

$this->expectException(\Atk4\Core\Exception::class);
$this->expectException(Exception::class);
$m->addCondition('last_name', 'Smith');
}

Expand Down
21 changes: 10 additions & 11 deletions tests/ReferenceSqlTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -491,10 +491,7 @@ public function testReferenceHook(): void
$this->assertSame('Peters new contact', $uu->get('address'));
}

/**
* Tests hasOne::our_key == owner::id_field.
*/
public function testIdFieldReferenceOurFieldCase(): void
public function testHasOneIdFieldAsOurField(): void
{
$this->setDb([
'player' => [
Expand All @@ -508,18 +505,20 @@ public function testIdFieldReferenceOurFieldCase(): void
],
]);

$p = (new Model($this->db, ['table' => 'player']))->addFields(['name']);

$s = (new Model($this->db, ['table' => 'stadium']));
$s->addFields(['name']);
$s->hasOne('player_id', ['model' => $p]);
$s->addField('name');
$s->addField('player_id', ['type' => 'integer']);

$p = new Model($this->db, ['table' => 'player']);
$p->addField('name');
$p->delete(2);
$p->hasOne('Stadium', ['model' => $s, 'our_field' => 'id', 'their_field' => 'player_id']);

$p = $p->load(2);
$p->ref('Stadium')->getModel()->import([['name' => 'Nou camp nou']]);
$s = $p->ref('Stadium')->createEntity()->save(['name' => 'Nou camp nou', 'player_id' => 4]);
$p = $p->createEntity()->save(['name' => 'Ivan']);

$this->assertSame('Nou camp nou', $p->ref('Stadium')->get('name'));
$this->assertSame(2, $p->ref('Stadium')->get('player_id'));
$this->assertSame(4, $p->ref('Stadium')->get('player_id'));
}

public function testModelProperty(): void
Expand Down
Loading