diff --git a/lib/Cake/Database/Schema/SqliteSchema.php b/lib/Cake/Database/Schema/SqliteSchema.php index edf7761c1b4..33301e0a0cd 100644 --- a/lib/Cake/Database/Schema/SqliteSchema.php +++ b/lib/Cake/Database/Schema/SqliteSchema.php @@ -139,4 +139,108 @@ public function convertFieldDescription(Table $table, $row, $fieldParams = []) { } } +/** + * Generate the SQL fragment for a single column in Sqlite + * + * @param Cake\Database\Schema\Table $table The table object the column is in. + * @param string $name The name of the column. + * @return string SQL fragment. + */ + public function columnSql(Table $table, $name) { + $data = $table->column($name); + $typeMap = [ + 'string' => ' VARCHAR', + 'integer' => ' INTEGER', + 'biginteger' => ' BIGINT', + 'boolean' => ' BOOLEAN', + 'binary' => ' BLOB', + 'float' => ' FLOAT', + 'decimal' => ' DECIMAL', + 'text' => ' TEXT', + 'date' => ' DATE', + 'time' => ' TIME', + 'datetime' => ' DATETIME', + 'timestamp' => ' TIMESTAMP', + ]; + if (!isset($typeMap[$data['type']])) { + throw new Error\Exception(__d('cake_dev', 'Unknown column type "%s"', $data['type'])); + } + + $out = $this->_driver->quoteIdentifier($name); + $out .= $typeMap[$data['type']]; + + $hasLength = ['integer', 'string']; + if (in_array($data['type'], $hasLength, true) && isset($data['length'])) { + $out .= '(' . (int)$data['length'] . ')'; + } + $hasPrecision = ['float', 'decimal']; + if ( + in_array($data['type'], $hasPrecision, true) && + (isset($data['length']) || isset($data['precision'])) + ) { + $out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')'; + } + if (isset($data['null']) && $data['null'] === false) { + $out .= ' NOT NULL'; + } + if (isset($data['null']) && $data['null'] === true) { + $out .= ' DEFAULT NULL'; + unset($data['default']); + } + if (isset($data['default'])) { + $out .= ' DEFAULT ' . $this->_value($data['default']); + } + return $out; + } + +/** + * Escapes values for use in schema definitions. + * + * @param mixed $value The value to escape. + * @return string String for use in schema definitions. + */ + protected function _value($value) { + if (is_null($value)) { + return 'NULL'; + } + if ($value === false) { + return 'FALSE'; + } + if ($value === true) { + return 'TRUE'; + } + if (is_float($value)) { + return str_replace(',', '.', strval($value)); + } + if ((is_int($value) || $value === '0') || ( + is_numeric($value) && strpos($value, ',') === false && + $value[0] != '0' && strpos($value, 'e') === false) + ) { + return $value; + } + return $this->_driver->quote($value, \PDO::PARAM_STR); + } + +/** + * Generate the SQL fragment for a single index in MySQL + * + * @param Cake\Database\Schema\Table $table The table object the column is in. + * @param string $name The name of the column. + * @return string SQL fragment. + */ + public function indexSql(Table $table, $name) { + $data = $table->index($name); + } + +/** + * Generate the SQL to create a table. + * + * @param string $table The name of the table. + * @param array $lines The lines (columns + indexes) to go inside the table. + * @return string A complete CREATE TABLE statement + */ + public function createTableSql($table, $lines) { + $content = implode(",\n", $lines); + return sprintf("CREATE TABLE \"%s\" (\n%s\n);", $table, $content); + } } diff --git a/lib/Cake/Test/TestCase/Database/Schema/SqliteSchemaTest.php b/lib/Cake/Test/TestCase/Database/Schema/SqliteSchemaTest.php index 4a22b44509d..144ea6555cd 100644 --- a/lib/Cake/Test/TestCase/Database/Schema/SqliteSchemaTest.php +++ b/lib/Cake/Test/TestCase/Database/Schema/SqliteSchemaTest.php @@ -20,6 +20,7 @@ use Cake\Database\Connection; use Cake\Database\Schema\Collection as SchemaCollection; use Cake\Database\Schema\SqliteSchema; +use Cake\Database\Schema\Table; use Cake\TestSuite\TestCase; @@ -247,4 +248,154 @@ public function testDescribeTable() { } } +/** + * Column provider for creating column sql + * + * @return array + */ + public static function columnSqlProvider() { + return [ + // strings + [ + 'title', + ['type' => 'string', 'length' => 25, 'null' => false], + '"title" VARCHAR(25) NOT NULL' + ], + [ + 'title', + ['type' => 'string', 'length' => 25, 'null' => true, 'default' => 'ignored'], + '"title" VARCHAR(25) DEFAULT NULL' + ], + [ + 'id', + ['type' => 'string', 'length' => 32, 'fixed' => true, 'null' => false], + '"id" VARCHAR(32) NOT NULL' + ], + [ + 'role', + ['type' => 'string', 'length' => 10, 'null' => false, 'default' => 'admin'], + '"role" VARCHAR(10) NOT NULL DEFAULT "admin"' + ], + [ + 'title', + ['type' => 'string'], + '"title" VARCHAR' + ], + // Text + [ + 'body', + ['type' => 'text', 'null' => false], + '"body" TEXT NOT NULL' + ], + // Integers + [ + 'post_id', + ['type' => 'integer', 'length' => 11], + '"post_id" INTEGER(11)' + ], + [ + 'post_id', + ['type' => 'biginteger', 'length' => 20], + '"post_id" BIGINT' + ], + // Decimal + [ + 'value', + ['type' => 'decimal'], + '"value" DECIMAL' + ], + [ + 'value', + ['type' => 'decimal', 'length' => 11], + '"value" DECIMAL(11,0)' + ], + [ + 'value', + ['type' => 'decimal', 'length' => 12, 'precision' => 5], + '"value" DECIMAL(12,5)' + ], + // Float + [ + 'value', + ['type' => 'float'], + '"value" FLOAT' + ], + [ + 'value', + ['type' => 'float', 'length' => 11, 'precision' => 3], + '"value" FLOAT(11,3)' + ], + // Boolean + [ + 'checked', + ['type' => 'boolean', 'default' => false], + '"checked" BOOLEAN DEFAULT FALSE' + ], + [ + 'checked', + ['type' => 'boolean', 'default' => true, 'null' => false], + '"checked" BOOLEAN NOT NULL DEFAULT TRUE' + ], + // datetimes + [ + 'created', + ['type' => 'datetime'], + '"created" DATETIME' + ], + // Date & Time + [ + 'start_date', + ['type' => 'date'], + '"start_date" DATE' + ], + [ + 'start_time', + ['type' => 'time'], + '"start_time" TIME' + ], + // timestamps + [ + 'created', + ['type' => 'timestamp', 'null' => true], + '"created" TIMESTAMP DEFAULT NULL' + ], + ]; + } + +/** + * Test generating column definitions + * + * @dataProvider columnSqlProvider + * @return void + */ + public function testColumnSql($name, $data, $expected) { + $driver = $this->_getMockedDriver(); + $schema = new SqliteSchema($driver); + + $table = (new Table('articles'))->addColumn($name, $data); + $this->assertEquals($expected, $schema->columnSql($table, $name)); + } + +/** + * Get a schema instance with a mocked driver/pdo instances + * + * @return MysqlSchema + */ + protected function _getMockedDriver() { + $driver = new \Cake\Database\Driver\Sqlite(); + $mock = $this->getMock('FakePdo', ['quote', 'quoteIdentifier']); + $mock->expects($this->any()) + ->method('quote') + ->will($this->returnCallback(function ($value) { + return '"' . $value . '"'; + })); + $mock->expects($this->any()) + ->method('quoteIdentifier') + ->will($this->returnCallback(function ($value) { + return '"' . $value . '"'; + })); + $driver->connection($mock); + return $driver; + } + }