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

Support DateTime instances #273

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
6 changes: 6 additions & 0 deletions src/ColumnSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use function bin2hex;
use function is_string;
use function str_starts_with;

/**
* Represents the metadata of a column in a database table for MSSQL Server.
Expand Down Expand Up @@ -51,4 +52,9 @@ public function dbTypecast(mixed $value): mixed

return parent::dbTypecast($value);
}

public function hasTimezone(): bool
{
return str_starts_with((string) $this->getDbType(), 'datetimeoffset');
}
}
36 changes: 33 additions & 3 deletions src/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Yiisoft\Db\Driver\Pdo\AbstractPdoSchema;
use Yiisoft\Db\Exception\Exception;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Expression\Expression;
use Yiisoft\Db\Helper\DbArrayHelper;
use Yiisoft\Db\Schema\Builder\ColumnInterface;
use Yiisoft\Db\Schema\ColumnSchemaInterface;
Expand Down Expand Up @@ -108,9 +109,16 @@ final class Schema extends AbstractPdoSchema
'image' => self::TYPE_BINARY,

/**
* Other data types 'cursor' type can't be used with tables
* The Transact-SQL rowversion data type is not a date or time data type.
* timestamp is a deprecated synonym for rowversion.
* rowversion column is semantically equivalent to a binary(8)/varbinary(8) column.
*
* @link https://learn.microsoft.com/en-us/sql/t-sql/functions/date-and-time-data-types-and-functions-transact-sql
* @link https://learn.microsoft.com/en-us/sql/t-sql/data-types/rowversion-transact-sql?view=sql-server-ver16
*/
'timestamp' => self::TYPE_TIMESTAMP,
'timestamp' => self::TYPE_BINARY,

/** Other data types 'cursor' type can't be used with tables */
'hierarchyid' => self::TYPE_STRING,
'uniqueidentifier' => self::TYPE_STRING,
'sql_variant' => self::TYPE_STRING,
Expand Down Expand Up @@ -466,6 +474,7 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface
}

$column->phpType($this->getColumnPhpType($column));
$column->dateTimeFormat($this->getDateTimeFormat($column));
$column->defaultValue($this->normalizeDefaultValue($info['column_default'], $column));

