From efa957fe58a20a0d62e9181eb807f826a127d947 Mon Sep 17 00:00:00 2001 From: Dave M Date: Fri, 16 Oct 2020 15:27:54 +0000 Subject: [PATCH] - Merged stash after pull --- src/Adapter/AdapterInterface.php | 1 + src/Adapter/MsSQL.php | 5 + src/Adapter/MySQL.php | 4 + src/Annotation/Classes/Table.php | 10 +- src/Annotation/Property/Field.php | 2 + src/Annotation/Property/Field/ForeignKey.php | 1 + src/Common/EntityField.php | 19 +- src/Common/EntityResolver.php | 46 ++- src/Common/PdoObject.php | 1 + src/ConnectionAdapter.php | 5 + src/Container/AdapterProxy.php | 13 + src/Entity/InformationSchema/Column.php | 124 ++++++ src/Entity/InformationSchema/Table.php | 135 +++++++ src/Migration/FieldDefinition.php | 137 +++++++ src/Modeler/Field.php | 0 src/Modeler/Query.php | 404 ------------------- src/Query/Create.php | 37 ++ src/Query/Engine.php | 19 + src/QueryBuilder.php | 36 ++ src/Repository.php | 60 ++- 20 files changed, 623 insertions(+), 436 deletions(-) create mode 100644 src/Container/AdapterProxy.php create mode 100644 src/Entity/InformationSchema/Column.php create mode 100644 src/Entity/InformationSchema/Table.php create mode 100644 src/Migration/FieldDefinition.php delete mode 100644 src/Modeler/Field.php delete mode 100644 src/Modeler/Query.php create mode 100644 src/Query/Create.php create mode 100644 src/Query/Engine.php diff --git a/src/Adapter/AdapterInterface.php b/src/Adapter/AdapterInterface.php index e26b222..ec8e6aa 100644 --- a/src/Adapter/AdapterInterface.php +++ b/src/Adapter/AdapterInterface.php @@ -14,4 +14,5 @@ interface AdapterInterface { public function buildDataSourceName() : string; public function setup(array $configuration) : void; public function escapeIdentifier(string $segment, int $type) : string; + public function defaultEngine() : ? string; } diff --git a/src/Adapter/MsSQL.php b/src/Adapter/MsSQL.php index baccea3..ade60fc 100644 --- a/src/Adapter/MsSQL.php +++ b/src/Adapter/MsSQL.php @@ -196,4 +196,9 @@ class MsSQL implements AdapterInterface { return "[" . str_replace(["[", "]"], [ "[[", "]]" ], $segment) . "]"; } } + + public function defaultEngine(): ? string + { + return null; + } } \ No newline at end of file diff --git a/src/Adapter/MySQL.php b/src/Adapter/MySQL.php index 77bb267..e033103 100644 --- a/src/Adapter/MySQL.php +++ b/src/Adapter/MySQL.php @@ -134,4 +134,8 @@ class MySQL implements AdapterInterface { } } + public function defaultEngine(): ? string + { + return "InnoDB"; + } } diff --git a/src/Annotation/Classes/Table.php b/src/Annotation/Classes/Table.php index 2f881ea..0e1af18 100644 --- a/src/Annotation/Classes/Table.php +++ b/src/Annotation/Classes/Table.php @@ -6,14 +6,22 @@ class Table implements \Ulmus\Annotation\Annotation { public string $name; + public string $database; + public string $schema; public string $adapter; + + public string $engine; - public function __construct($name = null) + public function __construct($name = null, $engine = null) { if ( $name !== null ) { $this->name = $name; } + + if ( $engine !== null ) { + $this->engine = $engine; + } } } diff --git a/src/Annotation/Property/Field.php b/src/Annotation/Property/Field.php index 1a4efcc..3a6382c 100644 --- a/src/Annotation/Property/Field.php +++ b/src/Annotation/Property/Field.php @@ -12,6 +12,8 @@ class Field implements \Ulmus\Annotation\Annotation { public int $length; + public int $precision; + public array $attributes = []; public bool $nullable = false; diff --git a/src/Annotation/Property/Field/ForeignKey.php b/src/Annotation/Property/Field/ForeignKey.php index f0ae7d0..5e27a19 100644 --- a/src/Annotation/Property/Field/ForeignKey.php +++ b/src/Annotation/Property/Field/ForeignKey.php @@ -14,6 +14,7 @@ class ForeignKey extends Id { unset($this->nullable); $this->attributes['primary_key'] = false; + $this->attributes['auto_increment'] = false; } } diff --git a/src/Common/EntityField.php b/src/Common/EntityField.php index a23398c..8a0eb26 100644 --- a/src/Common/EntityField.php +++ b/src/Common/EntityField.php @@ -2,6 +2,8 @@ namespace Ulmus\Common; +use Ulmus\Annotation\Annotation; +use Ulmus\Migration\FieldDefinition; use Ulmus\Ulmus, Ulmus\Adapter\AdapterInterface, Ulmus\Annotation\Property\Field; @@ -28,8 +30,8 @@ class EntityField { $name = $this->entityResolver->searchFieldAnnotation($this->name, new Field() )->name ?? $this->name; - $name = ( $this->entityResolver->databaseAdapter() ?? Ulmus::$defaultAdapter )->adapter()->escapeIdentifier($name, AdapterInterface::IDENTIFIER_FIELD); - + $name = $this->entityResolver->databaseAdapter()->adapter()->escapeIdentifier($name, AdapterInterface::IDENTIFIER_FIELD); + return $useAlias ? "{$this->alias}.$name" : $name; } @@ -47,6 +49,19 @@ class EntityField return false; } + public static function generateCreateColumn($field) : string + { + $definition = new FieldDefinition($field); + + # column_name data_type(length) [NOT NULL] [DEFAULT value] [AUTO_INCREMENT] column_constraint; + + return implode(" ", [ + $definition->getSqlName(), + $definition->getSqlType(), + + ]); + } + public static function isObjectType($type) : bool { # @ Should be fixed with isBuiltIn() instead, it won't be correct based only on name diff --git a/src/Common/EntityResolver.php b/src/Common/EntityResolver.php index 500eebf..17f1707 100644 --- a/src/Common/EntityResolver.php +++ b/src/Common/EntityResolver.php @@ -2,7 +2,8 @@ namespace Ulmus\Common; -use Ulmus\Annotation\Annotation, +use Ulmus\Ulmus, + Ulmus\Annotation\Annotation, Ulmus\Annotation\Classes\Table, Ulmus\Annotation\Property\Field, Ulmus\Annotation\Property\Relation; @@ -123,20 +124,36 @@ class EntityResolver { return $list; } - public function tableName() : string + public function tableName($required = false) : string { - if ( null === $table = $this->getAnnotationFromClassname( Table::class ) ) { - throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation"); + $table = $this->tableAnnotation($required); + + if ( ( $table->name ?? "" ) === "") { + if ($required) { + throw new \ArgumentCountError("Your entity {$this->entityClass} seems to be missing a `name` argument for your @Table() annotation"); + } } - if ( $table->name === "" ) { - throw new \ArgumentCountError("Your entity {$this->entityClass} seems to be missing a `name` argument for your @Table() annotation"); - } - - return $table->name; + return $table->name ?? ""; } - public function databaseAdapter() : ? \Ulmus\ConnectionAdapter + public function tableAnnotation($required = false) : Table + { + if ( null === $table = $this->getAnnotationFromClassname( Table::class ) ) { + if ($required) { + throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation"); + } + } + + return $table; + } + + public function databaseName() : string + { + return $this->tableAnnotation(false)->database ?? $this->databaseAdapter()->adapter()->database; + } + + public function databaseAdapter() : \Ulmus\ConnectionAdapter { if ( null !== $table = $this->getAnnotationFromClassname( Table::class ) ) { if ( $table->adapter ?? null ) { @@ -148,18 +165,17 @@ class EntityResolver { } } } - - return null; + + return Ulmus::$defaultAdapter; } - - public function schemaName() : ? string + public function schemaName(bool $required = false) : ? string { if ( null === $table = $this->getAnnotationFromClassname( Table::class ) ) { throw new \LogicException("Your entity {$this->entityClass} seems to be missing a @Table() annotation"); } - if ( $table->name === "" ) { + if ( $required && ( ( $table->schema ?? "" ) === "" ) ) { throw new \ArgumentCountError("Your entity {$this->entityClass} seems to be missing a `schema` argument for your @Table() annotation"); } diff --git a/src/Common/PdoObject.php b/src/Common/PdoObject.php index ba20a73..dade0a9 100644 --- a/src/Common/PdoObject.php +++ b/src/Common/PdoObject.php @@ -9,6 +9,7 @@ class PdoObject extends PDO { public function select(string $sql, array $parameters = []): PDOStatement { + //dump($sql, $parameters); try { if (false !== ( $statement = $this->prepare($sql) )) { $statement = $this->execute($statement, $parameters, false); diff --git a/src/ConnectionAdapter.php b/src/ConnectionAdapter.php index a4e6013..617b2c2 100644 --- a/src/ConnectionAdapter.php +++ b/src/ConnectionAdapter.php @@ -39,6 +39,11 @@ class ConnectionAdapter $this->adapter->setup($connection); } + public function getConfiguration() : array + { + return $this->configuration['connections'][$this->name]; + } + /** * Connect the adapter * @return self diff --git a/src/Container/AdapterProxy.php b/src/Container/AdapterProxy.php new file mode 100644 index 0000000..31aff87 --- /dev/null +++ b/src/Container/AdapterProxy.php @@ -0,0 +1,13 @@ +connections = $arguments; + } +} \ No newline at end of file diff --git a/src/Entity/InformationSchema/Column.php b/src/Entity/InformationSchema/Column.php new file mode 100644 index 0000000..829c26f --- /dev/null +++ b/src/Entity/InformationSchema/Column.php @@ -0,0 +1,124 @@ + "columns", 'database' => "information_schema") + */ +class Column +{ + use \Ulmus\EntityTrait; + + /** + * @Field('name' => "TABLE_CATALOG", 'length' => 512) + */ + public string $tableCatalog; + + /** + * @Field('name' => "TABLE_SCHEMA", 'length' => 64) + */ + public string $tableSchema; + + /** + * @Field('name' => "TABLE_NAME", 'length' => 64) + */ + public string $tableName; + + /** + * @Field('name' => "COLUMN_NAME", 'length' => 64, 'attributes' => [ 'primary_key' => true ]) + */ + public string $name; + + /** + * @Field('name' => "ORDINAL_POSITION", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public int $ordinalPosition; + + /** + * @Field('name' => "COLUMN_DEFAULT", 'type' => "longtext") + */ + public ? string $default; + + /** + * @Field('name' => "IS_NULLABLE", 'length' => 3) + */ + public string $nullable; + + /** + * @Field('name' => "DATA_TYPE", 'length' => 64) + */ + public string $dataType; + + /** + * @Field('name' => "CHARACTER_MAXIMUM_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? int $characterMaximumLength; + + /** + * @Field('name' => "CHARACTER_OCTET_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? int $characterOctetLength; + + /** + * @Field('name' => "NUMERIC_PRECISION", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? int $numericPrecision; + + /** + * @Field('name' => "NUMERIC_SCALE", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? int $numericScale; + + /** + * @Field('name' => "DATETIME_PRECISION", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? int $datetimePrecision; + + /** + * @Field('name' => "CHARACTER_SET_NAME", 'length' => 32) + */ + public ? string $characterSetName; + + /** + * @Field('name' => "COLLATION_NAME", 'length' => 32) + */ + public ? string $collationName; + + /** + * @Field('name' => "COLLATION_TYPE", 'type' => "longtext") + */ + public string $type; + + /** + * @Field('name' => "COLUMN_KEY", 'length' => 3) + */ + public string $key; + + /** + * @Field('name' => "EXTRA", 'length' => 30) + */ + public string $extra; + + /** + * @Field('name' => "PRIVILEGES", 'length' => 80) + */ + public string $privileges; + + /** + * @Field('name' => "COLUMN_COMMENT", 'length' => 1024) + */ + public string $comment; + + /** + * @Field('name' => "IS_GENERATED", 'length' => 6) + */ + public string $generated; + + /** + * @Field('name' => "GENERATION_EXPRESSION", 'type' => "longtext") + */ + public ? string $generationExpression; + +} \ No newline at end of file diff --git a/src/Entity/InformationSchema/Table.php b/src/Entity/InformationSchema/Table.php new file mode 100644 index 0000000..958dd51 --- /dev/null +++ b/src/Entity/InformationSchema/Table.php @@ -0,0 +1,135 @@ + "tables", 'database' => "information_schema") + */ +class Table +{ + use \Ulmus\EntityTrait; + + /** + * @Field('name' => "TABLE_CATALOG", 'length' => 512) + */ + public string $catalog; + + /** + * @Field('name' => "TABLE_SCHEMA", 'length' => 64) + */ + public string $schema; + + /** + * @Field('name' => "TABLE_NAME", 'length' => 64, 'attributes' => [ 'primary_key' => true ]) + */ + public string $name; + + /** + * @Field('name' => "TABLE_TYPE", 'length' => 64) + */ + public string $type; + + /** + * @Field('name' => "ENGINE", 'length' => 64) + */ + public ? string $engine ; + + /** + * @Field('name' => "VERSION", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? string $version; + + /** + * @Field('name' => "ROW_FORMAT", 'length' => 10) + */ + public ? string $rowFormat; + + /** + * @Field('name' => "TABLE_ROWS", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? string $rows; + + /** + * @Field('name' => "AVG_ROW_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? string $averageRowLength; + + /** + * @Field('name' => "DATA_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? string $dataLength; + + /** + * @Field('name' => "MAX_DATA_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? string $maxDataLength; + + /** + * @Field('name' => "INDEX_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? string $indexLength; + + /** + * @Field('name' => "DATA_FREE", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? string $dataFree; + + /** + * @Field('name' => "AUTO_INCREMENT", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? string $autoIncrement; + + /** + * @Field('name' => "CREATE_TIME") + */ + public ? Datetime $createTime; + + /** + * @Field('name' => "UPDATE_TIME") + */ + public ? Datetime $updateTime; + + /** + * @Field('name' => "CHECK_TIME") + */ + public ? Datetime $checkTime; + + /** + * @Field('name' => "TABLE_COLLATION", 'length' => 32) + */ + public ? string $tableCollation; + + /** + * @Field('name' => "CHECKSUM", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? string $checksum; + + /** + * @Field('name' => "CREATE_OPTIONS", 'length' => 2048) + */ + public ? string $createOptions; + + /** + * @Field('name' => "TABLE_COMMENT", 'length' => 2048) + */ + public string $tableComment; + + /** + * @Field('name' => "MAX_INDEX_LENGTH", 'type' => "bigint", 'length' => 21, 'attributes' => [ 'unsigned' => true ]) + */ + public ? int $maxIndexLength; + + /** + * @Field('name' => "TEMPORARY", 'length' => 1) + */ + public ? string $temporary; + + /** + * @Relation('oneToMany', 'key' => 'name', 'foreignKey' => Column::field('tableName'), 'entity' => Column::class) + * @Where('TABLE_SCHEMA', Column::field('tableSchema')) + */ + public EntityCollection $columns; +} \ No newline at end of file diff --git a/src/Migration/FieldDefinition.php b/src/Migration/FieldDefinition.php new file mode 100644 index 0000000..c96215c --- /dev/null +++ b/src/Migration/FieldDefinition.php @@ -0,0 +1,137 @@ +name = $data['name']; + $this->builtIn = $data['builtin']; + $this->tags = $data['tags']; + + $field = $this->getFieldTag(); + $this->type = $field->type ?? $data['type']; + $this->length = $field->length ?? null; + $this->precision = $field->precision ?? null; + $this->nullable = $field->nullable ?? $data['nullable']; + $this->update = $field->attributes['update'] ?? null; + } + + public function getSqlName() : string + { + return $this->getColumnName(); + } + + public function getSqlType() : string + { + $type = $this->type; + $length = $this->length; + $default = $this->getDefault(); + + switch($type) { + case "bool": + $type = "TINYINT"; + $length = 1; + break; + + case "array": + case "string": + if ($length && $length <= 255) { + $type = "VARCHAR"; + break; + } + elseif (! $length || ( $length <= 65535 ) ) { + $type = "TEXT"; + } + elseif ( $length <= 16777215 ) { + $type = "MEDIUMTEXT"; + } + elseif ($length <= 4294967295) { + $type = "LONGTEXT"; + } + else { + throw new \Exception("A column with size bigger than 4GB cannot be created."); + } + + # Length is unnecessary on TEXT fields + unset($length); + + break; + + case "float": + $type = "DOUBLE"; + break; + + default: + $type = strtoupper($type); + break; + } + + return implode(' ', array_filter([ + $type . ( $length ? "($length" . ( $precision ? ",$precision" : "" ) . ")" : "" ), + $this->isUnsigned() ? "UNSIGNED" : null, + $this->nullable ? "NULL" : "NOT NULL", + $default ? "DEFAULT $default" : null, + $this->isAutoIncrement() ? "AUTO_INCREMENT" : null, + $this->isPrimaryKey() ? "PRIMARY KEY" : null, + ])); + } + + protected function getFieldTag() : ? Field + { + $field = array_filter($this->tags, function($item) { + return $item['object'] instanceof Field; + }); + + return array_pop($field)['object']; + } + + protected function getColumnName() : ? string + { + return $this->getFieldTag()->name ?? $this->name; + } + + protected function getDefault() : ? string + { + return $this->getFieldTag()->attributes['default'] ?? null; + } + + protected function isPrimaryKey() : bool + { + return $this->getFieldTag()->attributes['primary_key'] ?? false; + } + + protected function isAutoIncrement() : bool + { + return $this->getFieldTag()->attributes['auto_increment'] ?? false; + } + + protected function isUnique() : bool + { + return $this->getFieldTag()->attributes['unique'] ?? false; + } + + protected function isUnsigned() : bool + { + return $this->getFieldTag()->attributes['unsigned'] ?? false; + } +} diff --git a/src/Modeler/Field.php b/src/Modeler/Field.php deleted file mode 100644 index e69de29..0000000 diff --git a/src/Modeler/Query.php b/src/Modeler/Query.php deleted file mode 100644 index 0db7442..0000000 --- a/src/Modeler/Query.php +++ /dev/null @@ -1,404 +0,0 @@ - 'AGAINST', - 'and' => 'AND', - 'as' => 'AS', - 'charset' => 'CHARACTER SET', - 'collate' => 'COLLATE', - 'create' => 'CREATE', - 'database' => 'DATABASE', - 'delete' => 'DELETE FROM', - 'distinct' => 'DISTINCT', - 'drop' => 'DROP', - 'engine' => 'ENGINE', - '!exist' => 'IF NOT EXISTS', - 'exist' => 'IF EXISTS', - 'explain' => 'EXPLAIN', - 'from' => 'FROM', - 'grant' => 'GRANT', - 'grant_option' => 'GRANT OPTION', - 'group_by' => 'GROUP BY', - 'having' => 'HAVING', - 'in' => 'IN', - 'insert' => 'INSERT INTO', - 'join' => 'JOIN', - 'join-left' => 'LEFT', - 'join-right' => 'RIGHT', - 'join-inner' => 'INNER', - 'join-full' => 'FULL', - 'join-self' => 'SELF', - #'join-outer' => 'OUTER', - 'join-cross' => 'CROSS', - 'like' => 'LIKE', - 'limit' => 'LIMIT', - 'match' => 'MATCH', - 'not_in' => 'NOT IN', - 'on' => 'ON', - 'on_table' => 'ON TABLE', - 'or' => 'OR', - 'order_by' => 'ORDER BY', - 'offset' => 'OFFSET', - 'revoke' => 'REVOKE', - 'select' => 'SELECT', - 'set' => 'SET', - 'table' => 'TABLE', - 'table_charset' => 'DEFAULT CHARSET', - 'to' => 'TO', - 'update' => 'UPDATE', - 'values' => 'VALUES', - 'where' => 'WHERE' - ]; - - static $escape_char = '`'; - - protected $compiled = []; - - public static function select($param) { - $param = Arrayobj::make($param); - - return static::prepare_array([ - $param->if_has('explain' , static::$syntax['explain']), - static::$syntax['select'], - $param->if_has('distinct' , static::$syntax['distinct']), - static::group_fields($param['fields'] ?: '*'), - static::$syntax['from'], - static::full_tablename($param), - static::prepare_join($param['join']), - static::prepare_where($param['where'], false, $param->ternary('escaped', true)), - $param->if_has('group_by' , static::prepare_group($param['group_by'], $param['alias'] ?? null)), - $param->if_has('having' , static::$syntax['having']." {$param['having']}"), - /* @todo UNION | INTERSECT | EXCEPT GOES HERE !*/ - $param->if_has('order_by' , static::prepare_order($param['order_by'], $param['alias'] ?? null)), - static::prepare_limit($param) - ]); - } - - /** - * This function will translate parameters into a "create database" or "create table", depending - * on given param. - * - * @param array $param 'subject': table or database - * - * @return Type Description - */ - public static function create($param) { - $param = Arrayobj::make($param); - return strtolower( $param['subject'] ) === 'table' ? static::create_table($param) : static::create_database($param); - } - - public static function create_table($param) { - $param = is_array($param) ? Arrayobj::make($param) : $param; - - return static::prepare_array([ - static::$syntax['create'], - static::$syntax['table'], - $param->if_has('!exist', static::$syntax['!exist']), - static::full_tablename($param), - static::group_create_fields($param->mandatory('fields'), true), - $param->if_has('collation' , static::$syntax['collate']." {$param['collation']}" ) - ]); - } - - public static function create_database($param) { - $param = is_array($param) ? Arrayobj::make($param) : $param; - - return static::prepare_array([ - static::$syntax['create'], - static::$syntax['database'], - $param->if_has('!exist', static::$syntax['!exist']), - static::escape( $param->mandatory('database') ) - ]); - } - - public static function insert($param) { - $param = Arrayobj::make($param); - - $field_label = static::group_fields( $param->mandatory('fields'), true, true ); - $field_values = static::group_values( $param->mandatory('values'), $param['escaped'] ?: false ); - - return static::prepare_array([ - static::$syntax['insert'], - static::full_tablename($param), - $field_label, - static::$syntax['values'], - $field_values - ]); - } - - public static function grant($param) { - $param = Arrayobj::make($param); - - $field_label = static::group_fields( $param->mandatory('privileges') ); - $users = static::group_fields( $param->mandatory('users') ); - - return static::prepare_array([ - static::$syntax['grant'], - $field_label, - static::$syntax['on_table'], - static::full_tablename($param), - static::$syntax['to'], - $users, - $param->if_has('grant_option', static::$syntax['grant_option']) - ]); - } - - public static function delete($param) { - $param = Arrayobj::make($param); - - return static::prepare_array([ - static::$syntax['delete'], - static::full_tablename($param), - static::prepare_where($param['where'], false, $param->ternary('escaped', true)), - static::prepare_order($param), - static::prepare_limit($param) - ]); - } - - public static function update($param) { - $param = Arrayobj::make($param); - - $fields = static::group_values_and_fields($param->mandatory('fields'), $param->mandatory('values')); - - return static::prepare_array([ - static::$syntax['update'], - static::full_tablename($param), - static::$syntax['set'], - $fields, - static::prepare_where($param['where']) - ]); - } - - public static function drop($param) { - $param = Arrayobj::make($param); - - return static::prepare_array([ - static::$syntax['drop'], - $param->exist('table_name') ? static::$syntax['table']." ".static::full_tablename($param) : static::$syntax['database']." ".static::escape($param->mandatory('database')) - ]); - } - - public static function full_tablename($param) { - is_array($param) && ($param = Arrayobj::make($param)); - return $param->if_has('database', static::escape($param['database']).".") . static::escape($param->mandatory('table_name')) . $param->if_has('alias', " ".static::$syntax['as']." " . $param['alias']); - } - - public static function group_fields($fields, $enclose = false, $escape = false) { - if (is_array($fields)) { - return ($enclose ? "(" : "") .implode(', ', $escape ? array_map(function($item){ return static::escape($item); }, $fields) : $fields).($enclose ? ")" : ""); - } - else { - return $escape ? static::escape($fields) : $fields; - } - } - - public static function group_create_fields($fields, $enclose = false) { - if (is_array($fields)) { - $retval = []; - - foreach($fields as $key => $value) { - $retval[] = static::escape($key)." ".$value; - } - - return ($enclose ? "(" : "") .implode(', ', $retval).($enclose ? ")" : ""); - } - else { - return $fields; - } - } - - public static function group_values($values, $escaped = false) { - $tmp = array_pop($values); - array_push($values, $tmp); - - # Are we dealing with an array of values ? - if ( is_array($tmp) ) { - $retval = []; - - foreach($values as $item) { - $retval[] = implode(', ', $escaped ? $item : static::escape_values($item) ); - } - - return "(".implode('), (', $retval).")"; - } - else { - return "(".implode(', ', $escaped ? $values : static::escape_values($values)).")"; - } - } - - public static function escape_values($values) { - $type_function = function(& $item) { - - switch( $t = gettype($item) ) { - case "boolean": - $item = $item ? 1 : 0; - break; - - case "double": - case "integer": - break; - - case "NULL": - $item = "NULL"; - break; - - case "string": - $item = "\"$item\""; - break; - } - - - return $item; - }; - - return is_array($values) ? array_map($type_function, $values) : $type_function($values); - } - - public static function group_values_and_fields($fields, $values) { - $retval = []; - - foreach($fields as $key => $item) { - $retval[] = "{$item} = {$values[$key]}"; - } - - return implode(', ', $retval); - } - - public static function prepare_array($sql) { - return implode(" ", array_filter($sql)).";"; - } - - public static function prepare_where($where, $recursion = false, $escaped = false) { - $retval = []; - - if (is_array($where)) { - $count = count($where); - for($i = 0; $i < $count; $i++) { - $item = $where[$i]; - - if ( ! Arrayobj::array_is_associative($item) ) { - $retval[] = "(".static::prepare_where($item, true, $escaped).")"; - } - else { - $comparison = (isset($item['comparison']) ? $item['comparison'] : "="); - - # are we having an IN comparison here ... - if ( $is_array = (is_array($item['value']) && count($item['value']) > 1) ) { - switch ($item['comparison']) { - case '=': - $comparison = '='; - break; - - case '!=': - $comparison = 'not_in'; - break; - } - } - - $value = static::group_fields($item['value'], true); - - - switch($comparison) { - case 'match': - $retval[] = static::$syntax[$comparison].' ('.static::fieldname($item['field'], $item['alias'] ?? null).") ".static::$syntax['against']. - " (".(!$escaped || $is_array ? $value : static::escape_values($value))." IN BOOLEAN MODE)". - ($i + 1 < $count ? " ".static::$syntax[ isset($item['operator']) ? $item['operator'] : "and" ] : ""); - - break; - - default: - $retval[] = static::fieldname($item['field'], $item['alias'] ?? null)." " . ( isset(static::$syntax[$comparison]) ? static::$syntax[$comparison] : $comparison) . - " ".(!$escaped || $is_array ? $value : static::escape_values($value)). - ($i + 1 < $count ? " ".static::$syntax[ isset($item['operator']) ? $item['operator'] : "and" ] : ""); - break; - - } - } - } - } - - return $retval ? ($recursion ? "" : static::$syntax['where'] . " ") . implode(" ", $retval ) : ""; - } - - public static function prepare_join($joins) { - $retval = []; - - if ( is_array($joins) ) { - $count = count($joins); - - for($i = 0; $i < $count; $i++) { - $join = []; - - $table = Arrayobj::make([ - 'table_name' => $joins[$i]['table'], - 'alias' => $joins[$i]['alias_right'] - ]); - - $join[] = static::$syntax[ "join-".$joins[$i]['type'] ] ?? $joins[$i]['type']; - $join[] = static::$syntax[ 'join' ]; - $join[] = static::full_tablename($table); - $join[] = static::$syntax[ 'on' ]; - - foreach($joins[$i]['fields'] as $left_field => $right_field) { - #$join[] = $joins[$i]['alias_left'].".".static::escape($left_field); - $join[] = static::fieldname($left_field, $joins[$i]['alias_left']); - $join[] = $joins[$i]['comparison']; - $join[] = static::fieldname($right_field, $joins[$i]['alias_right']); - } - - $retval[] = implode(' ', $join); - } - - } - - return implode(' ', $retval); - } - - public static function prepare_order($order, $alias = null) - { - $retval = []; - - if (is_array($order)) { - foreach($order as $item) { - $retval[] = static::fieldname($item['field'], $alias).( !empty($item['order']) ? " ".$item['order'] : "" ); - } - } - - return $retval ? static::$syntax['order_by']." ".implode(', ', $retval) : ""; - } - - public static function prepare_group($group) - { - return $group ? static::$syntax['group_by']." ".( is_array($group) ? implode(', ', $group) : $group ) : ""; - } - - public static function prepare_limit($param) - { - return implode(' ', array_filter([ - $param->if_has('limit' , static::$syntax['limit'] ." {$param['limit']}"), - $param->if_has('offset', static::$syntax['offset']." {$param['offset']}") - ])); - } - - public static function fieldname($field, $alias = null) - { - return strpos($field, '.') ? $field : (!empty($alias) ? $alias."." : "").static::escape($field); - } - - public static function escape($field) - { - return static::$escape_char . str_replace(static::$escape_char, '', $field) . static::$escape_char; - } -} diff --git a/src/Query/Create.php b/src/Query/Create.php new file mode 100644 index 0000000..4c9476c --- /dev/null +++ b/src/Query/Create.php @@ -0,0 +1,37 @@ +renderSegments([ + static::SQL_TOKEN, $this->renderTables($this->table), + $this->renderFields(), + ]); + } + + public function renderFields() : string + { + return "(" . PHP_EOL . implode(",".PHP_EOL, array_map(function($field) { + return " ".EntityField::generateCreateColumn($field); + }, $this->fieldList)) . PHP_EOL . ")"; + } +} diff --git a/src/Query/Engine.php b/src/Query/Engine.php new file mode 100644 index 0000000..ef801f4 --- /dev/null +++ b/src/Query/Engine.php @@ -0,0 +1,19 @@ +renderSegments([ + static::SQL_TOKEN, $this->engine, + ], "="); + } +} diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index c158d9d..9664105 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -305,6 +305,42 @@ class QueryBuilder return $this; } + + public function create(array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self + { + if ( null === $this->getFragment(Query\Create::class) ) { + if ( $schema ) { + $table = "\"$schema\".$table"; + } + + if ( $database ) { + $table = "\"$database\".$table"; + } + + $create = new Query\Create(); + $this->push($create); + + $create->fieldList = $fieldlist; + $create->table = $table; + } + else { + throw new \Exception("A create SQL fragment was already found within the query builder"); + } + + return $this; + } + + public function engine(string $value) : self + { + if ( null === $engine = $this->getFragment(Query\Engine::class) ) { + $engine = new Query\Engine(); + $this->push($engine); + } + + $engine->engine = $value; + + return $this; + } public function push(Query\Fragment $queryFragment) : self { diff --git a/src/Repository.php b/src/Repository.php index 8d13e8a..cd19a48 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -26,7 +26,7 @@ class Repository $this->entityClass = $entity; $this->alias = $alias; $this->entityResolver = Ulmus::resolveEntity($entity); - $this->adapter = $adapter ?? $this->entityResolver->databaseAdapter() ?? Ulmus::$defaultAdapter; + $this->adapter = $adapter ?? $this->entityResolver->databaseAdapter(); $this->queryBuilder = new QueryBuilder(); } @@ -134,8 +134,9 @@ class Repository if ( ! $entity->isLoaded() ) { $statement = $this->insertSqlQuery($dataset)->runQuery(); - if ( ( 0 !== $statement->lastInsertId ) && - ( null !== $primaryKeyDefinition )) { + if ( ( 0 !== $statement->lastInsertId ) && + ( null !== $primaryKeyDefinition )) { + $pkField = key($primaryKeyDefinition); $entity->$pkField = $statement->lastInsertId; } @@ -172,7 +173,7 @@ class Repository { $schema = $schema ?: $this->entityResolver->schemaName(); - $this->queryBuilder->truncate($this->escapeTable($table ?: $this->entityResolver->tableName()), $alias ?: $this->alias, $this->escapeDatabase($this->adapter->adapter()->database), $schema ? $this->escapeSchema($schema) : null); + $this->queryBuilder->truncate($this->escapeTable($table ?: $this->entityResolver->tableName()), $alias ?: $this->alias, $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null); $this->finalizeQuery(); @@ -181,6 +182,11 @@ class Repository return $this; } + public function createTable() + { + return $this->createSqlQuery()->runQuery(); + } + public function generateDatasetDiff(object $entity) : array { return array_diff_assoc( array_change_key_case($entity->toArray()), array_change_key_case($entity->entityGetDataset(false, true)) ); @@ -208,7 +214,7 @@ class Repository public function insert(array $fieldlist, string $table, string $alias, ? string $schema) : self { - $this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $alias, $schema); + $this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $alias, $this->escapedDatabase(), $schema); return $this; } @@ -222,7 +228,7 @@ class Repository public function update(string $table, string $alias, ? string $schema) : self { - $this->queryBuilder->update($this->escapeTable($table), $alias, $schema); + $this->queryBuilder->update($this->escapeTable($table), $alias, $this->escapedDatabase(), $schema); return $this; } @@ -243,8 +249,7 @@ class Repository public function from(string $table, ? string $alias, ? string $schema) : self { - $this->queryBuilder->from($this->escapeTable($table), $alias, $this->escapeDatabase($this->adapter->adapter()->database), $schema ? $this->escapeSchema($schema) : null); - + $this->queryBuilder->from($this->escapeTable($table), $alias, $this->escapedDatabase(), $schema ? $this->escapeSchema($schema) : null); return $this; } @@ -322,7 +327,6 @@ class Repository return $this; } - public function limit(int $value) : self { $this->queryBuilder->limit($value); @@ -337,7 +341,6 @@ class Repository return $this; } - /* @TODO */ public function commit() : self { @@ -363,16 +366,14 @@ class Repository public function withJoin(/*string|array*/ $fields) : self { - $resolvedEntity = Ulmus::resolveEntity($this->entityClass); - if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) { $this->select("{$this->alias}.*"); } foreach((array) $fields as $item) { - if ( null !== $join = $resolvedEntity->searchFieldAnnotation($item, new Annotation\Property\Join) ) { + if ( null !== $join = $this->entityResolver->searchFieldAnnotation($item, new Annotation\Property\Join) ) { $alias = $join->alias ?? $item; - $entity = $join->entity ?? $resolvedEntity->properties[$item]['type']; + $entity = $join->entity ?? $this->entityResolver->properties[$item]['type']; foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME) as $key => $field) { $this->select("$alias.$key as {$alias}\${$field['name']}"); @@ -491,6 +492,21 @@ class Repository return $this; } + public function createSqlQuery() : self + { + if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) { + $this->queryBuilder->create($this->entityResolver->fieldList(), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName()); + } + + if ( null === $this->queryBuilder->getFragment(Query\Engine::class) ) { + if ( $engine = $this->entityResolver->tableAnnotation()->engine ?? $this->entityResolver->databaseAdapter()->adapter()->defaultEngine() ) { + $this->queryBuilder->engine($engine); + } + } + + return $this; + } + protected function fromRow($row) : self { @@ -501,6 +517,15 @@ class Repository } + public function getSqlQuery(bool $flush = true) : string + { + $result = $this->queryBuilder->render(); + + $flush and $this->queryBuilder->reset(); + + return $result; + } + public function instanciateEntityCollection() : EntityCollection { return $this->entityClass::entityCollection(); @@ -521,6 +546,13 @@ class Repository return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_DATABASE); } + public function escapedDatabase() : ? string + { + $name = $this->entityResolver->tableAnnotation()->database ?? $this->adapter->adapter()->database ?? null; + + return $name ? static::escapeDatabase($name) : null; + } + public function escapeSchema(string $identifier) : string { return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_SCHEMA);