Skip to content

Commit 9e19f79

Browse files
committed
fix: Connection::getFieldData() default value convention for SQLSRV and OCI8
1 parent 103f1a9 commit 9e19f79

File tree

5 files changed

+96
-8
lines changed

5 files changed

+96
-8
lines changed

system/Database/OCI8/Connection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ protected function _fieldData(string $table): array
349349
$retval[$i]->max_length = $length;
350350

351351
$retval[$i]->nullable = $query[$i]->NULLABLE === 'Y';
352-
$retval[$i]->default = $query[$i]->DATA_DEFAULT;
352+
$retval[$i]->default = rtrim($query[$i]->DATA_DEFAULT);
353353
}
354354

355355
return $retval;

system/Database/SQLSRV/Connection.php

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,12 +384,42 @@ protected function _fieldData(string $table): array
384384
);
385385

386386
$retVal[$i]->nullable = $query[$i]->IS_NULLABLE !== 'NO';
387-
$retVal[$i]->default = $query[$i]->COLUMN_DEFAULT;
387+
$retVal[$i]->default = $this->normalizeDefault($query[$i]->COLUMN_DEFAULT);
388388
}
389389

390390
return $retVal;
391391
}
392392

393+
/**
394+
* Normalizes SQL Server COLUMN_DEFAULT values.
395+
* Removes wrapping parentheses and handles basic conversions.
396+
*/
397+
private function normalizeDefault(?string $default): ?string
398+
{
399+
if ($default === null) {
400+
return null;
401+
}
402+
403+
$default = trim($default);
404+
405+
// Remove outer parentheses (handles both single and double wrapping)
406+
while (preg_match('/^\((.*)\)$/', $default, $matches)) {
407+
$default = trim($matches[1]);
408+
}
409+
410+
// Handle NULL literal
411+
if (strcasecmp($default, 'NULL') === 0) {
412+
return null;
413+
}
414+
415+
// Handle string literals - remove quotes and unescape
416+
if (preg_match("/^'(.*)'$/s", $default, $matches)) {
417+
return str_replace("''", "'", $matches[1]);
418+
}
419+
420+
return $default;
421+
}
422+
393423
/**
394424
* Begin Transaction
395425
*/

tests/system/Database/Live/ForgeTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,7 @@ public function testAddFields(): void
10931093
'type' => 'int',
10941094
'max_length' => 10,
10951095
'nullable' => false,
1096-
'default' => '((0))', // Why?
1096+
'default' => '0',
10971097
],
10981098
];
10991099
} elseif ($this->db->DBDriver === 'OCI8') {
@@ -1124,7 +1124,7 @@ public function testAddFields(): void
11241124
'type' => 'NUMBER',
11251125
'max_length' => '11',
11261126
'nullable' => false,
1127-
'default' => '0 ', // Why?
1127+
'default' => '0',
11281128
],
11291129
];
11301130

tests/system/Database/Live/SQLSRV/GetFieldDataTestCase.php

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515

1616
use CodeIgniter\Database\Live\AbstractGetFieldDataTestCase;
1717
use Config\Database;
18+
use PHPUnit\Framework\Attributes\DataProvider;
1819
use PHPUnit\Framework\Attributes\Group;
20+
use ReflectionClass;
1921