return $column;
Expand All @@ -479,7 +488,7 @@ protected function loadColumnSchema(array $info): ColumnSchemaInterface
*
* @return mixed The normalized default value.
*/
private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterface $column): mixed
private function normalizeDefaultValue(string|null $defaultValue, ColumnSchemaInterface $column): mixed
{
if (
$defaultValue === null
Expand All @@ -492,6 +501,10 @@ private function normalizeDefaultValue(?string $defaultValue, ColumnSchemaInterf

$value = $this->parseDefaultValue($defaultValue);

if ($column->getDateTimeFormat() !== null) {
return date_create_immutable($value) ?: new Expression($value);
}

return is_numeric($value)
? $column->phpTypeCast($value)
: $value;
Expand Down Expand Up @@ -541,6 +554,12 @@ protected function findColumns(TableSchemaInterface $table): bool
ELSE
[t1].[data_type] + '(' + LTRIM(RTRIM(CONVERT(CHAR,[t1].[numeric_precision]))) + ',' + LTRIM(RTRIM(CONVERT(CHAR,[t1].[numeric_scale]))) + ')'
END
WHEN [t1].[data_type] IN ('datetime2','datetimeoffset','time') THEN
CASE WHEN [t1].[datetime_precision] = NULL OR [t1].[datetime_precision] = -1 THEN
[t1].[data_type]
ELSE
[t1].[data_type] + '(' + LTRIM(RTRIM(CONVERT(CHAR,[t1].[datetime_precision]))) + ')'
END
ELSE
[t1].[data_type]
END AS 'data_type',
Expand Down Expand Up @@ -937,4 +956,15 @@ private function parseDefaultValue(string $value): string

return $value;
}

protected function getMillisecondsFormat(ColumnSchemaInterface $column): string
{
$dbType = explode('(', (string) $column->getDbType(), 2)[0];

return match ($dbType) {
'date', 'smalldatetime' => '',
'datetime' => '.v',
default => parent::getMillisecondsFormat($column),
};
}
}
22 changes: 20 additions & 2 deletions tests/ColumnSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Yiisoft\Db\Mssql\Tests;

use DateTimeImmutable;
use PHPUnit\Framework\TestCase;
use Yiisoft\Db\Mssql\Tests\Support\TestTrait;
use Yiisoft\Db\Query\Query;
Expand Down Expand Up @@ -33,7 +34,12 @@ public function testPhpTypeCast(): void
'char_col3' => null,
'float_col' => 1.234,
'blob_col' => "\x10\x11\x12",
'datetime_col' => '2023-07-11 14:50:00.123',
'smalldatetime_col' => '2023-08-05 12:05:00',
'datetime_col' => '2023-07-11 14:50:12.123',
'datetime2_col' => new DateTimeImmutable('2023-07-11 14:50:23.12 +02:00'),
'datetimeoffset_col' => new DateTimeImmutable('2023-07-11 14:50:23.1234567 -2:30'),
'date_col' => new DateTimeImmutable('2023-07-11'),
'time_col' => new DateTimeImmutable('14:50:23.123456'),
'bool_col' => false,
]
);
Expand All @@ -47,15 +53,27 @@ public function testPhpTypeCast(): void
$charCol3PhpType = $tableSchema->getColumn('char_col3')?->phpTypecast($query['char_col3']);
$floatColPhpType = $tableSchema->getColumn('float_col')?->phpTypecast($query['float_col']);
$blobColPhpType = $tableSchema->getColumn('blob_col')?->phpTypecast($query['blob_col']);
$smalldatetimeColPhpType = $tableSchema->getColumn('smalldatetime_col')?->phpTypecast($query['smalldatetime_col']);
$datetimeColPhpType = $tableSchema->getColumn('datetime_col')?->phpTypecast($query['datetime_col']);
$datetime2ColPhpType = $tableSchema->getColumn('datetime2_col')?->phpTypecast($query['datetime2_col']);
$datetimeoffsetColPhpType = $tableSchema->getColumn('datetimeoffset_col')?->phpTypecast($query['datetimeoffset_col']);
$dateColPhpType = $tableSchema->getColumn('date_col')?->phpTypecast($query['date_col']);
$timeColPhpType = $tableSchema->getColumn('time_col')?->phpTypecast($query['time_col']);
$datetime2DefaultPhpType = $tableSchema->getColumn('datetime2_default')?->phpTypecast($query['datetime2_default']);
$boolColPhpType = $tableSchema->getColumn('bool_col')?->phpTypecast($query['bool_col']);

$this->assertSame(1, $intColPhpType);
$this->assertSame(str_repeat('x', 100), $charColPhpType);
$this->assertNull($charCol3PhpType);
$this->assertSame(1.234, $floatColPhpType);
$this->assertSame("\x10\x11\x12", $blobColPhpType);
$this->assertSame('2023-07-11 14:50:00.123', $datetimeColPhpType);
$this->assertEquals(new DateTimeImmutable('2023-08-05 12:05:00'), $smalldatetimeColPhpType);
$this->assertEquals(new DateTimeImmutable('2023-07-11 14:50:12.123'), $datetimeColPhpType);
$this->assertEquals(new DateTimeImmutable('2023-07-11 14:50:23.12 +02:00'), $datetime2ColPhpType);
$this->assertEquals(new DateTimeImmutable('2023-07-11 14:50:23.1234567 -2:30'), $datetimeoffsetColPhpType);
$this->assertEquals(new DateTimeImmutable('2023-07-11'), $dateColPhpType);
$this->assertEquals(new DateTimeImmutable('14:50:23.123456'), $timeColPhpType);
$this->assertInstanceOf(DateTimeImmutable::class, $datetime2DefaultPhpType);
$this->assertEquals(false, $boolColPhpType);

$db->close();
Expand Down
17 changes: 13 additions & 4 deletions tests/Function/CastTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Yiisoft\Db\Exception\InvalidArgumentException;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Mssql\Tests\Support\TestTrait;

