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: 7 additions & 2 deletions docs/book/sql-ddl/columns.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ use PhpDb\Sql\Ddl\Column\Integer;
$column = new Integer('user_id');
$column = new Integer('count', false, 0); // NOT NULL with default 0

// With display length (platform-specific)
// With display width (platform-specific)
$column = new Integer('user_id');
$column->setOption('length', 11);
$column->setOption('length', 11); // INTEGER(11)
```

The `length` option is a display width rendered directly after the type, as `INTEGER(11)`. It
accepts an `int` or a string of digits. MySQL deprecated integer display widths in 8.0.17, and the
MySQL platform decorator drops the attribute (see phpdb-mysql#81); other platforms render it as
given.

**Constructor:**

```php
Expand Down
2 changes: 1 addition & 1 deletion src/Sql/Ddl/Column/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public function setNullable(bool $nullable): static
return $this;
}

public function setOption(string $name, bool|string $value): static
public function setOption(string $name, bool|int|string $value): static
{
$this->options[$name] = $value;
return $this;
Expand Down
47 changes: 44 additions & 3 deletions src/Sql/Ddl/Column/Integer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,61 @@
namespace PhpDb\Sql\Ddl\Column;

use Override;
use PhpDb\Sql\Argument\Literal;
use PhpDb\Sql\Exception\InvalidArgumentException;

use function array_splice;
use function ctype_digit;
use function is_int;
use function is_string;
use function sprintf;
use function strlen;
use function substr;

class Integer extends Column
{
/** @inheritDoc */
/**
* Renders the display width from the "length" option in parentheses directly after the type,
* ahead of the nullability and default clauses.
*
* @inheritDoc
* @throws InvalidArgumentException When the "length" option is not a non-negative integer.
*/
#[Override]
public function getExpressionData(): array
{
$expressionData = parent::getExpressionData();
$options = $this->getOptions();

if (isset($options['length'])) {
$expressionData['spec'] .= " ({$options['length']})";
if (! isset($options['length'])) {
return $expressionData;
}

$displayWidth = $this->normaliseDisplayWidth($options['length']);
$attributes = substr($expressionData['spec'], strlen($this->specification));

$expressionData['spec'] = "{$this->specification}(%s){$attributes}";
array_splice($expressionData['values'], offset: 2, length: 0, replacement: [new Literal($displayWidth)]);

return $expressionData;
}

/**
* @throws InvalidArgumentException When the value is not a non-negative integer.
*/
private function normaliseDisplayWidth(mixed $length): string
{
if (is_int($length) && $length >= 0) {
return (string) $length;
}

if (is_string($length) && ctype_digit($length)) {
return $length;
}

throw new InvalidArgumentException(sprintf(
'Column "%s" length option must be a non-negative integer',
$this->name,
));
}
}
15 changes: 15 additions & 0 deletions test/unit/Sql/Ddl/Column/BigIntegerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,28 @@
use PhpDb\Sql\Argument;
use PhpDb\Sql\Ddl\Column\BigInteger;
use PhpDb\Sql\Ddl\Column\Column;
use PhpDb\Sql\Ddl\Column\Integer;
use PhpDb\Sql\Ddl\CreateTable;
use PHPUnit\Framework\Attributes\CoversMethod;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

#[CoversMethod(BigInteger::class, '__construct')]
#[CoversMethod(Column::class, 'getExpressionData')]
#[CoversMethod(Integer::class, 'getExpressionData')]
#[Group('unit')]
final class BigIntegerTest extends TestCase
{
#[Test]
public function rendersLengthDirectlyAfterType(): void
{
$createTable = new CreateTable('t');
$createTable->addColumn(new BigInteger('i', false, null, ['length' => 20]));

static::assertSame("CREATE TABLE \"t\" ( \n \"i\" BIGINT(20) NOT NULL \n)", $createTable->getSqlString());
}

public function testGetExpressionData(): void
{
$column = new BigInteger('foo');
Expand Down
12 changes: 12 additions & 0 deletions test/unit/Sql/Ddl/Column/ColumnTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use PhpDb\Sql\Ddl\Constraint\PrimaryKey;
use PHPUnit\Framework\Attributes\CoversMethod;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

#[CoversMethod(Column::class, '__construct')]
Expand All @@ -28,6 +29,17 @@
#[Group('unit')]
final class ColumnTest extends TestCase
{
#[Test]
public function setOptionAcceptsIntegerValue(): void
{
$column = new Column();

$result = $column->setOption('length', 11);

static::assertSame($column, $result);
static::assertSame(['length' => 11], $column->getOptions());
}

public function testAddConstraintAppendsConstraintToColumn(): void
{
$column = new Column('id');
Expand Down
95 changes: 94 additions & 1 deletion test/unit/Sql/Ddl/Column/IntegerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,91 @@

use PhpDb\Sql\Argument;
use PhpDb\Sql\Ddl\Column\Column;
use PhpDb\Sql\Ddl\Column\ColumnInterface;
use PhpDb\Sql\Ddl\Column\Integer;
use PhpDb\Sql\Ddl\Constraint\PrimaryKey;
use PhpDb\Sql\Ddl\CreateTable;
use PhpDb\Sql\Exception\InvalidArgumentException;
use PhpDbTest\TestAsset\TrustingSql92Platform;
use PHPUnit\Framework\Attributes\CoversMethod;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

#[CoversMethod(Integer::class, '__construct')]
#[CoversMethod(Integer::class, 'getExpressionData')]
#[CoversMethod(Integer::class, 'normaliseDisplayWidth')]
#[CoversMethod(Column::class, 'getExpressionData')]
#[Group('unit')]
final class IntegerTest extends TestCase
{
/**
* @return array<string, array{bool|int|string}>
*/
public static function invalidLengthProvider(): array
{
return [
'boolean' => [true],
'negative integer' => [-1],
'non-digit string' => ['abc'],
];
}

/**
* Asserts what a column renders to inside CREATE TABLE, with values quoted.
*/
private static function assertColumnRenders(string $expected, ColumnInterface $column): void
{
$createTable = new CreateTable('t');
$createTable->addColumn($column);

static::assertSame(
"CREATE TABLE \"t\" ( \n {$expected} \n)",
$createTable->getSqlString(new TrustingSql92Platform()),
);
}

#[Test]
public function getExpressionDataPlacesLengthDirectlyAfterType(): void
{
$expressionData = (new Integer('i', false, null, ['length' => 11]))->getExpressionData();

static::assertSame('%s %s(%s) NOT NULL', $expressionData['spec']);
static::assertEquals(
[
Argument::identifier('i'),
Argument::literal('INTEGER'),
Argument::literal('11'),
],
$expressionData['values'],
);
}

#[Test]
public function rendersLengthBeforeNullabilityAndDefault(): void
{
static::assertColumnRenders(
'"i" INTEGER(11) NULL DEFAULT \'7\'',
new Integer('i', true, 7, ['length' => 11]),
);
}

#[Test]
public function rendersLengthDirectlyAfterType(): void
{
static::assertColumnRenders('"i" INTEGER(11) NOT NULL', new Integer('i', false, null, ['length' => 11]));
}

#[Test]
public function rendersLengthSetAsIntegerOption(): void
{
$column = new Integer('i');
$column->setOption('length', 11);

static::assertColumnRenders('"i" INTEGER(11) NOT NULL', $column);
}

public function testGetExpressionData(): void
{
$column = new Integer('foo');
Expand Down Expand Up @@ -64,12 +137,32 @@ public function testGetExpressionDataIncludesLengthWhenOptionSet(): void

$expressionData = $column->getExpressionData();

self::assertStringContainsString('(11)', $expressionData['spec']);
static::assertSame('%s %s(%s) NOT NULL', $expressionData['spec']);
static::assertEquals(
[
Argument::identifier('id'),
Argument::literal('INTEGER'),
Argument::literal('11'),
],
$expressionData['values'],
);
}

public function testObjectConstruction(): void
{
$integer = new Integer('foo');
self::assertEquals('foo', $integer->getName());
}

#[Test]
#[DataProvider('invalidLengthProvider')]
public function throwsWhenLengthOptionIsNotANonNegativeInteger(bool|int|string $length): void
{
$column = new Integer('i', false, null, ['length' => $length]);

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Column "i" length option must be a non-negative integer');

$column->getExpressionData();
}
}
15 changes: 15 additions & 0 deletions test/unit/Sql/Ddl/Column/SmallIntegerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,29 @@

use PhpDb\Sql\Argument;
use PhpDb\Sql\Ddl\Column\Column;
use PhpDb\Sql\Ddl\Column\Integer;
use PhpDb\Sql\Ddl\Column\SmallInteger;
use PhpDb\Sql\Ddl\CreateTable;
use PHPUnit\Framework\Attributes\CoversMethod;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

#[CoversMethod(SmallInteger::class, '__construct')]
#[CoversMethod(Column::class, 'getExpressionData')]
#[CoversMethod(Integer::class, 'getExpressionData')]
#[Group('unit')]
final class SmallIntegerTest extends TestCase
{
#[Test]
public function rendersLengthDirectlyAfterType(): void
{
$createTable = new CreateTable('t');
$createTable->addColumn(new SmallInteger('i', false, null, ['length' => 6]));

static::assertSame("CREATE TABLE \"t\" ( \n \"i\" SMALLINT(6) NOT NULL \n)", $createTable->getSqlString());
}

public function testGetExpressionData(): void
{
$column = new SmallInteger('foo');
Expand Down
Loading