Skip to content

Commit 69c8da3

Browse files
authored
Merge pull request #185 from simon-mundy/fix/178-integer-display-width
Render Integer display width directly after the type
2 parents 52c744b + 492806a commit 69c8da3

7 files changed

Lines changed: 188 additions & 7 deletions

File tree

‎docs/book/sql-ddl/columns.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ use PhpDb\Sql\Ddl\Column\Integer;
1414
$column = new Integer('user_id');
1515
$column = new Integer('count', false, 0); // NOT NULL with default 0
1616

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

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

2429
```php

‎src/Sql/Ddl/Column/Column.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public function setNullable(bool $nullable): static
130130
return $this;
131131
}
132132

133-
public function setOption(string $name, bool|string $value): static
133+
public function setOption(string $name, bool|int|string $value): static
134134
{
135135
$this->options[$name] = $value;
136136
return $this;

‎src/Sql/Ddl/Column/Integer.php‎

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,61 @@
55
namespace PhpDb\Sql\Ddl\Column;
66

77
use Override;
8+
use PhpDb\Sql\Argument\Literal;
9+
use PhpDb\Sql\Exception\InvalidArgumentException;
10+
11+
use function array_splice;
12+
use function ctype_digit;
13+
use function is_int;
14+
use function is_string;
15+
use function sprintf;
16+
use function strlen;
17+
use function substr;
818

919
class Integer extends Column
1020
{
11-
/** @inheritDoc */
21+
/**
22+
* Renders the display width from the "length" option in parentheses directly after the type,
23+
* ahead of the nullability and default clauses.
24+
*
25+
* @inheritDoc
26+
* @throws InvalidArgumentException When the "length" option is not a non-negative integer.
27+
*/
1228
#[Override]
1329
public function getExpressionData(): array
1430
{
1531
$expressionData = parent::getExpressionData();
1632
$options = $this->getOptions();
1733

18-
if (isset($options['length'])) {
19-
$expressionData['spec'] .= " ({$options['length']})";
34+
if (! isset($options['length'])) {
35+
return $expressionData;
2036
}
2137

38+
$displayWidth = $this->normaliseDisplayWidth($options['length']);
39+
$attributes = substr($expressionData['spec'], strlen($this->specification));
40+
41+
$expressionData['spec'] = "{$this->specification}(%s){$attributes}";
42+
array_splice($expressionData['values'], offset: 2, length: 0, replacement: [new Literal($displayWidth)]);
43+
2244
return $expressionData;
2345
}
46+
47+
/**
48+
* @throws InvalidArgumentException When the value is not a non-negative integer.
49+
*/
50+
private function normaliseDisplayWidth(mixed $length): string
51+
{
52+
if (is_int($length) && $length >= 0) {
53+
return (string) $length;
54+
}
55+
56+
if (is_string($length) && ctype_digit($length)) {
57+
return $length;
58+
}
59+
60+
throw new InvalidArgumentException(sprintf(
61+
'Column "%s" length option must be a non-negative integer',
62+
$this->name,
63+
));
64+
}
2465
}

‎test/unit/Sql/Ddl/Column/BigIntegerTest.php‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,28 @@
77
use PhpDb\Sql\Argument;
88
use PhpDb\Sql\Ddl\Column\BigInteger;
99
use PhpDb\Sql\Ddl\Column\Column;
10+
use PhpDb\Sql\Ddl\Column\Integer;
11+
use PhpDb\Sql\Ddl\CreateTable;
1012
use PHPUnit\Framework\Attributes\CoversMethod;
13+
use PHPUnit\Framework\Attributes\Group;
14+
use PHPUnit\Framework\Attributes\Test;
1115
use PHPUnit\Framework\TestCase;
1216

1317
#[CoversMethod(BigInteger::class, '__construct')]
1418
#[CoversMethod(Column::class, 'getExpressionData')]
19+
#[CoversMethod(Integer::class, 'getExpressionData')]
20+
#[Group('unit')]
1521
final class BigIntegerTest extends TestCase
1622
{
23+
#[Test]
24+
public function rendersLengthDirectlyAfterType(): void
25+
{
26+
$createTable = new CreateTable('t');
27+
$createTable->addColumn(new BigInteger('i', false, null, ['length' => 20]));
28+
29+
static::assertSame("CREATE TABLE \"t\" ( \n \"i\" BIGINT(20) NOT NULL \n)", $createTable->getSqlString());
30+
}
31+
1732
public function testGetExpressionData(): void
1833
{
1934
$column = new BigInteger('foo');

‎test/unit/Sql/Ddl/Column/ColumnTest.php‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PhpDb\Sql\Ddl\Constraint\PrimaryKey;
1212
use PHPUnit\Framework\Attributes\CoversMethod;
1313
use PHPUnit\Framework\Attributes\Group;
14+
use PHPUnit\Framework\Attributes\Test;
1415
use PHPUnit\Framework\TestCase;
1516

1617
#[CoversMethod(Column::class, '__construct')]
@@ -28,6 +29,17 @@
2829
#[Group('unit')]
2930
final class ColumnTest extends TestCase
3031
{
32+
#[Test]
33+
public function setOptionAcceptsIntegerValue(): void
34+
{
35+
$column = new Column();
36+
37+
$result = $column->setOption('length', 11);
38+
39+
static::assertSame($column, $result);
40+
static::assertSame(['length' => 11], $column->getOptions());
41+
}
42+
3143
public function testAddConstraintAppendsConstraintToColumn(): void
3244
{
3345
$column = new Column('id');

‎test/unit/Sql/Ddl/Column/IntegerTest.php‎

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,91 @@
66

77
use PhpDb\Sql\Argument;
88
use PhpDb\Sql\Ddl\Column\Column;
9+
use PhpDb\Sql\Ddl\Column\ColumnInterface;
910
use PhpDb\Sql\Ddl\Column\Integer;
1011
use PhpDb\Sql\Ddl\Constraint\PrimaryKey;
12+
use PhpDb\Sql\Ddl\CreateTable;
13+
use PhpDb\Sql\Exception\InvalidArgumentException;
14+
use PhpDbTest\TestAsset\TrustingSql92Platform;
1115
use PHPUnit\Framework\Attributes\CoversMethod;
16+
use PHPUnit\Framework\Attributes\DataProvider;
1217
use PHPUnit\Framework\Attributes\Group;
18+
use PHPUnit\Framework\Attributes\Test;
1319
use PHPUnit\Framework\TestCase;
1420

1521
#[CoversMethod(Integer::class, '__construct')]
1622
#[CoversMethod(Integer::class, 'getExpressionData')]
23+
#[CoversMethod(Integer::class, 'normaliseDisplayWidth')]
1724
#[CoversMethod(Column::class, 'getExpressionData')]
1825
#[Group('unit')]
1926
final class IntegerTest extends TestCase
2027
{
28+
/**
29+
* @return array<string, array{bool|int|string}>
30+
*/
31+
public static function invalidLengthProvider(): array
32+
{
33+
return [
34+
'boolean' => [true],
35+
'negative integer' => [-1],
36+
'non-digit string' => ['abc'],
37+
];
38+
}
39+
40+
/**
41+
* Asserts what a column renders to inside CREATE TABLE, with values quoted.
42+
*/
43+
private static function assertColumnRenders(string $expected, ColumnInterface $column): void
44+
{
45+
$createTable = new CreateTable('t');
46+
$createTable->addColumn($column);
47+
48+
static::assertSame(
49+
"CREATE TABLE \"t\" ( \n {$expected} \n)",
50+
$createTable->getSqlString(new TrustingSql92Platform()),
51+
);
52+
}
53+
54+
#[Test]
55+
public function getExpressionDataPlacesLengthDirectlyAfterType(): void
56+
{
57+
$expressionData = (new Integer('i', false, null, ['length' => 11]))->getExpressionData();
58+
59+
static::assertSame('%s %s(%s) NOT NULL', $expressionData['spec']);
60+
static::assertEquals(
61+
[
62+
Argument::identifier('i'),
63+
Argument::literal('INTEGER'),
64+
Argument::literal('11'),
65+
],
66+
$expressionData['values'],
67+
);
68+
}
69+
70+
#[Test]
71+
public function rendersLengthBeforeNullabilityAndDefault(): void
72+
{
73+
static::assertColumnRenders(
74+
'"i" INTEGER(11) NULL DEFAULT \'7\'',
75+
new Integer('i', true, 7, ['length' => 11]),
76+
);
77+
}
78+
79+
#[Test]
80+
public function rendersLengthDirectlyAfterType(): void
81+
{
82+
static::assertColumnRenders('"i" INTEGER(11) NOT NULL', new Integer('i', false, null, ['length' => 11]));
83+
}
84+
85+
#[Test]
86+
public function rendersLengthSetAsIntegerOption(): void
87+
{
88+
$column = new Integer('i');
89+
$column->setOption('length', 11);
90+
91+
static::assertColumnRenders('"i" INTEGER(11) NOT NULL', $column);
92+
}
93+
2194
public function testGetExpressionData(): void
2295
{
2396
$column = new Integer('foo');
@@ -64,12 +137,32 @@ public function testGetExpressionDataIncludesLengthWhenOptionSet(): void
64137

65138
$expressionData = $column->getExpressionData();
66139

67-
self::assertStringContainsString('(11)', $expressionData['spec']);
140+
static::assertSame('%s %s(%s) NOT NULL', $expressionData['spec']);
141+
static::assertEquals(
142+
[
143+
Argument::identifier('id'),
144+
Argument::literal('INTEGER'),
145+
Argument::literal('11'),
146+
],
147+
$expressionData['values'],
148+
);
68149
}
69150

70151
public function testObjectConstruction(): void
71152
{
72153
$integer = new Integer('foo');
73154
self::assertEquals('foo', $integer->getName());
74155
}
156+
157+
#[Test]
158+
#[DataProvider('invalidLengthProvider')]
159+
public function throwsWhenLengthOptionIsNotANonNegativeInteger(bool|int|string $length): void
160+
{
161+
$column = new Integer('i', false, null, ['length' => $length]);
162+
163+
$this->expectException(InvalidArgumentException::class);
164+
$this->expectExceptionMessage('Column "i" length option must be a non-negative integer');
165+
166+
$column->getExpressionData();
167+
}
75168
}

‎test/unit/Sql/Ddl/Column/SmallIntegerTest.php‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,29 @@
66

77
use PhpDb\Sql\Argument;
88
use PhpDb\Sql\Ddl\Column\Column;
9+
use PhpDb\Sql\Ddl\Column\Integer;
910
use PhpDb\Sql\Ddl\Column\SmallInteger;
11+
use PhpDb\Sql\Ddl\CreateTable;
1012
use PHPUnit\Framework\Attributes\CoversMethod;
13+
use PHPUnit\Framework\Attributes\Group;
14+
use PHPUnit\Framework\Attributes\Test;
1115
use PHPUnit\Framework\TestCase;
1216

1317
#[CoversMethod(SmallInteger::class, '__construct')]
1418
#[CoversMethod(Column::class, 'getExpressionData')]
19+
#[CoversMethod(Integer::class, 'getExpressionData')]
20+
#[Group('unit')]
1521
final class SmallIntegerTest extends TestCase
1622
{
23+
#[Test]
24+
public function rendersLengthDirectlyAfterType(): void
25+
{
26+
$createTable = new CreateTable('t');
27+
$createTable->addColumn(new SmallInteger('i', false, null, ['length' => 6]));
28+
29+
static::assertSame("CREATE TABLE \"t\" ( \n \"i\" SMALLINT(6) NOT NULL \n)", $createTable->getSqlString());
30+
}
31+
1732
public function testGetExpressionData(): void
1833
{
1934
$column = new SmallInteger('foo');

0 commit comments

Comments
 (0)