therselman 7 years ago
parent
commit
5a63f9e8cd
  1. 141
      src/DBAL/ArrayType.php
  2. 92
      src/DBAL/Table.php
  3. 73
      src/DBAL/Type.php
  4. 6
      src/DBAL/Types/ArrayType.php
  5. 92
      src/DBAL/Types/BaseType.php
  6. 13
      src/DBAL/Types/IntegerType.php
  7. 2
      src/DBAL/Types/StringType.php
  8. 159
      src/Schema.php

141
src/DBAL/ArrayType.php

@ -0,0 +1,141 @@ @@ -0,0 +1,141 @@
<?php
/**
* Immutable!
*/
namespace Twister\DBAL;
class ArrayType extends Type implements \Iterator, \Countable, \ArrayAccess
{
protected $members = null;
public function __construct(array $properties)
{
parent::__construct($properties);
$this->members =& $properties[3];
}
// Get the Enum array as keys, values contains the index numbers
public function asKeys() // AKA flip()
{
return array_flip($this->members);
}
// Get the Enum array with both keys AND values
public function asBoth()
{
return array_combine($this->members, $this->members);
}
// pass null to the component for members to use
public function combine(array $keys = null, array $values = null)
{
return array_combine($keys ?: $this->members, $values ?: $this->members);
}
// Creates a new array with Enum as keys, and $arr as values
public function combineWithValues(array $values)
{
return array_combine($this->members, $values);
}
// Creates a new array with Enum as values, and $arr as keys
public function combineWithKeys(array $keys)
{
return array_combine($keys, $this->members);
}
// Passed array must contain the same keys as our member array, used to verify that our array contains valid Enum entries
public function verifyKeys(array $arr)
{
// TODO
}
// Passed array must contain the same Values as our member array, used to verify that our array contains valid Enum entries
public function verifyValues(array $arr)
{
// TODO
}
/**
* Iterator interface
*/
public function rewind()
{
return reset($this->members);
}
public function current()
{
return current($this->members);
}
public function key()
{
return key($this->members);
}
public function next()
{
return next($this->members);
}
public function valid()
{
return key($this->members) !== null;
}
/**
* Countable interface
*/
public function count()
{
return count($this->members);
}
// WARNING: I think I should switch the definition of `get`,
// if we query $obj['my_enum_value'] I think it should return the Enum index number ?
/**
* ArrayAccess interface
*/
public function offsetGet($idx) // eg. var_dump($obj['two']);
{
return $this->members[$idx];
}
public function offsetSet($idx, $value) // eg. $obj['two'] = 'A value';
{
throw new \Exception('Cannot set an Array Type value! This object is Immutable!');
$this->members[$idx] = $value;
}
public function offsetExists($idx) // eg. isset($obj['two'])
{
return isset($this->members[$idx]);
}
public function offsetUnset($idx) // eg. unset($obj['two']);
{
throw new \Exception('Cannot unset an Array Type value! This object is Immutable!');
unset($this->members[$idx]);
}
/**
* Test if a value is part of the Enum or Set
* This function is case-sensitive!
* TODO: Sets can include multiple values eg. VALUE1 | VALUE2 etc.
* We need to validate the whole set!
*/
function isValid($value)
{
return in_array($value, $this->members);
}
/**
* Test if a value is part of the Enum or Set
* This function is case-sensitive!
* TODO: Sets can include multiple values eg. VALUE1 | VALUE2 etc.
* We need to validate the whole set!
*/
function isMember($value)
{
return in_array($value, $this->members);
}
}

92
src/DBAL/Table.php

