Skip to content

Commit a77f309

Browse files
authored
Merge pull request #133 from WebFiori/dev
fix: auto-map cross-database column types in ColumnFactory
2 parents 8bc8c02 + a8ec169 commit a77f309

3 files changed

Lines changed: 247 additions & 3 deletions

File tree

WebFiori/Database/Factory/ColumnFactory.php

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use WebFiori\Database\Column;
1616
use WebFiori\Database\ConnectionInfo;
1717
use WebFiori\Database\DatabaseException;
18+
use WebFiori\Database\DataType;
1819
use WebFiori\Database\MsSql\MSSQLColumn;
1920
use WebFiori\Database\MySql\MySQLColumn;
2021
use WebFiori\Database\Util\TypesMap;
@@ -72,9 +73,20 @@ public static function create(string $database, string $name, array $options = [
7273
$datatype = 'mixed';
7374
}
7475

75-
$col->setDatatype($datatype);
76-
$size = isset($options['size']) ? intval($options['size']) : 1;
77-
$col->setSize($size);
76+
$resolved = self::resolveDatatype($database, $datatype);
77+
$col->setDatatype($resolved);
78+
79+
$explicitSize = isset($options['size']) ? intval($options['size']) : null;
80+
81+
if ($explicitSize !== null) {
82+
$col->setSize($explicitSize);
83+
} else if ($resolved !== $datatype) {
84+
// Type was auto-mapped; use a large default for string/binary types
85+
// to preserve the intent of the original unsized type.
86+
$col->setSize(self::getDefaultMappedSize($resolved));
87+
} else {
88+
$col->setSize(1);
89+
}
7890

7991
self::primaryCheck($col, $options);
8092
self::columnAttributesCheck($col, $options);
@@ -124,6 +136,57 @@ public static function map(string $to, Column $column) : Column {
124136

125137
return self::create($to, $column->getName(), $optionsArr);
126138
}
139+
/**
140+
* Resolves a datatype for the target database engine.
141+
*
142+
* If the type is natively supported by the target engine, it is returned
143+
* as-is. Otherwise, the method searches all registered engine mappings in
144+
* {@see TypesMap::MAP} to find an equivalent type for the target engine.
145+
*
146+
* @param string $database Target database engine (e.g. 'mysql', 'mssql').
147+
* @param string $datatype The requested datatype.
148+
*
149+
* @return string The resolved datatype for the target engine.
150+
*/
151+
private static function resolveDatatype(string $database, string $datatype): string {
152+
$normalized = strtolower(trim($datatype));
153+
154+
if (in_array($normalized, DataType::getSupportedDataTypes($database))) {
155+
return $normalized;
156+
}
157+
158+
foreach (array_keys(TypesMap::MAP) as $sourceEngine) {
159+
if ($sourceEngine === $database) {
160+
continue;
161+
}
162+
163+
$mapped = TypesMap::getType($sourceEngine, $database, $normalized);
164+
165+
if ($mapped !== '') {
166+
return $mapped;
167+
}
168+
}
169+
170+
return $normalized;
171+
}
172+
/**
173+
* Returns a sensible default size for a type that was auto-mapped from
174+
* an unsized type (e.g. TEXT → nvarchar).
175+
*
176+
* @param string $resolvedType The mapped type.
177+
*
178+
* @return int Default size.
179+
*/
180+
private static function getDefaultMappedSize(string $resolvedType): int {
181+
return match ($resolvedType) {
182+
'nvarchar' => 4000,
183+
'varchar' => 8000,
184+
'nchar', 'char' => 255,
185+
'binary', 'varbinary' => 8000,
186+
'text', 'mediumtext', 'blob', 'mediumblob', 'longblob', 'tinyblob' => 1,
187+
default => 1
188+
};
189+
}
127190
/**
128191
*
129192
* @param MSSQLColumn $col

tests/WebFiori/Tests/Database/MsSql/MSSQLColumnTest.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
namespace WebFiori\Tests\Database\MsSql;
33

44
use PHPUnit\Framework\TestCase;
5+
use WebFiori\Database\DataType;
56
use WebFiori\Database\Factory\ColumnFactory;
67
use WebFiori\Database\MsSql\MSSQLColumn;
78
use WebFiori\Database\MySql\MySQLColumn;
@@ -581,4 +582,96 @@ public function testIdentity02() {
581582
$col->setIsIdentity(true);
582583
$this->assertEquals('[iden] [bigint] identity(1,1) not null',$col.'');
583584
}
585+
/**
586+
* @test
587+
* Test that MySQL-only types are auto-mapped when creating MSSQL columns.
588+
*/
589+
public function testAutoMapMySQLTextToMSSQL() {
590+
$col = ColumnFactory::create('mssql', 'content', ['type' => DataType::TEXT]);
591+
$this->assertInstanceOf(MSSQLColumn::class, $col);
592+
$this->assertEquals('nvarchar', $col->getDatatype());
593+
$this->assertEquals(4000, $col->getSize());
594+
}
595+
/**
596+
* @test
597+
*/
598+
public function testAutoMapMySQLMediumTextToMSSQL() {
599+
$col = ColumnFactory::create('mssql', 'body', ['type' => DataType::TEXT_MEDIUM]);
600+
$this->assertInstanceOf(MSSQLColumn::class, $col);
601+
$this->assertEquals('nvarchar', $col->getDatatype());
602+
$this->assertEquals(4000, $col->getSize());
603+
}
604+
/**
605+
* @test
606+
*/
607+
public function testAutoMapMySQLBlobToMSSQL() {
608+
$col = ColumnFactory::create('mssql', 'data', ['type' => DataType::BLOB]);
609+
$this->assertInstanceOf(MSSQLColumn::class, $col);
610+
$this->assertEquals('binary', $col->getDatatype());
611+
}
612+
/**
613+
* @test
614+
*/
615+
public function testAutoMapMySQLLongBlobToMSSQL() {
616+
$col = ColumnFactory::create('mssql', 'data', ['type' => DataType::BLOB_LONG]);
617+
$this->assertInstanceOf(MSSQLColumn::class, $col);
618+
$this->assertEquals('binary', $col->getDatatype());
619+
}
620+
/**
621+
* @test
622+
*/
623+
public function testAutoMapMySQLMediumBlobToMSSQL() {
624+
$col = ColumnFactory::create('mssql', 'data', ['type' => DataType::BLOB_MEDIUM]);
625+
$this->assertInstanceOf(MSSQLColumn::class, $col);
626+
$this->assertEquals('binary', $col->getDatatype());
627+
}
628+
/**
629+
* @test
630+
*/
631+
public function testAutoMapMySQLTinyBlobToMSSQL() {
632+
$col = ColumnFactory::create('mssql', 'data', ['type' => DataType::BLOB_TINY]);
633+
$this->assertInstanceOf(MSSQLColumn::class, $col);
634+
$this->assertEquals('binary', $col->getDatatype());
635+
}
636+
/**
637+
* @test
638+
*/
639+
public function testAutoMapMySQLDoubleToMSSQL() {
640+
$col = ColumnFactory::create('mssql', 'price', ['type' => DataType::DOUBLE]);
641+
$this->assertInstanceOf(MSSQLColumn::class, $col);
642+
$this->assertEquals('float', $col->getDatatype());
643+
}
644+
/**
645+
* @test
646+
*/
647+
public function testAutoMapMySQLTimestampToMSSQL() {
648+
$col = ColumnFactory::create('mssql', 'ts', ['type' => DataType::TIMESTAMP]);
649+
$this->assertInstanceOf(MSSQLColumn::class, $col);
650+
$this->assertEquals('datetime2', $col->getDatatype());
651+
}
652+
/**
653+
* @test
654+
* Test that explicit size is preserved when type is auto-mapped.
655+
*/
656+
public function testAutoMapPreservesExplicitSize() {
657+
$col = ColumnFactory::create('mssql', 'summary', ['type' => DataType::TEXT, 'size' => 500]);
658+
$this->assertEquals('nvarchar', $col->getDatatype());
659+
$this->assertEquals(500, $col->getSize());
660+
}
661+
/**
662+
* @test
663+
* Test that native MSSQL types are not affected by auto-mapping.
664+
*/
665+
public function testNativeTypesUnaffected() {
666+
$col = ColumnFactory::create('mssql', 'name', ['type' => DataType::VARCHAR, 'size' => 100]);
667+
$this->assertEquals('varchar', $col->getDatatype());
668+
$this->assertEquals(100, $col->getSize());
669+
670+
$col2 = ColumnFactory::create('mssql', 'num', ['type' => DataType::INT]);
671+
$this->assertEquals('int', $col2->getDatatype());
672+
673+
$col3 = ColumnFactory::create('mssql', 'uname', ['type' => DataType::NVARCHAR, 'size' => 200]);
674+
$this->assertEquals('nvarchar', $col3->getDatatype());
675+
$this->assertEquals(200, $col3->getSize());
676+
}
584677
}