/**
Expand All @@ -37,15 +38,19 @@ public function testCreateTableWithDefaultValue(
string $column,
string $dbType,
string $phpType,
string $defaultValue
string|ExpressionInterface $defaultValue
): void {
$db = $this->buildTable();

$tableSchema = $db->getTableSchema('cast');

$this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
$this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
if ($defaultValue instanceof ExpressionInterface) {
$this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
} else {
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
}

$db->createCommand()->dropTable('cast')->execute();
}
Expand Down Expand Up @@ -89,7 +94,7 @@ public function testDefaultValue(
string $column,
string $dbType,
string $phpType,
string $defaultValue
string|ExpressionInterface $defaultValue
): void {
$this->setFixture('Function/convertion.sql');

Expand All @@ -98,7 +103,11 @@ public function testDefaultValue(

$this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
$this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
if ($defaultValue instanceof ExpressionInterface) {
$this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
} else {
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
}

$db->createCommand()->dropTable('cast')->execute();
}
Expand Down
17 changes: 13 additions & 4 deletions tests/Function/ConvertTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Yiisoft\Db\Exception\InvalidArgumentException;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Mssql\Tests\Support\TestTrait;

/**
Expand All @@ -37,15 +38,19 @@ public function testCreateTableWithDefaultValue(
string $column,
string $dbType,
string $phpType,
string $defaultValue
string|ExpressionInterface $defaultValue
): void {
$db = $this->buildTable();

$tableSchema = $db->getTableSchema('convert');

$this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
$this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
if ($defaultValue instanceof ExpressionInterface) {
$this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
} else {
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
}

$db->createCommand()->dropTable('convert')->execute();
}
Expand Down Expand Up @@ -89,7 +94,7 @@ public function testDefaultValue(
string $column,
string $dbType,
string $phpType,
string $defaultValue
string|ExpressionInterface $defaultValue
): void {
$this->setFixture('Function/convertion.sql');

Expand All @@ -98,7 +103,11 @@ public function testDefaultValue(

$this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
$this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
if ($defaultValue instanceof ExpressionInterface) {
$this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
} else {
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
}

$db->createCommand()->dropTable('convert')->execute();
}
Expand Down
17 changes: 13 additions & 4 deletions tests/Function/DateTimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Yiisoft\Db\Exception\InvalidArgumentException;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Mssql\Tests\Support\TestTrait;

/**
Expand All @@ -36,15 +37,19 @@ public function testCreateTableWithDefaultValue(
string $column,
string $dbType,
string $phpType,
string $defaultValue
string|ExpressionInterface $defaultValue
): void {
$db = $this->buildTable();

$tableSchema = $db->getTableSchema('datetime');

$this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
$this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
if ($defaultValue instanceof ExpressionInterface) {
$this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
} else {
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
}

$db->createCommand()->dropTable('datetime')->execute();
}
Expand Down Expand Up @@ -136,7 +141,7 @@ public function testDefaultValue(
string $column,
string $dbType,
string $phpType,
string $defaultValue
string|ExpressionInterface $defaultValue
): void {
$this->setFixture('Function/datetime.sql');

Expand All @@ -145,7 +150,11 @@ public function testDefaultValue(

$this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
$this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
if ($defaultValue instanceof ExpressionInterface) {
$this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
} else {
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
}

$db->createCommand()->dropTable('datetime')->execute();
}
Expand Down
17 changes: 13 additions & 4 deletions tests/Function/TryCastTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Yiisoft\Db\Exception\InvalidArgumentException;
use Yiisoft\Db\Exception\InvalidConfigException;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Mssql\Tests\Support\TestTrait;

/**
Expand All @@ -37,15 +38,19 @@ public function testCreateTableWithDefaultValue(
string $column,
string $dbType,
string $phpType,
string $defaultValue
string|ExpressionInterface $defaultValue
): void {
$db = $this->buildTable();

$tableSchema = $db->getTableSchema('trycast');

$this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
$this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
if ($defaultValue instanceof ExpressionInterface) {
$this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
} else {
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
}

$db->createCommand()->dropTable('trycast')->execute();
}
Expand Down Expand Up @@ -89,7 +94,7 @@ public function testDefaultValue(
string $column,
string $dbType,
string $phpType,
string $defaultValue
string|ExpressionInterface $defaultValue
): void {
$this->setFixture('Function/convertion.sql');

Expand All @@ -98,7 +103,11 @@ public function testDefaultValue(

$this->assertSame($dbType, $tableSchema?->getColumn($column)->getDbType());
$this->assertSame($phpType, $tableSchema?->getColumn($column)->getPhpType());
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
if ($defaultValue instanceof ExpressionInterface) {
$this->assertEquals($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
} else {
$this->assertSame($defaultValue, $tableSchema?->getColumn($column)->getDefaultValue());
}

$db->createCommand()->dropTable('trycast')->execute();
}
Expand Down
Loading