@ -2,8 +2,6 @@ @@ -2,8 +2,6 @@
namespace Twister\DBAL;
use Twister\DBAL\Types;
class Table
{
protected $fields = null;
@ -18,86 +16,20 @@ class Table @@ -18,86 +16,20 @@ class Table
return $this->fields[$field];
}
public static function build($db, string $cache, string $schema, $table = null)
// $statement = 'UPDATE' | 'INSERT' ... UPDATE = partial check, INSERT = FULL (required non-nullable fields) check!
public function validate($statement)
{
mysqli_report( MYSQLI_REPORT_STRICT );
$sql = 'SELECT ' .
'TABLE_NAME,' .
'COLUMN_NAME,' .
'DATA_TYPE,' . // varchar
'COLUMN_DEFAULT,' .
// 'NULLIF(IS_NULLABLE="YES",0),' .// NULL / 1 alternative
'NULLIF(IS_NULLABLE,"NO"),' . // NULL / YES
// 'IS_NULLABLE,' . // NO / YES
'COALESCE(CHARACTER_MAXIMUM_LENGTH, NUMERIC_SCALE) AS scale,' . // These two columns data NEVER overlap!
// 'CHARACTER_MAXIMUM_LENGTH,' .
// 'CHARACTER_OCTET_LENGTH,' . // eg. varchar(32) == CHARACTER_MAXIMUM_LENGTH = 32 && CHARACTER_OCTET_LENGTH = 96
// 'NUMERIC_PRECISION,' . // eg. decimal(9,5) == NUMERIC_PRECISION = 9 && NUMERIC_SCALE = 5
// 'NUMERIC_SCALE,' . // 5 (the decimal values, or 0 for integers)
'CHARACTER_SET_NAME,' . // utf8
// 'COLLATION_NAME,' . // utf8_general_ci
'COLUMN_TYPE' . // int(10) unsigned / enum('','right','left')
// 'COLUMN_KEY,' . // PRI / UNI / MUL
// 'EXTRA' . // auto_increment / on update CURRENT_TIMESTAMP
' FROM INFORMATION_SCHEMA.COLUMNS' .
' WHERE TABLE_SCHEMA = "' . $schema . ($table ? (is_array($table) ? '" AND TABLE_NAME IN ("' . implode('","', $table) . '")' : '" AND TABLE_NAME = "' . $table) : null) . '"';
' ORDER BY TABLE_NAME, ORDINAL_POSITION';
$results = null;
return $this->fields[$field];
}
$rows = $db->query($sql)->fetch_all(MYSQLI_NUM); // MYSQLI_ASSOC || MYSQLI_NUM
foreach ($rows as $row)
{
//$field = [$row[2], $row[3], $row[4] !== null];
//... would have added more to the array ... but decided to make this realtime only for now!
//$results[$row[0]][$row[1]] = &$field;
$unsigned = strpos($row[7], 'unsigned') !== false;
switch($row[2])
{
case 'int': $obj = new IntegerType('int', $row[3], $row[4] !== null, $unsigned ? 0 : -2147483648, $unsigned ? 4294967295 : 2147483647);
case 'tinyint': $obj = new IntegerType('tinyint', $row[3], $row[4] !== null, $unsigned ? 0 : -128, $unsigned ? 255 : 127);
case 'float': $obj = new FloatType('float', $row[3], $row[4] !== null, $row[5]);
case 'varchar': $obj = new StringType('varchar', $row[3], $row[4] !== null, $row[5], $row[6]);
case 'smallint': $obj = new IntegerType('smallint', $row[3], $row[4] !== null, $unsigned ? 0 : -32768, $unsigned ? 65535 : 32767);
case 'enum': $obj = new ArrayType('enum', $row[3], $row[4] !== null, $row[3]);
case 'mediumint': $obj = new IntegerType('mediumint', $row[3], $row[4] !== null, $unsigned ? 0 : -8388608, $unsigned ? 16777215 : 8388607);
case 'date': $obj = new DateType('date', $row[3], $row[4] !== null);
case 'bit': $obj = new IntegerType('bit', $row[3], $row[4] !== null, 0, 1);
case 'char': $obj = new StringType('char', $row[3], $row[4] !== null, $row[5], $row[6]);
case 'text': $obj = new StringType('text', $row[3], $row[4] !== null, 65535, $row[6]);
case 'timestamp': $obj = new DateType('timestamp', $row[3], $row[4] !== null);
case 'binary': $obj = new StringType('binary', $row[3], $row[4] !== null, $row[5], 0);
case 'double': $obj = new FloatType('double', $row[3], $row[4] !== null, $row[5]);
case 'tinytext': $obj = new StringType('tinytext', $row[3], $row[4] !== null, 255, $row[6]);
case 'set': $obj = new ArrayType('set', $row[3], $row[4] !== null, $row[3]);
case 'decimal': $obj = new FloatType('decimal', $row[3], $row[4] !== null, $row[5]);
case 'year': $obj = new IntegerType('year', $row[3], $row[4] !== null, 1970, 2070); // 4-digit format = 1901 to 2155, or 0000.
case 'varbinary': $obj = new StringType('varbinary', $row[3], $row[4] !== null, $row[5], 0);
case 'bigint': $obj = new IntegerType('bigint', $row[3], $row[4] !== null, $unsigned ? 0 : -9223372036854775808, $unsigned ? 18446744073709551615 : 9223372036854775807);
case 'datetime': $obj = new DateType('datetime', $row[3], $row[4] !== null);
case 'time': $obj = new DateType('time', $row[3], $row[4] !== null);
case 'mediumtext': $obj = new StringType('mediumtext', $row[3], $row[4] !== null, 16777215, $row[6]);
case 'longblob': $obj = new StringType('longblob', $row[3], $row[4] !== null, 4294967295, $row[6]);
case 'mediumblob': $obj = new StringType('mediumblob', $row[3], $row[4] !== null, 16777215, $row[6]);
case 'numeric': $obj = new FloatType('numeric', $row[3], $row[4] !== null, $row[5]);
case 'blob': $obj = new StringType('blob', $row[3], $row[4] !== null, 65535, $row[6]);
case 'tinyblob': $obj = new StringType('tinyblob', $row[3], $row[4] !== null, 255, $row[6]);
case 'longtext': $obj = new StringType('longtext', $row[3], $row[4] !== null, 4294967295, $row[6]);
}
$results[$row[0]][$row[1]] = &$obj;
}
public function fields()
{
return $this->fields;
}
public function getFields()
{
return $this->fields;
}
dump($results);
die();
var_dump($tmp);
die();
$columns = $db->get_array($sql, ['TABLE_NAME', 'COLUMN_NAME']);
var_dump($columns);
foreach ($columns as $table => $fields)
{
}
}
}