tests/WebFiori/Tests/Database/MySql/MySQLColumnTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,4 +796,92 @@ public function testGetPHPType05() {
796796
$colObj->setIsNull(true);
797797
$this->assertEquals('string|null', $colObj->getPHPType());
798798
}
799+
/**
800+
* @test
801+
* Test that MSSQL-only types are auto-mapped when creating MySQL columns.
802+
*/
803+
public function testAutoMapMSSQLBigintToMySQL() {
804+
$col = ColumnFactory::create('mysql', 'big_id', ['type' => DataType::BIGINT]);
805+
$this->assertInstanceOf(MySQLColumn::class, $col);
806+
$this->assertEquals('int', $col->getDatatype());
807+
}
808+
/**
809+
* @test
810+
*/
811+
public function testAutoMapMSSQLBinaryToMySQL() {
812+
$col = ColumnFactory::create('mysql', 'data', ['type' => DataType::BINARY]);
813+
$this->assertInstanceOf(MySQLColumn::class, $col);
814+
$this->assertEquals('blob', $col->getDatatype());
815+
}
816+
/**
817+
* @test
818+
*/
819+
public function testAutoMapMSSQLDateToMySQL() {
820+
$col = ColumnFactory::create('mysql', 'd', ['type' => DataType::DATE]);
821+
$this->assertInstanceOf(MySQLColumn::class, $col);
822+
$this->assertEquals('datetime', $col->getDatatype());
823+
}
824+
/**
825+
* @test
826+
*/
827+
public function testAutoMapMSSQLDatetime2ToMySQL() {
828+
$col = ColumnFactory::create('mysql', 'dt', ['type' => DataType::DATETIME2]);
829+
$this->assertInstanceOf(MySQLColumn::class, $col);
830+
$this->assertEquals('datetime', $col->getDatatype());
831+
}
832+
/**
833+
* @test
834+
*/
835+
public function testAutoMapMSSQLMoneyToMySQL() {
836+
$col = ColumnFactory::create('mysql', 'amount', ['type' => DataType::MONEY]);
837+
$this->assertInstanceOf(MySQLColumn::class, $col);
838+
$this->assertEquals('decimal', $col->getDatatype());
839+
}
840+
/**
841+
* @test
842+
*/
843+
public function testAutoMapMSSQLNcharToMySQL() {
844+
$col = ColumnFactory::create('mysql', 'code', ['type' => DataType::NCHAR]);
845+
$this->assertInstanceOf(MySQLColumn::class, $col);
846+
$this->assertEquals('text', $col->getDatatype());
847+
}
848+
/**
849+
* @test
850+
*/
851+
public function testAutoMapMSSQLNvarcharToMySQL() {
852+
$col = ColumnFactory::create('mysql', 'content', ['type' => DataType::NVARCHAR]);
853+
$this->assertInstanceOf(MySQLColumn::class, $col);
854+
$this->assertEquals('text', $col->getDatatype());
855+
}
856+
/**
857+
* @test
858+
*/
859+
public function testAutoMapMSSQLTimeToMySQL() {
860+
$col = ColumnFactory::create('mysql', 't', ['type' => DataType::TIME]);
861+
$this->assertInstanceOf(MySQLColumn::class, $col);
862+
$this->assertEquals('varchar', $col->getDatatype());
863+
}
864+
/**
865+
* @test
866+
*/
867+
public function testAutoMapMSSQLVarbinaryToMySQL() {
868+
$col = ColumnFactory::create('mysql', 'bin', ['type' => DataType::VARBINARY]);
869+
$this->assertInstanceOf(MySQLColumn::class, $col);
870+
$this->assertEquals('blob', $col->getDatatype());
871+
}
872+
/**
873+
* @test
874+
* Test that native MySQL types are not affected by auto-mapping.
875+
*/
876+
public function testNativeTypesUnaffected() {
877+
$col = ColumnFactory::create('mysql', 'name', ['type' => DataType::VARCHAR, 'size' => 100]);
878+
$this->assertEquals('varchar', $col->getDatatype());
879+
$this->assertEquals(100, $col->getSize());
880+
881+
$col2 = ColumnFactory::create('mysql', 'body', ['type' => DataType::TEXT]);
882+
$this->assertEquals('text', $col2->getDatatype());
883+
884+
$col3 = ColumnFactory::create('mysql', 'num', ['type' => DataType::INT]);
885+
$this->assertEquals('int', $col3->getDatatype());
886+
}
799887
}

0 commit comments

Comments
 (0)