From 114fa5be09d50c911e0ea6d0c8b066a48351aba2 Mon Sep 17 00:00:00 2001 From: Dave M Date: Fri, 16 Oct 2020 15:04:05 +0000 Subject: [PATCH 1/3] - Had to fix a changed behaviour from PHP 7.4 and 7.4.x where __isset() and __get is not called on initialized properties anymore. --- src/EntityTrait.php | 34 ++++--- src/Repository.php | 217 ++++++++++++++++++++++---------------------- 2 files changed, 132 insertions(+), 119 deletions(-) diff --git a/src/EntityTrait.php b/src/EntityTrait.php index 4521f5f..933cb20 100644 --- a/src/EntityTrait.php +++ b/src/EntityTrait.php @@ -35,12 +35,11 @@ trait EntityTrait { public function __get(string $name) { $entityResolver = $this->resolveEntity(); - + # Resolve relations here if one is called - + # @TODO REFACTOR THIS CODE ASAP ! if ( $this->isLoaded() ) { - if ( null !== ( $join= $entityResolver->searchFieldAnnotation($name, new Join() ) ) ) { $vars = []; @@ -60,7 +59,7 @@ trait EntityTrait { } if ( null !== ( $relation = $entityResolver->searchFieldAnnotation($name, new Relation() ) ) ) { - $relationType = strtolower(str_replace(['-', '_'], '', $relation->type)); + $relationType = strtolower(str_replace(['-', '_', ' '], '', $relation->type)); $order = $entityResolver->searchFieldAnnotationList($name, new OrderBy() ); $where = $entityResolver->searchFieldAnnotationList($name, new Where() ); @@ -84,6 +83,7 @@ trait EntityTrait { switch( $relationType ) { case 'onetoone': $repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity->field($relation->foreignKey), $this->$field ); + $repository->limit(1); $this->eventExecute(Event\EntityRelationLoadInterface::class, $name, $repository); @@ -113,7 +113,7 @@ trait EntityTrait { if ($relationRelation === null) { throw new \Exception("@Relation annotation not found for field `{$relation->foreignField}` in entity {$relation->bridge}"); } - + $repository = $relationRelation->entity()->repository(); $bridgeAlias = uniqid("bridge_"); @@ -164,10 +164,7 @@ trait EntityTrait { return; } } - else { - - } - + throw new \Exception(sprintf("[%s] - Undefined variable: %s", static::class, $name)); } @@ -179,7 +176,7 @@ trait EntityTrait { if ( $this->isLoaded() && static::resolveEntity()->searchFieldAnnotation($name, new Relation() ) ) { return true; } - + return isset($this->$name); } @@ -246,6 +243,21 @@ trait EntityTrait { return $this; } + + public function resetVirtualProperties() : self + { + foreach($this->resolveEntity()->properties as $prop => $property) { + if ( ! $property['builtin'] ) { + foreach($property['tags'] as $tag) { + if ( in_array(strtolower($tag['tag']), [ 'relation', 'join' ] ) ) { + unset($this->$prop); + } + } + } + } + + return $this; + } /** * @Ignore @@ -388,7 +400,7 @@ trait EntityTrait { { $collection = new EntityCollection(...$arguments); $collection->entityClass = static::class; - + return $collection; } diff --git a/src/Repository.php b/src/Repository.php index b093bbb..8d13e8a 100644 --- a/src/Repository.php +++ b/src/Repository.php @@ -7,7 +7,7 @@ use Ulmus\Common\EntityResolver; class Repository { use EventTrait, Repository\ConditionTrait; - + const DEFAULT_ALIAS = "this"; public ? ConnectionAdapter $adapter; @@ -19,9 +19,9 @@ class Repository public string $alias; public string $entityClass; - + public array $events = []; - + public function __construct(string $entity, string $alias = self::DEFAULT_ALIAS, ConnectionAdapter $adapter = null) { $this->entityClass = $entity; $this->alias = $alias; @@ -39,12 +39,12 @@ class Repository { return $this->where($field, $value)->loadOne(); } - + public function loadFromPk($value, /* ? stringable */ $primaryKey = null) : ? object { return $primaryKey ? $this->loadOneFromField($primaryKey, $value) : $this->wherePrimaryKey($value)->loadOne(); } - + public function loadAll() : EntityCollection { return $this->collectionFromQuery(); @@ -54,20 +54,20 @@ class Repository { return $this->where($field, $value)->collectionFromQuery(); } - + public function count() : int { if ( null !== $select = $this->queryBuilder->getFragment(Query\Select::class) ) { $this->queryBuilder->removeFragment($select); } - + if ( $this->queryBuilder->getFragment(Query\GroupBy::class) ) { $this->select( "DISTINCT COUNT(*) OVER ()" ); } else { $this->select(Common\Sql::function("COUNT", "*")); } - + $this->selectSqlQuery(); $this->finalizeQuery(); @@ -75,13 +75,13 @@ class Repository return Ulmus::runSelectQuery($this->queryBuilder, $this->adapter)->fetchColumn(0); } - protected function deleteOne() + protected function deleteOne() { return $this->limit(1)->deleteSqlQuery()->runQuery(); } - - protected function deleteAll() - { + + protected function deleteAll() + { return $this->deleteSqlQuery()->runQuery(); } @@ -90,16 +90,16 @@ class Repository if ( $value !== 0 && empty($value) ) { throw new Exception\EntityPrimaryKeyUnknown("A primary key value has to be defined to delete an item."); } - + return (bool) $this->wherePrimaryKey($value)->deleteOne()->rowCount(); } - + public function destroy(object $entity) : bool { if ( ! $this->matchEntity($entity) ) { throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`"); } - + $primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField(); if ( $primaryKeyDefinition === null ) { @@ -107,39 +107,39 @@ class Repository } else { $pkField = key($primaryKeyDefinition); - + return $this->deleteFromPk($entity->$pkField); } - + return false; } - public function destroyAll(EntityCollection $collection) : void + public function destroyAll(EntityCollection $collection) : void { foreach($collection as $entity) { $this->destroy($entity); } } - + public function save(object $entity) : bool - { + { if ( ! $this->matchEntity($entity) ) { throw new \Exception("Your entity class `" . get_class($entity) . "` cannot match entity type of repository `{$this->entityClass}`"); } $dataset = $entity->toArray(); - + $primaryKeyDefinition = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField(); 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; } - + return true; } else { @@ -153,45 +153,45 @@ class Repository $this->where($pkFieldName, $dataset[$pkFieldName]); $update = $this->updateSqlQuery($diff)->runQuery(); - + return $update ? (bool) $update->rowCount() : false; } } - + return false; } - - public function saveAll(EntityCollection $collection) : void + + public function saveAll(EntityCollection $collection) : void { foreach($collection as $entity) { $this->save($entity); } } - + public function truncate(? string $table = null, ? string $alias = null, ? string $schema = null) : self { $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->finalizeQuery(); - + $result = Ulmus::runSelectQuery($this->queryBuilder, $this->adapter); - + return $this; } - + public function generateDatasetDiff(object $entity) : array { return array_diff_assoc( array_change_key_case($entity->toArray()), array_change_key_case($entity->entityGetDataset(false, true)) ); } - + public function yield() : \Generator { $class = $this->entityClass; - + $this->selectSqlQuery(); - + $this->finalizeQuery(); foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) { @@ -202,74 +202,74 @@ class Repository public function select($fields) : self { $this->queryBuilder->select($fields); - + return $this; } public function insert(array $fieldlist, string $table, string $alias, ? string $schema) : self { $this->queryBuilder->insert($fieldlist, $this->escapeTable($table), $alias, $schema); - + return $this; } public function values(array $dataset) : self { $this->queryBuilder->values($dataset); - + return $this; } - + public function update(string $table, string $alias, ? string $schema) : self { $this->queryBuilder->update($this->escapeTable($table), $alias, $schema); - + return $this; } - + public function set(array $dataset) : self { $this->queryBuilder->set($dataset); - + return $this; } - + public function delete() : self { $this->queryBuilder->delete($this->alias); - + return $this; } 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); - + return $this; } public function join(string $type, $table, $field, $value, ? string $alias = null, ? callable $callback = null) : self { $join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, false, $alias); - + if ( $callback ) { $callback($join); } - + return $this; } public function outerJoin(string $type, $table, $field, $value, ? string $alias = null, ? callable $callback = null) : self { $join = $this->queryBuilder->withJoin($type, $this->escapeTable($table), $field, $value, true, $alias); - + if ( $callback ) { $callback($join); } - + return $this; } - + public function match() : self { @@ -289,11 +289,11 @@ class Repository { } - + public function groupBy($field) : self { $this->queryBuilder->groupBy($field); - + return $this; } @@ -302,14 +302,14 @@ class Repository foreach($groups as $field ) { $this->groupBy($field); } - + return $this; } - + public function orderBy($field, ? string $direction = null) : self { $this->queryBuilder->orderBy($field, $direction); - + return $this; } @@ -318,22 +318,22 @@ class Repository foreach($orderList as $field => $direction) { $this->orderBy($field, $direction); } - + return $this; } - + public function limit(int $value) : self { $this->queryBuilder->limit($value); - + return $this; } public function offset(int $value) : self { $this->queryBuilder->offset($value); - + return $this; } @@ -355,86 +355,82 @@ class Repository if ( null === $primaryKeyField = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField() ) { throw new Exception\EntityPrimaryKeyUnknown("Entity has no field containing attributes 'primary_key'"); } - + $pkField = key($primaryKeyField); - + return $this->where($primaryKeyField[$pkField]->name ?? $pkField, $value); } - + 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) ) { $alias = $join->alias ?? $item; $entity = $join->entity ?? $resolvedEntity->properties[$item]['type']; - + foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME) as $key => $field) { $this->select("$alias.$key as {$alias}\${$field['name']}"); } - + $key = is_string($join->key) ? $this->entityClass::field($join->key) : $join->key; $foreignKey = is_string($join->foreignKey) ? $entity::field($join->foreignKey, $alias) : $join->foreignKey; - + $this->join($join->type, $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias); } else { throw new \Exception("You referenced field `$item` which do not exist or do not contain a valid @Join annotation."); } } - + return $this; } - - public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest) : self + + public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest) : self { - $likes = $searchRequest->likes(); - $wheres = $searchRequest->wheres(); - $groups = $searchRequest->groups(); - - $searchRequest->count = $searchRequest->skipCount ? 0 : $searchRequest->filter( clone $this ) - ->wheres($wheres, Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) - ->likes($likes, Query\Where::CONDITION_OR) - ->groups($groups) + $searchRequest->count = $searchRequest->filter( clone $this ) + ->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) + ->likes($searchRequest->likes(), Query\Where::CONDITION_OR) + ->groups($searchRequest->groups()) ->count(); - + return $searchRequest->filter($this) - ->wheres($wheres, Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) - ->likes($likes, Query\Where::CONDITION_OR) + ->wheres($searchRequest->wheres(), Query\Where::OPERATOR_EQUAL, Query\Where::CONDITION_AND) + ->likes($searchRequest->likes(), Query\Where::CONDITION_OR) ->orders($searchRequest->orders()) - ->groups($groups) + ->groups($searchRequest->groups()) ->offset($searchRequest->offset()) ->limit($searchRequest->limit()); } - + public function collectionFromQuery(? string $entityClass = null) : EntityCollection { $class = $entityClass ?: $this->entityClass; - - $entityCollection = $class::entityCollection(); - + + $entityCollection = $this->instanciateEntityCollection(); + $this->selectSqlQuery(); - + $this->finalizeQuery(); foreach(Ulmus::iterateQueryBuilder($this->queryBuilder, $this->adapter) as $entityData) { - $entityCollection->append( ( new $class() )->entityFillFromDataset($entityData) ); + $entityCollection->append( ( new $class() )->resetVirtualProperties()->entityFillFromDataset($entityData) ); } - - $this->eventExecute(Event\Repository\CollectionFromQueryInterface::class, $entityCollection); + + $this->eventExecute(Event\RepositoryCollectionFromQueryInterface::class, $entityCollection); return $entityCollection; } - + public function arrayFromQuery() : array { $this->selectSqlQuery(); - + $this->finalizeQuery(); return Ulmus::datasetQueryBuilder($this->queryBuilder, $this->adapter); @@ -443,10 +439,10 @@ class Repository public function runQuery() : ? \PDOStatement { $this->finalizeQuery(); - + return Ulmus::runQuery($this->queryBuilder, $this->adapter); } - + protected function insertSqlQuery(array $dataset) : self { if ( null === $this->queryBuilder->getFragment(Query\Insert::class) ) { @@ -457,18 +453,18 @@ class Repository return $this; } - + protected function updateSqlQuery(array $dataset) : self { if ( null === $this->queryBuilder->getFragment(Query\Update::class) ) { $this->update($this->entityResolver->tableName(), $this->alias, $this->entityResolver->schemaName()); } - + $this->set($dataset); return $this; } - + protected function selectSqlQuery() : self { if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) { @@ -494,7 +490,7 @@ class Repository return $this; } - + protected function fromRow($row) : self { @@ -504,30 +500,35 @@ class Repository { } - + public function instanciateEntityCollection() : EntityCollection { return $this->entityClass::entityCollection(); } - + + public function instanciateEntity() : object + { + return new $this->entityClass(); + } + public function escapeTable(string $identifier) : string { return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_TABLE); } - + public function escapeDatabase(string $identifier) : string { return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_DATABASE); } - + public function escapeSchema(string $identifier) : string { return $this->adapter->adapter()->escapeIdentifier($identifier, Adapter\AdapterInterface::IDENTIFIER_SCHEMA); } - + protected function matchEntity(object $entity) { return get_class($entity) === $this->entityClass; } - + protected function finalizeQuery() : void {} -} +} \ No newline at end of file From efa957fe58a20a0d62e9181eb807f826a127d947 Mon Sep 17 00:00:00 2001 From: Dave M Date: Fri, 16 Oct 2020 15:27:54 +0000 Subject: [PATCH 2/3] - 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); From bcbf65e0b3b4ee9bb7139bf8634f3c20275bbb68 Mon Sep 17 00:00:00 2001 From: Dave M Date: Tue, 20 Oct 2020 19:40:44 +0000 Subject: [PATCH 3/3] - Fixed in the query builder (still a big WIP) --- src/Annotation/Property/Field.php | 8 ++++---- src/Common/EntityField.php | 2 +- src/Migration/FieldDefinition.php | 16 +++++++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Annotation/Property/Field.php b/src/Annotation/Property/Field.php index 3a6382c..3f97dfb 100644 --- a/src/Annotation/Property/Field.php +++ b/src/Annotation/Property/Field.php @@ -7,23 +7,23 @@ class Field implements \Ulmus\Annotation\Annotation { public string $type; public string $name; - + # public string $column; - + public int $length; public int $precision; public array $attributes = []; - public bool $nullable = false; + public bool $nullable; public function __construct(? string $type = null, ? int $length = null) { if ( $type !== null ) { $this->type = $type; } - + if ( $length !== null ) { $this->length = $length; } diff --git a/src/Common/EntityField.php b/src/Common/EntityField.php index 8a0eb26..781f580 100644 --- a/src/Common/EntityField.php +++ b/src/Common/EntityField.php @@ -58,7 +58,7 @@ class EntityField return implode(" ", [ $definition->getSqlName(), $definition->getSqlType(), - + $definition->getSqlParams(), ]); } diff --git a/src/Migration/FieldDefinition.php b/src/Migration/FieldDefinition.php index c96215c..c129c04 100644 --- a/src/Migration/FieldDefinition.php +++ b/src/Migration/FieldDefinition.php @@ -28,11 +28,12 @@ class FieldDefinition { $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->nullable = isset($field->nullable) ? $field->nullable : $data['nullable']; $this->update = $field->attributes['update'] ?? null; } @@ -41,11 +42,10 @@ class FieldDefinition { return $this->getColumnName(); } - public function getSqlType() : string + public function getSqlType(bool $typeOnly = false) : string { $type = $this->type; $length = $this->length; - $default = $this->getDefault(); switch($type) { case "bool": @@ -86,8 +86,14 @@ class FieldDefinition { break; } + return $typeOnly ? $type : $type . ( $length ? "($length" . ( $precision ? ",$precision" : "" ) . ")" : "" ); + } + + public function getSqlParams() : string + { + $default = $this->getDefault(); + return implode(' ', array_filter([ - $type . ( $length ? "($length" . ( $precision ? ",$precision" : "" ) . ")" : "" ), $this->isUnsigned() ? "UNSIGNED" : null, $this->nullable ? "NULL" : "NOT NULL", $default ? "DEFAULT $default" : null, @@ -112,7 +118,7 @@ class FieldDefinition { protected function getDefault() : ? string { - return $this->getFieldTag()->attributes['default'] ?? null; + return $this->getFieldTag()->attributes['default'] ?? ( $this->nullable ? "NULL" : null ) ; } protected function isPrimaryKey() : bool