73
src/DBAL/Type.php

@ -0,0 +1,73 @@ @@ -0,0 +1,73 @@
<?php
namespace Twister\DBAL;
class Type
{
protected $properties = null;
public function __construct(array $properties)
{
$properties['type'] = &$properties[0];
$properties['default'] = &$properties[1];
$properties['nullable'] = &$properties[2];
$this->properties =& $properties;
}
/**
* Get table field/column property
*
* @param string $name
* @return mixed
*/
public function __get($name)
{
return $this->properties[$name];
}
/**
* Set table field/column property
* Values cannot be set, only callables
*
* @param string $name
* @param mixed $value
* @return void
*/
public function __set($name, $value)
{
if ( ! isset($this->properties[$name]) || is_callable($this->properties[$name]))
$this->properties[$name] = $value;
else
throw new \Exception("Cannot set protected property {$name}");
}
public function __isset($name)
{
return isset($this->properties[$name]);
}
public function __unset($name)
{
unset($this->properties[$name]);
}
public function __call($method, $args)
{
return $this->properties[$method]($this, ...$args); // TEST !!!
/*
array_unshift($args, $this);
return call_user_func_array($this->properties[$method], $args);
*/
}
public function __invoke()
{
return $this->properties['__invoke']($this);
}
public function setMethod($method, callable $callable)
{
$this->properties[$method] = $callable;
return $this;
}
}

6
src/DBAL/Types/ArrayType.php

@ -94,7 +94,7 @@ class ArrayType extends BaseType implements \Iterator, \Countable, \ArrayAccess @@ -94,7 +94,7 @@ class ArrayType extends BaseType implements \Iterator, \Countable, \ArrayAccess
* TODO: Sets can include multiple values eg. VALUE1 | VALUE2 etc.
* We need to validate the whole set!
*/
function isValid(string $value, bool $casesensitive = true)
function isValid($value)
{
static $combined = null;
if ($combined === null) {
@ -122,8 +122,8 @@ class ArrayType extends BaseType implements \Iterator, \Countable, \ArrayAccess @@ -122,8 +122,8 @@ class ArrayType extends BaseType implements \Iterator, \Countable, \ArrayAccess
trigger_error('Invalid data type passed to getIndex()', E_USER_ERROR);
}
function toSQL(&$value)
function toSQL($value)
{
return value;
return $value;
}
}

92
src/DBAL/Types/BaseType.php