2022
/**
2123
* @internal
@@ -68,31 +70,31 @@ public function testGetFieldDataDefault(): void
6870
'type' => 'int',
6971
'max_length' => 10,
7072
'nullable' => false,
71-
'default' => '((0))', // int 0
73+
'default' => '0',
7274
// 'primary_key' => 0,
7375
],
7476
(object) [
7577
'name' => 'text_default_null',
7678
'type' => 'varchar',
7779
'max_length' => 64,
7880
'nullable' => true,
79-
'default' => '(NULL)', // NULL value
81+
'default' => null,
8082
// 'primary_key' => 0,
8183
],
8284
(object) [
8385
'name' => 'text_default_text_null',
8486
'type' => 'varchar',
8587
'max_length' => 64,
8688
'nullable' => false,
87-
'default' => "('null')", // string "null"
89+
'default' => 'null', // string "null"
8890
// 'primary_key' => 0,
8991
],
9092
(object) [
9193
'name' => 'text_default_abc',
9294
'type' => 'varchar',
9395
'max_length' => 64,
9496
'nullable' => false,
95-
'default' => "('abc')", // string "abc"
97+
'default' => 'abc',
9698
// 'primary_key' => 0,
9799
],
98100
];
@@ -235,4 +237,59 @@ public function testGetFieldDataType(): void
235237
];
236238
$this->assertSameFieldData($expected, $fields);
237239
}
240+
241+
#[DataProvider('provideNormalizeDefault')]
242+
public function testNormalizeDefault(?string $input, ?string $expected, string $description): void
243+
{
244+
$reflection = new ReflectionClass($this->db);
245+
$method = $reflection->getMethod('normalizeDefault');
246+
247+
$result = $method->invoke($this->db, $input);
248+
249+
$this->assertSame($expected, $result, "Failed test: {$description}");
250+
}
251+
252+
/**
253+
* @return iterable<array{string|null, string|null, string}>
254+
*/
255+
public static function provideNormalizeDefault(): iterable
256+
{
257+
return [
258+
// [input, expected_output, description]
259+
260+
// Null cases
261+
[null, null, 'null input'],
262+
['(NULL)', null, 'NULL literal wrapped in parentheses'],
263+
['(null)', null, 'null literal lowercase'],
264+
['(Null)', null, 'null literal mixed case'],
265+
['(nULL)', null, 'null literal random case'],
266+
267+
// String literal cases
268+
["('hello')", 'hello', 'simple string'],
269+
["('hello world')", 'hello world', 'string with space'],
270+
["('')", '', 'empty string literal'],
271+
["('can''t')", "can't", 'string with escaped quote'],
272+
["('it''s a ''test''')", "it's a 'test'", 'string with multiple escaped quotes'],
273+
["('line1'+char(10)+'line2')", "line1'+char(10)+'line2", 'concatenated multiline expression'],
274+
275+
// Numeric cases
276+
['((0))', '0', 'zero with double parentheses'],
277+
['((123))', '123', 'positive integer with double parentheses'],
278+
['((-456))', '-456', 'negative integer with double parentheses'],
279+
['((3.14))', '3.14', 'float with double parentheses'],
280+
281+
// Function/expression cases
282+
['(getdate())', 'getdate()', 'function call'],
283+
['(newid())', 'newid()', 'newid function'],
284+
['(user_name())', 'user_name()', 'user_name function'],
285+
['(current_timestamp)', 'current_timestamp', 'current_timestamp'],
286+
['((1+1))', '1+1', 'mathematical expression'],
287+
['((100*2))', '100*2', 'multiplication expression'],
288+
289+
// Edge cases
290+
["((('nested')))", 'nested', 'multiple nested parentheses'],
291+
['plain_value', 'plain_value', 'value without parentheses'],
292+
['(complex_func(1, 2))', 'complex_func(1, 2)', 'function with parameters'],
293+
];
294+
}
238295
}

user_guide_src/source/changelogs/v4.6.4.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Bugs Fixed
3131
**********
3232

3333
- **Database:** Fixed a bug in ``Database::connect()`` which was causing to store non-shared connection instances in shared cache.
34+
- **Database:** Fixed a bug in ``Connection::getFieldData()`` for ``SQLSRV`` and ``OCI8`` where extra characters were returned in column default values (specific to those handlers), instead of following the convention used by other drivers.
3435

3536
See the repo's
3637
`CHANGELOG.md <https://github.com/codeigniter4/CodeIgniter4/blob/develop/CHANGELOG.md>`_

0 commit comments

Comments
 (0)