Skip to content
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
9 changes: 9 additions & 0 deletions src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,15 @@ public function getDatatype(DataType $type): string
return $this->dbDriver->getDatatype($type);
}

/**
* @inheritDoc
*/
#[Override]
public function mapsToSameDatatype(DataType $a, DataType $b): bool
{
return $this->getDatatype($a) === $this->getDatatype($b);
}

/**
* @inheritDoc
* @throws ConnectionException
Expand Down
11 changes: 11 additions & 0 deletions src/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,17 @@ public function getTableInformation(string $tableName): Table;
*/
public function getDatatype(DataType $type): string;

/**
* Returns true when both datatypes resolve to the same backend SQL via
* {@see self::getDatatype()}.
*
* Useful for schema-drift checks: e.g. on Postgres, TINYINT and SMALLINT
* both map to "SMALLINT", and on SQLite all integer types collapse to
* "INTEGER". A naive enum comparison would falsely report drift after a
* round-trip through such a backend.
*/
public function mapsToSameDatatype(DataType $a, DataType $b): bool;

/**
* Used to send a `CREATE TABLE` statement to the database.
* By passing the query through this method, the driver can add db-specific commands.
Expand Down
10 changes: 10 additions & 0 deletions src/Driver/MysqliDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,14 @@ public function getTableInformation(string $tableName): Table
*/
private function getCoreTypeForDbType(array $infoSchemaRow): ?DataType
{
if ($infoSchemaRow['Type'] === 'tinyint(4)' || $infoSchemaRow['Type'] === 'tinyint') {
return DataType::TINYINT;
}

if ($infoSchemaRow['Type'] === 'smallint(6)' || $infoSchemaRow['Type'] === 'smallint') {
return DataType::SMALLINT;
}

if ($infoSchemaRow['Type'] === 'int(11)' || $infoSchemaRow['Type'] === 'int') {
return DataType::INT;
}
Expand Down Expand Up @@ -374,6 +382,8 @@ private function getCoreTypeForDbType(array $infoSchemaRow): ?DataType
public function getDatatype(DataType $type): string
{
return match ($type) {
DataType::TINYINT => ' TINYINT ',
DataType::SMALLINT => ' SMALLINT ',
DataType::INT => ' INT ',
DataType::BIGINT => ' BIGINT ',
DataType::FLOAT => ' DOUBLE ',
Expand Down
5 changes: 5 additions & 0 deletions src/Driver/PostgresDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ public function getTableInformation(string $tableName): Table
*/
private function getCoreTypeForDbType(array $infoSchemaRow): ?DataType
{
if ($infoSchemaRow['data_type'] === 'smallint') {
return DataType::SMALLINT;
}

if ($infoSchemaRow['data_type'] === 'integer') {
return DataType::INT;
}
Expand Down Expand Up @@ -341,6 +345,7 @@ private function getCoreTypeForDbType(array $infoSchemaRow): ?DataType
public function getDatatype(DataType $type): string
{
return match ($type) {
DataType::TINYINT, DataType::SMALLINT => ' SMALLINT ',
DataType::INT => ' INT ',
DataType::BIGINT => ' BIGINT ',
DataType::FLOAT => ' NUMERIC ',
Expand Down
2 changes: 1 addition & 1 deletion src/Driver/Sqlite3Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ public function dbImport(string $fileName): bool
public function getDatatype(DataType $type): string
{
return match ($type) {
DataType::INT, DataType::BIGINT => ' INTEGER ',
DataType::TINYINT, DataType::SMALLINT, DataType::INT, DataType::BIGINT => ' INTEGER ',
DataType::FLOAT => ' REAL ',
default => ' TEXT ',
};
Expand Down
6 changes: 6 additions & 0 deletions src/MockConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@ public function getDatatype(DataType $type): string
return DataType::TEXT->value;
}

#[Override]
public function mapsToSameDatatype(DataType $a, DataType $b): bool
{
return $this->getDatatype($a) === $this->getDatatype($b);
}

#[Override]
public function createTable(string $tableName, array $columns, array $keys, array $indices = []): bool
{
Expand Down
2 changes: 2 additions & 0 deletions src/Schema/DataType.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
enum DataType: string
{
case TINYINT = 'tinyint';
case SMALLINT = 'smallint';
case INT = 'int';
case BIGINT = 'long';
case FLOAT = 'double';
Expand Down
21 changes: 21 additions & 0 deletions tests/ConnectionColumnTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Artemeon\Database\Exception\ConnectionException;
use Artemeon\Database\Exception\QueryException;
use Artemeon\Database\Exception\TableNotFoundException;
use Artemeon\Database\Schema\DataType;
use PHPUnit\Framework\Attributes\DataProvider;

/**
* @internal
Expand Down Expand Up @@ -43,4 +45,23 @@ public function testTypeConversion(): void
);
}
}

/**
* @return iterable<string, array{DataType, DataType, bool}>
*/
public static function provideMapsToSameDatatypeCases(): iterable
{
yield 'INT == INT' => [DataType::INT, DataType::INT, true];
yield 'TEXT == TEXT' => [DataType::TEXT, DataType::TEXT, true];
yield 'INT vs TEXT must stay distinct on every backend' => [DataType::INT, DataType::TEXT, false];
}

#[DataProvider('provideMapsToSameDatatypeCases')]
public function testMapsToSameDatatype(DataType $a, DataType $b, bool $expected): void
{
$this->assertSame(
$expected,
$this->getConnection()->mapsToSameDatatype($a, $b),
);
}
}
2 changes: 2 additions & 0 deletions tests/ConnectionMultiInsertTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public function testInserts(): void
for ($i = 1; $i <= 50; $i++) {
$row = $connection->getPRow('SELECT * FROM ' . self::TEST_TABLE_NAME . ' WHERE temp_int = ?', [123456 + $i]);

$this->assertEquals($i % 128, $row['temp_tinyint']);
$this->assertEquals(1000 + $i, $row['temp_smallint']);
$this->assertEquals(123456 + $i, $row['temp_int']);
$this->assertEquals(20200508095300 + $i, $row['temp_bigint']);
$this->assertIsScalar($row['temp_float']);
Expand Down
6 changes: 6 additions & 0 deletions tests/ConnectionTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ protected function getTestTableColumns(): array
{
return [
'temp_id' => [DataType::CHAR20, false],
'temp_tinyint' => [DataType::TINYINT, true],
'temp_smallint' => [DataType::SMALLINT, true],
'temp_int' => [DataType::INT, true],
'temp_bigint' => [DataType::BIGINT, true],
'temp_float' => [DataType::FLOAT, true],
Expand Down Expand Up @@ -119,6 +121,8 @@ protected function getRows(int $count, bool $assoc = true): array
for ($i = 1; $i <= $count; $i++) {
$row = [
'temp_id' => $this->generateSystemid(),
'temp_tinyint' => $i % 128,
'temp_smallint' => 1000 + $i,
'temp_int' => 123456 + $i,
'temp_bigint' => 20200508095300 + $i,
'temp_float' => 23.45,
Expand All @@ -144,6 +148,8 @@ protected function getColumnNames(): array
{
return [
'temp_id',
'temp_tinyint',
'temp_smallint',
'temp_int',
'temp_bigint',
'temp_float',
Expand Down
11 changes: 11 additions & 0 deletions tests/Driver/MysqliDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Artemeon\Database\ConnectionParameters;
use Artemeon\Database\Driver\MysqliDriver;
use Artemeon\Database\Schema\DataType;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
Expand All @@ -26,6 +27,16 @@ public function testBuildsDatabaseSpecificSubstringExpression(): void
self::assertEquals('SUBSTRING("test value", 1, 1)', $mysqliDriver->getSubstringExpression('"test value"', 1, 1));
}

public function testKeepsIntegerTypesDistinct(): void
{
$driver = new MysqliDriver();

self::assertSame(' TINYINT ', $driver->getDatatype(DataType::TINYINT));
self::assertSame(' SMALLINT ', $driver->getDatatype(DataType::SMALLINT));
self::assertSame(' INT ', $driver->getDatatype(DataType::INT));
self::assertSame(' BIGINT ', $driver->getDatatype(DataType::BIGINT));
}

/**
* @return array{string,list<string>,string,string}[]
*/
Expand Down
12 changes: 12 additions & 0 deletions tests/Driver/PostgresDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Artemeon\Database\ConnectionParameters;
use Artemeon\Database\Driver\PostgresDriver;
use Artemeon\Database\Schema\DataType;
use Mockery;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
Expand All @@ -26,6 +27,17 @@ public function testBuildsDatabaseSpecificSubstringExpression(): void
self::assertEquals('SUBSTRING(cast ("test value" as text), 1, 1)', $postgresDriver->getSubstringExpression('"test value"', 1, 1));
}

public function testCollapsesTinyintToSmallint(): void
{
$driver = new PostgresDriver();

// Postgres has no 1-byte integer type, so TINYINT round-trips as SMALLINT.
self::assertSame(' SMALLINT ', $driver->getDatatype(DataType::TINYINT));
self::assertSame(' SMALLINT ', $driver->getDatatype(DataType::SMALLINT));
self::assertSame(' INT ', $driver->getDatatype(DataType::INT));
self::assertSame(' BIGINT ', $driver->getDatatype(DataType::BIGINT));
}

/**
* @return array{string,list<string>,string,string}[]
*/
Expand Down
11 changes: 11 additions & 0 deletions tests/Driver/Sqlite3DriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Artemeon\Database\Tests\Driver;

use Artemeon\Database\Driver\Sqlite3Driver;
use Artemeon\Database\Schema\DataType;
use PHPUnit\Framework\TestCase;

/**
Expand All @@ -21,4 +22,14 @@ public function testBuildsDatabaseSpecificSubstringExpression(): void
self::assertEquals('SUBSTR("test value", 1)', $sqlite3Driver->getSubstringExpression('"test value"', 1, null));
self::assertEquals('SUBSTR("test value", 1, 1)', $sqlite3Driver->getSubstringExpression('"test value"', 1, 1));
}

public function testCollapsesAllIntegerTypesToInteger(): void
{
$driver = new Sqlite3Driver();

self::assertSame(' INTEGER ', $driver->getDatatype(DataType::TINYINT));
self::assertSame(' INTEGER ', $driver->getDatatype(DataType::SMALLINT));
self::assertSame(' INTEGER ', $driver->getDatatype(DataType::INT));
self::assertSame(' INTEGER ', $driver->getDatatype(DataType::BIGINT));
}
}
Loading