@ -1,43 +1,9 @@ @@ -1,43 +1,9 @@
<?php
/**
* Immutable!
*/
namespace Twister\DBAL\Types;
abstract class BaseType // AKA Type (Doctrine), Primitive, Core, Base
{
const TYPE_INT = 0;
const TYPE_TINYINT = 1;
const TYPE_SMALLINT = 2;
const TYPE_MEDIUMINT = 3;
const TYPE_BIGINT = 4;
const TYPE_BIT = 5;
const TYPE_FLOAT = 6;
const TYPE_DOUBLE = 7; // AKA REAL
const TYPE_DECIMAL = 8;
const TYPE_NUMERIC = 9; // `In MySQL, DECIMAL(M,D) and NUMERIC(M,D) are the same, and both have a precision of exactly M digits.`
const TYPE_TIMESTAMP = 10;
const TYPE_DATETIME = 11;
const TYPE_DATE = 12;
const TYPE_TIME = 13;
const TYPE_YEAR = 14;
const TYPE_VARCHAR = 15; // AKA NVARCHAR
const TYPE_CHAR = 16;
const TYPE_BLOB = 17;
const TYPE_TINYBLOB = 18;
const TYPE_MEDIUMBLOB = 19;
const TYPE_LONGBLOB = 20;
const TYPE_TEXT = 21;
const TYPE_TINYTEXT = 22;
const TYPE_MEDIUMTEXT = 23;
const TYPE_LONGTEXT = 24;
const TYPE_BINARY = 25;
const TYPE_VARBINARY = 26;
const TYPE_ENUM = 27;
const TYPE_SET = 28;
static $primitives = [ 'int',
'tinyint',
'smallint',
@ -319,35 +285,35 @@ abstract class BaseType // AKA Type (Doctrine), Primitive, Core, Base @@ -319,35 +285,35 @@ abstract class BaseType // AKA Type (Doctrine), Primitive, Core, Base
switch ($properties[0])
{
case 'int': return new Integer('int', $properties[1], $properties[2], $properties[3] === 0 ? 0 : -2147483648, $properties[3] === 0 ? 4294967295 : 2147483647);
case 'tinyint': return new Integer('tinyint', $properties[1], $properties[2], $properties[3] === 0 ? 0 : -128, $properties[3] === 0 ? 255 : 127);
case 'float': return new Float('float', $properties[1], $properties[2], $properties[3]);
case 'varchar': return new String('varchar', $properties[1], $properties[2], $properties[3], $properties[4]);
case 'smallint': return new Integer('smallint', $properties[1], $properties[2], $properties[3] === 0 ? 0 : -32768, $properties[3] === 0 ? 65535 : 32767);
case 'enum': return new Array('enum', $properties[1], $properties[2], $properties[3]);
case 'mediumint': return new Integer('mediumint', $properties[1], $properties[2], $properties[3] === 0 ? 0 : -8388608, $properties[3] === 0 ? 16777215 : 8388607);
case 'date': return new Type('date', $properties[1], $properties[2]);
case 'bit': return new Integer('bit', $properties[1], $properties[2], 0, 1);
case 'char': return new String('char', $properties[1], $properties[2], $properties[3], $properties[4]);
case 'text': return new String('text', $properties[1], $properties[2], 65535, $properties[3]);
case 'timestamp': return new Type('timestamp', $properties[1], $properties[2]);
case 'binary': return new String('binary', $properties[1], $properties[2], $properties[3], 0);
case 'double': return new Float('double', $properties[1], $properties[2], $properties[3]);
case 'tinytext': return new String('tinytext', $properties[1], $properties[2], 255, $properties[3]);
case 'set': return new Array('set', $properties[1], $properties[2], $properties[3]);
case 'decimal': return new Float('decimal', $properties[1], $properties[2], $properties[3]);
case 'year': return new Integer('year', $properties[1], $properties[2], 1970, 2070); // 4-digit format = 1901 to 2155, or 0000.
case 'varbinary': return new String('varbinary', $properties[1], $properties[2], $properties[3], 0);
case 'bigint': return new Integer('bigint', $properties[1], $properties[2], $properties[3] === 0 ? 0 : -9223372036854775808, $properties[3] === 0 ? 18446744073709551615 : 9223372036854775807);
case 'datetime': return new Type('datetime', $properties[1], $properties[2]);
case 'mediumtext': return new String('mediumtext', $properties[1], $properties[2], 16777215, $properties[3]);
case 'longblob': return new String('longblob', $properties[1], $properties[2], 4294967295, $properties[3]);
case 'mediumblob': return new String('mediumblob', $properties[1], $properties[2], 16777215, $properties[3]);
case 'numeric': return new Float('numeric', $properties[1], $properties[2], $properties[3]);
case 'time': return new Type('time', $properties[1], $properties[2]);
case 'blob': return new String('blob', $properties[1], $properties[2], 65535, $properties[3]);
case 'tinyblob': return new String('tinyblob', $properties[1], $properties[2], 255, $properties[3]);
case 'longtext': return new String('longtext', $properties[1], $properties[2], 4294967295, $properties[3]);
case 'int': return new IntegerType('int', $properties[1], $properties[2], $properties[3] === 0 ? 0 : -2147483648, $properties[3] === 0 ? 4294967295 : 2147483647);
case 'tinyint': return new IntegerType('tinyint', $properties[1], $properties[2], $properties[3] === 0 ? 0 : -128, $properties[3] === 0 ? 255 : 127);
case 'float': return new FloatType('float', $properties[1], $properties[2], $properties[3]);
case 'varchar': return new StringType('varchar', $properties[1], $properties[2], $properties[3], $properties[4]);
case 'smallint': return new IntegerType('smallint', $properties[1], $properties[2], $properties[3] === 0 ? 0 : -32768, $properties[3] === 0 ? 65535 : 32767);
case 'enum': return new ArrayType('enum', $properties[1], $properties[2], $properties[3]);
case 'mediumint': return new IntegerType('mediumint', $properties[1], $properties[2], $properties[3] === 0 ? 0 : -8388608, $properties[3] === 0 ? 16777215 : 8388607);
case 'date': return new DateType('date', $properties[1], $properties[2]);
case 'bit': return new IntegerType('bit', $properties[1], $properties[2], 0, 1);
case 'char': return new StringType('char', $properties[1], $properties[2], $properties[3], $properties[4]);
case 'text': return new StringType('text', $properties[1], $properties[2], 65535, $properties[3]);
case 'timestamp': return new DateType('timestamp', $properties[1], $properties[2]);
case 'binary': return new StringType('binary', $properties[1], $properties[2], $properties[3], 0);
case 'double': return new FloatType('double', $properties[1], $properties[2], $properties[3]);
case 'tinytext': return new StringType('tinytext', $properties[1], $properties[2], 255, $properties[3]);
case 'set': return new ArrayType('set', $properties[1], $properties[2], $properties[3]);
case 'decimal': return new FloatType('decimal', $properties[1], $properties[2], $properties[3]);
case 'year': return new IntegerType('year', $properties[1], $properties[2], 1970, 2070); // 4-digit format = 1901 to 2155, or 0000.
case 'varbinary': return new StringType('varbinary', $properties[1], $properties[2], $properties[3], 0);
case 'bigint': return new IntegerType('bigint', $properties[1], $properties[2], $properties[3] === 0 ? 0 : -9223372036854775808, $properties[3] === 0 ? 18446744073709551615 : 9223372036854775807);
case 'datetime': return new DateType('datetime', $properties[1], $properties[2]);
case 'mediumtext': return new StringType('mediumtext', $properties[1], $properties[2], 16777215, $properties[3]);
case 'longblob': return new StringType('longblob', $properties[1], $properties[2], 4294967295, $properties[3]);
case 'mediumblob': return new StringType('mediumblob', $properties[1], $properties[2], 16777215, $properties[3]);
case 'numeric': return new FloatType('numeric', $properties[1], $properties[2], $properties[3]);
case 'time': return new DateType('time', $properties[1], $properties[2]);
case 'blob': return new StringType('blob', $properties[1], $properties[2], 65535, $properties[3]);
case 'tinyblob': return new StringType('tinyblob', $properties[1], $properties[2], 255, $properties[3]);
case 'longtext': return new StringType('longtext', $properties[1], $properties[2], 4294967295, $properties[3]);
}
throw new \Exception('Invalid data-type `' . $properties[0] . '` specified in ' . __METHOD__);
}

13
src/DBAL/Types/IntegerType.php

@ -13,6 +13,12 @@ class IntegerType extends BaseType @@ -13,6 +13,12 @@ class IntegerType extends BaseType
// public $auto_increment = null;
private $clamp = function ($type, $value) {}; // callback function for `clamp`
private $__invoke = function ($type, $value) {}; // callback function for `__invoke`
private $toPHP = function ($type, $value) {}; // callback function for toPHP
private $toSQL = function ($type, $value) {}; // callback function for toSQL
private $valid = function ($type, $value) {}; // callback function for isValid
public function __construct($type, $default, $nullable, $min, $max)
{
$this->type = $type;
@ -38,7 +44,7 @@ class IntegerType extends BaseType @@ -38,7 +44,7 @@ class IntegerType extends BaseType
// Either a NULL (if valid for the field), or ZERO or clamped to range!
public function clamp($value)
{
return isset($value) && is_numeric($value) ? min(max($value, $this->min), $this->max) : ($this->default ?: ($this->nullable ? null : 0));
return isset($value) && is_numeric($value) ? min(max($value, $this->min), $this->max) : $this->default ?: ($this->nullable ? null : min(max(0, $this->min), $this->max));
}
public function getMin()
@ -51,11 +57,6 @@ class IntegerType extends BaseType @@ -51,11 +57,6 @@ class IntegerType extends BaseType
return $this->max;
}
public function isUnsigned()
{
return $this->min >= 0;
}
public function getRange()
{
return [$this->min, $this->max];

2
src/DBAL/Types/StringType.php

@ -34,7 +34,7 @@ class StringType extends BaseType @@ -34,7 +34,7 @@ class StringType extends BaseType
return parent::isValid($value) && is_scalar($value) && strlen($value) <= $length;
}
function toSQL(&$value)
function toSQL($value)
{
return $value;
}

159
src/Schema.php

@ -0,0 +1,159 @@ @@ -0,0 +1,159 @@
<?php
//Schema::worlds()->id->min
//Schema::users()->fields()
class Schema
{
private static $tables = null;
private static $db = null;
private static $schema = 'DATABASE()';
private static $cache = null;
// Set db to MySQL Connection object - if not set, then procedural style will be used
public static setConn($conn)
{
self::$db = $conn;
}
// Set the database schema name
public static setSchema($schema)
{
self::$schema = $schema != 'DATABASE()' ? '"' . $schema . '"' : 'DATABASE()';
}
// Set the schema cache directory, where we can store files for the composer autoloader
public static setCache($cache)
{
self::$cache = $cache;
}
public static __callStatic(string $table, array $args)
{
if ( ! isset($tables[$table]))
{
try
{
$class = 'Schema\\' . self::toPascalCase($table);
$tables[$table] = new $class();
}
catch (\Exception $e)
{
$tables[$table] = self::build($table);
}
}
return $tables[$table];
}
/**
* Converts $table to PascalCase, preserving leading and trailing underscores '_'
* eg. nation_capitals__ => NationCapitals__
*/
private static toPascalCase($table)
{
return str_repeat('_', strspn($table, '_')) . str_replace('_', '', ucwords($table, '_')) . str_repeat('_', strspn(strrev($table), '_'));
}
public static function build($table = null, $db = null, string $cache = null)
{
mysqli_report( MYSQLI_REPORT_STRICT );
$cache = $cache ?: self::$cache;
// here for potential caching possibilities !?!? Hopefully PHP can set an internal ref-counter and not re-define the functions each time!
static $methods = [ 'date' => function ($type, string $value) { return $value === null && $type->nullable || preg_match('~^\d\d\d\d-\d\d-\d\d$~', $value) === 1; }, // TODO: Add DateTime object validation! ie. instanceof DateTime
'datetime' => function ($type, string $value) { return $value === null && $type->nullable || preg_match('~^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$~', $value) === 1; },
'time' => function ($type, string $value) { return $value === null && $type->nullable || preg_match('~^\d\d:\d\d:\d\d$~', $value) === 1; },
'time_ex' => function ($type, string $value) { return $value === null && $type->nullable || preg_match('~^-?\d?\d\d:\d\d:\d\d$~', $value) === 1; }, // From: http://www.mysqltutorial.org/mysql-time/ A TIME value ranges from -838:59:59 to 838:59:59. In addition, a TIME value can have fractional seconds part that is up to microseconds precision (6 digits). To define a column whose data type is TIME with a fractional second precision part, you use the following syntax: column_name TIME(N);
'year' => function ($type, string $value) { return $value === null && $type->nullable || preg_match('~^\d\d\d\d$~', $value) === 1; },
'enum' => function ($type, string $value) { return in_array($value, $type->members); },
'int' => function ($type, $value) { return $value === null && $type->nullable || is_numeric($value) && $value >= $type->min && $value <= $type->max; },
'float' => function ($type, $value) { return $value === null && $type->nullable || is_numeric($value); },
'string' => function ($type, $value) { return $value === null && $type->nullable || is_string($value) && ($type->charset); },
// '[0,1]' => function ($type, $value) { return $value === null && $type->nullable || is_numeric($value) && $value >= 0.0 && $value <= 1.0; }, // HYPOTHETICAL 'bit'!
'clamp' => function ($type, $value) { return isset($value) && is_numeric($value) ? min(max($value, $type->min), $type->max) : $type->default ?: ($type->nullable ? null : min(max(0, $type->min), $type->max)); },
'isTrue' => function ($type) { return true; },
'isFalse' => function ($type) { return false; },
'isUnsigned' => function ($type) { return $type->min >= 0; },
];
$sql = 'SELECT ' .
'TABLE_NAME,' .
'COLUMN_NAME,' .
'DATA_TYPE,' . // varchar
'COLUMN_DEFAULT,' .
// 'NULLIF(IS_NULLABLE="YES",0),' .// NULL / 1 alternative
'NULLIF(IS_NULLABLE,"NO"),' . // NULL / YES
// 'IS_NULLABLE,' . // NO / YES
'COALESCE(CHARACTER_MAXIMUM_LENGTH, NUMERIC_SCALE) AS scale,' . // These two columns data NEVER overlap!
// 'CHARACTER_MAXIMUM_LENGTH,' .
// 'CHARACTER_OCTET_LENGTH,' . // eg. varchar(32) == CHARACTER_MAXIMUM_LENGTH = 32 && CHARACTER_OCTET_LENGTH = 96
// 'NUMERIC_PRECISION,' . // eg. decimal(9,5) == NUMERIC_PRECISION = 9 && NUMERIC_SCALE = 5
// 'NUMERIC_SCALE,' . // 5 (the decimal values, or 0 for integers)
'CHARACTER_SET_NAME,' . // utf8 (binary == null)
// 'COLLATION_NAME,' . // utf8_general_ci
'COLUMN_TYPE' . // int(10) unsigned / enum('','right','left')
// 'COLUMN_KEY,' . // PRI / UNI / MUL
// 'EXTRA' . // auto_increment / on update CURRENT_TIMESTAMP
' FROM INFORMATION_SCHEMA.COLUMNS' .
' WHERE TABLE_SCHEMA = DATABASE()' . ($table ? (is_array($table) ? ' AND TABLE_NAME IN ("' . implode('","', $table) . '")' : ' AND TABLE_NAME = "' . $table . '"') : null);
' ORDER BY TABLE_NAME, ORDINAL_POSITION';
$result = null;
$rows = $db->query($sql)->fetch_all(MYSQLI_NUM); // MYSQLI_ASSOC || MYSQLI_NUM
foreach ($rows as $row)
{
//$field = [$row[2], $row[3], $row[4] !== null];
//... would have added more to the array ... but decided to make this realtime only for now!
//$result[$row[0]][$row[1]] = &$field;
$unsigned = strpos($row[7], 'unsigned') !== false;
switch($row[2])
{ // 5383 / 5996 fields (406 tables) are NOT nullable (nullable = 11%, NON-nullable = 89%) && 3022 fields have defaults (50%, 658 fields default is empty string, 1752 fields default = 0)
case 'int': $obj = new Type(['int', $row[3], $row[4] !== null, 'min' => $unsigned ? 0 : -2147483648, 'max' => $unsigned ? 4294967295 : 2147483647, 'isValid' => $methods['int'], 'clamp' => $methods['clamp']]); break;
case 'tinyint': $obj = new Type(['tinyint', $row[3], $row[4] !== null, 'min' => $unsigned ? 0 : -128, 'max' => $unsigned ? 255 : 127, 'isValid' => $methods['int'], 'clamp' => $methods['clamp']]); break;
case 'float': $obj = new Type(['float', $row[3], $row[4] !== null, 'precision' => $row[5], 'isValid' => $methods['float']]); break;
case 'varchar': $obj = new Type(['varchar', $row[3], $row[4] !== null, 'maxlen' => $row[5], 'charset' => $row[6]]); break;
case 'smallint': $obj = new Type(['smallint', $row[3], $row[4] !== null, 'min' => $unsigned ? 0 : -32768, 'max' => $unsigned ? 65535 : 32767, 'isValid' => $methods['int'], 'clamp' => $methods['clamp']]); break;
case 'enum': $obj = new Type(['enum', $row[3], $row[4] !== null, 'members' => self::convertToArray($row[7]), 'isValid' => $methods['enum']]); break;
case 'mediumint': $obj = new Type(['mediumint', $row[3], $row[4] !== null, 'min' => $unsigned ? 0 : -8388608, 'max' => $unsigned ? 16777215 : 8388607, 'isValid' => $methods['int'], 'clamp' => $methods['clamp']]); break;
case 'date': $obj = new Type(['date', $row[3], $row[4] !== null, 'isValid' => $methods['date']]); break;
case 'bit': $obj = new Type(['bit', $row[3] === null ? null : ($row[3] === "b'0'" ? 0 : 1), $row[4] !== null, 0, 1, 'isValid' => $methods['int'], 'clamp' => $methods['clamp']]); break;
case 'char': $obj = new Type(['char', $row[3], $row[4] !== null, 'maxlen' => $row[5], 'charset' => $row[6]]); break;
case 'text': $obj = new Type(['text', $row[3], $row[4] !== null, 'maxlen' => 65535, 'charset' => $row[6]]); break;
case 'timestamp': $obj = new Type(['timestamp', $row[3] === 'CURRENT_TIMESTAMP' ? null : $row[3], $row[4] !== null, 'isValid' => $methods['datetime']]); break;
case 'binary': $obj = new Type(['binary', $row[3], $row[4] !== null, 'maxlen' => $row[5], 'charset' => null]); break;
case 'double': $obj = new Type(['double', $row[3], $row[4] !== null, 'precision' => $row[5], 'isValid' => $methods['float']]); break;
case 'tinytext': $obj = new Type(['tinytext', $row[3], $row[4] !== null, 'maxlen' => 255, 'charset' => $row[6]]); break;
case 'set': $obj = new Type(['set', $row[3], $row[4] !== null, self::convertToArray($row[7])]); break;
case 'decimal': $obj = new Type(['decimal', $row[3], $row[4] !== null, 'precision' => $row[5], 'isValid' => $methods['float']]); break;
case 'year': $obj = new Type(['year', $row[3], $row[4] !== null, 'min' => 1970, 'max' => 2070, 'isValid' => $methods['int'], 'clamp' => $methods['clamp']]); break; // 4-digit format = 1901 to 2155, or 0000.
case 'varbinary': $obj = new Type(['varbinary', $row[3], $row[4] !== null, 'maxlen' => $row[5], 'charset' => null]); break;
case 'bigint': $obj = new Type(['bigint', $row[3], $row[4] !== null, 'min' => $unsigned ? 0 : -9223372036854775808, 'max' => $unsigned ? 18446744073709551615 : 9223372036854775807, 'isValid' => $methods['int'], 'clamp' => $methods['clamp']]); break;
case 'datetime': $obj = new Type(['datetime', $row[3], $row[4] !== null, 'isValid' => $methods['datetime']]); break;
case 'time': $obj = new Type(['time', $row[3], $row[4] !== null, 'isValid' => $methods['time']]); break;
case 'mediumtext': $obj = new Type(['mediumtext', $row[3], $row[4] !== null, 'maxlen' => 16777215, 'charset' => $row[6]]); break;
case 'longblob': $obj = new Type(['longblob', $row[3], $row[4] !== null, 'maxlen' => 4294967295, 'charset' => $row[6]]); break;
case 'mediumblob': $obj = new Type(['mediumblob', $row[3], $row[4] !== null, 'maxlen' => 16777215, 'charset' => $row[6]]); break;
case 'numeric': $obj = new Type(['numeric', $row[3], $row[4] !== null, 'precision' => $row[5], 'isValid' => $methods['float']]); break;
case 'blob': $obj = new Type(['blob', $row[3], $row[4] !== null, 'maxlen' => 65535, 'charset' => $row[6]]); break;
case 'tinyblob': $obj = new Type(['tinyblob', $row[3], $row[4] !== null, 'maxlen' => 255, 'charset' => $row[6]]); break;
case 'longtext': $obj = new Type(['longtext', $row[3], $row[4] !== null, 'maxlen' => 4294967295, 'charset' => $row[6]]); break;
}
$result[$row[0]][$row[1]] = $obj;
}
if ( ! empty($cache))
{
}
return is_string($table) ? $result[$table] : $result;
}
// Converts enum() and set() to array
public static function convertToArray($value)
{
return explode('\',\'', substr($value, strpos($value, '(') + 2, -2));
}
}
Loading…
Cancel
Save