Compare commits
7
Commits
v1.0.0
..
8fff21ecb0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fff21ecb0 | ||
|
|
cc6048d4ee | ||
|
|
761e0401b0 | ||
|
|
1ec582d1ac | ||
|
|
9556dea849 | ||
|
|
bfddf84564 | ||
|
|
e0ec140661 |
No files matched your search
@@ -4,7 +4,7 @@ namespace Ulmus\Adapter;
|
||||
|
||||
use Ulmus\{ConnectionAdapter, Entity\InformationSchema\Table, Migration\FieldDefinition, Repository, QueryBuilder\Sql\MysqlQueryBuilder, Entity};
|
||||
|
||||
trait DefaultAdapterTrait
|
||||
UNUSED? trait DefaultAdapterTrait
|
||||
{
|
||||
public function repositoryClass() : string
|
||||
{
|
||||
|
||||
+14
-3
@@ -9,10 +9,14 @@ use Ulmus\Exception\AdapterConfigurationException;
|
||||
use Ulmus\Ulmus;
|
||||
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\{Entity\InformationSchema\Table, Migration\MigrateInterface, Repository, QueryBuilder};
|
||||
use Ulmus\{Entity\InformationSchema\Table,
|
||||
Migration\MigrateInterface,
|
||||
Migration\SqlMigrationTrait,
|
||||
Repository,
|
||||
QueryBuilder};
|
||||
|
||||
class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
use SqlAdapterTrait;
|
||||
use SqlAdapterTrait, SqlMigrationTrait;
|
||||
|
||||
const ALLOWED_ATTRIBUTES = [
|
||||
'default', 'primary_key', 'auto_increment',
|
||||
@@ -205,7 +209,14 @@ class MsSQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
$this->traceOn = $configuration['trace_on'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
|
||||
{
|
||||
$mapper = new MsSQLFieldMapper($field);
|
||||
|
||||
return $typeOnly ? $mapper->type : $mapper->render();
|
||||
}
|
||||
|
||||
public static function escapeIdentifier(string $segment, int $type) : string
|
||||
{
|
||||
switch($type) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Adapter;
|
||||
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
|
||||
class MsSQLFieldMapper extends SqlFieldMapper
|
||||
{
|
||||
public function map() : void
|
||||
{
|
||||
parent::map();
|
||||
|
||||
if (in_array($this->type, [ 'CHAR', 'VARCHAR', 'TEXT', ])) {
|
||||
$this->type = "N" . $this->type;
|
||||
};
|
||||
}
|
||||
|
||||
/* @TODO !
|
||||
public function postProcess() : void
|
||||
{
|
||||
if (
|
||||
in_array($this->type, [ 'BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB', 'JSON', 'TEXT', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'GEOMETRY' ]) &&
|
||||
! is_object($this->field->default ?? false) # Could be a functional default, which would now be valid
|
||||
) {
|
||||
unset($this->field->default);
|
||||
}
|
||||
}
|
||||
* */
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use Ulmus\ConnectionAdapter;
|
||||
use Ulmus\Entity\Mysql\Table;
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\Migration\MigrateInterface;
|
||||
use Ulmus\Migration\SqlMigrationTrait;
|
||||
use Ulmus\QueryBuilder\Sql;
|
||||
use Ulmus\Common\PdoObject;
|
||||
|
||||
@@ -13,7 +14,7 @@ use Ulmus\Exception\AdapterConfigurationException;
|
||||
use Ulmus\Repository;
|
||||
|
||||
class MySQL implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
use SqlAdapterTrait;
|
||||
use SqlAdapterTrait, SqlMigrationTrait;
|
||||
|
||||
const ALLOWED_ATTRIBUTES = [
|
||||
'default', 'primary_key', 'auto_increment', 'update',
|
||||
|
||||
@@ -6,10 +6,6 @@ use Ulmus\Migration\FieldDefinition;
|
||||
|
||||
class MySQLFieldMapper extends SqlFieldMapper
|
||||
{
|
||||
public readonly string $type;
|
||||
|
||||
public readonly string $length;
|
||||
|
||||
public function map() : void
|
||||
{
|
||||
$type = $this->field->type;
|
||||
|
||||
@@ -7,10 +7,10 @@ use Ulmus\Common\PdoObject;
|
||||
use Ulmus\ConnectionAdapter;
|
||||
use Ulmus\Entity;
|
||||
use Ulmus\Migration\FieldDefinition;
|
||||
use Ulmus\{Migration\MigrateInterface, Repository, QueryBuilder};
|
||||
use Ulmus\{Migration\MigrateInterface, Migration\SqlMigrationTrait, Repository, QueryBuilder};
|
||||
|
||||
class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface {
|
||||
use SqlAdapterTrait;
|
||||
use SqlAdapterTrait, SqlMigrationTrait;
|
||||
|
||||
const ALLOWED_ATTRIBUTES = [
|
||||
'default', 'primary_key', 'auto_increment', 'collate nocase', 'collate binary', 'collate rtrim',
|
||||
@@ -27,6 +27,7 @@ class SQLite implements AdapterInterface, MigrateInterface, SqlAdapterInterface
|
||||
public function connect() : PdoObject
|
||||
{
|
||||
try {
|
||||
#$pdo = new PdoObject($this->buildDataSourceName(), null, null);
|
||||
$pdo = new PdoObject\SqlitePdoObject($this->buildDataSourceName(), null, null);
|
||||
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
|
||||
|
||||
@@ -36,44 +36,11 @@ trait SqlAdapterTrait
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
public function schemaTable(ConnectionAdapter $adapter, $databaseName, string $tableName) : null|object
|
||||
{
|
||||
return Table::repository(Repository::DEFAULT_ALIAS, $adapter)
|
||||
->select(\Ulmus\Common\Sql::raw('this.*'))
|
||||
->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName)
|
||||
->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName);
|
||||
}
|
||||
|
||||
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
|
||||
{
|
||||
$mapper = new SqlFieldMapper($field);
|
||||
|
||||
return $typeOnly ? $mapper->type : $mapper->render();
|
||||
}
|
||||
|
||||
public function whitelistAttributes(array &$parameters) : void
|
||||
{
|
||||
$parameters = array_intersect_key($parameters, array_flip(static::ALLOWED_ATTRIBUTES));
|
||||
}
|
||||
|
||||
public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable
|
||||
{
|
||||
if (! empty($field['previous']) ) {
|
||||
$position = sprintf('AFTER %s', $this->escapeIdentifier($field['previous']->field, AdapterInterface::IDENTIFIER_FIELD));
|
||||
}
|
||||
else {
|
||||
$position = "FIRST";
|
||||
}
|
||||
|
||||
return implode(" ", [
|
||||
strtoupper($field['action']),
|
||||
$this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD),
|
||||
$definition->getSqlType(),
|
||||
$definition->getSqlParams(),
|
||||
$position,
|
||||
]);
|
||||
}
|
||||
|
||||
public function writableValue(mixed $value) : mixed
|
||||
{
|
||||
switch (true) {
|
||||
@@ -92,9 +59,4 @@ trait SqlAdapterTrait
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function splitAlterQuery() : bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ use Ulmus\Entity;
|
||||
|
||||
class SqlFieldMapper
|
||||
{
|
||||
public readonly string $type;
|
||||
public string $type;
|
||||
|
||||
public readonly string $length;
|
||||
public string $length;
|
||||
|
||||
public function __construct(
|
||||
public FieldDefinition $field,
|
||||
|
||||
@@ -10,6 +10,12 @@ class Attribute
|
||||
public static function handleArrayField(null|\Stringable|string|array $field, null|string|bool $alias = Repository::DEFAULT_ALIAS, string $separator = ', ') : mixed
|
||||
{
|
||||
if ( is_array($field) ) {
|
||||
if (count($field) < 2) {
|
||||
throw new \RuntimeException(
|
||||
sprintf("Array field must be formed of at least two things, a class name, and a property. (received %s)", json_encode($field))
|
||||
);
|
||||
}
|
||||
|
||||
$class = array_shift($field);
|
||||
$field[1] ??= $alias;
|
||||
|
||||
|
||||
@@ -11,4 +11,4 @@ class Index {
|
||||
public IndexTypeEnum $type = IndexTypeEnum::Unique,
|
||||
public null|string $name = null,
|
||||
) {}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ class Virtual extends Field implements ResettablePropertyInterface {
|
||||
public function __construct(
|
||||
public null|string|array $method = null,
|
||||
public ? \Closure $closure = null,
|
||||
public null|string $name = null,
|
||||
) {
|
||||
$this->method ??= [ static::class, 'noop' ];
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Ulmus\Common;
|
||||
use Notes\Common\ReflectedClass;
|
||||
use Notes\Common\ReflectedProperty;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use Ulmus\Attribute\Obj\Index;
|
||||
use Ulmus\Ulmus,
|
||||
Ulmus\Attribute\Obj\Table,
|
||||
Ulmus\Attribute\Obj\AdapterAttributeInterface,
|
||||
@@ -113,7 +114,12 @@ class EntityResolver {
|
||||
throw new \InvalidArgumentException("Can't find entity relation's column named `$name` from entity {$this->entityClass}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getPropertyEntityType(string $name) : false|string
|
||||
{
|
||||
return $this->reflectedClass->getProperties(true)[$name]->getTypes()[0]->type ?? false;
|
||||
}
|
||||
|
||||
public function searchFieldAnnotation(string $field, array|object|string $annotationType, bool $caseSensitive = true) : ? object
|
||||
{
|
||||
return $this->searchFieldAnnotationList($field, $annotationType, $caseSensitive)[0] ?? null;
|
||||
@@ -214,9 +220,9 @@ class EntityResolver {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getCompoundKeyFields() : ? array
|
||||
public function getCompoundKeyFields() : ? Index
|
||||
{
|
||||
return null;
|
||||
return $this->getAttributeImplementing(Index::class);
|
||||
}
|
||||
|
||||
public function getUniqueFields() : ? array
|
||||
|
||||
@@ -56,7 +56,7 @@ class PdoObject extends PDO {
|
||||
}
|
||||
}
|
||||
catch (\Throwable $e) {
|
||||
throw new \PdoException($e->getMessage() . " `$sql` with data:" . json_encode($parameters));
|
||||
throw new \PdoException($e->getMessage() . " `$sql` with data:" . json_encode($parameters), (int) $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -83,7 +83,7 @@ class PdoObject extends PDO {
|
||||
$this->lastInsertId = null;
|
||||
|
||||
try {
|
||||
if ( ! $this->inTransaction() ) {
|
||||
if (! $this->inTransaction()) {
|
||||
$this->beginTransaction();
|
||||
}
|
||||
|
||||
@@ -113,8 +113,6 @@ class PdoObject extends PDO {
|
||||
catch (\Throwable $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function bindVariables(PDOStatement $statement, array &$parameters) : void
|
||||
|
||||
@@ -12,7 +12,7 @@ class SqlPdoObject extends \Ulmus\Common\PdoObject
|
||||
return $this->openTransaction();
|
||||
}
|
||||
|
||||
return $this->exec('SAVEPOINT transaction_'.$this->openedTransaction) !== false;
|
||||
return $this->exec("SAVEPOINT transaction_{$this->openedTransaction}") !== false;
|
||||
}
|
||||
|
||||
public function commit() : bool
|
||||
@@ -21,16 +21,19 @@ class SqlPdoObject extends \Ulmus\Common\PdoObject
|
||||
return parent::commit();
|
||||
}
|
||||
|
||||
return false;
|
||||
return $this->exec("RELEASE SAVEPOINT transaction_{$this->openedTransaction}") !== false;
|
||||
}
|
||||
|
||||
public function rollback() : bool
|
||||
{
|
||||
if (0 !== $this->openedTransaction) {
|
||||
if ($this->openedTransaction > 1) {
|
||||
return $this->exec('ROLLBACK TO transaction_' . $this->openedTransaction--) !== false;
|
||||
}
|
||||
elseif ($this->openedTransaction-- === 1) {
|
||||
return parent::rollback();
|
||||
}
|
||||
|
||||
return parent::rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function openTransaction() : bool
|
||||
|
||||
@@ -17,6 +17,19 @@ class SqlitePdoObject extends SqlPdoObject
|
||||
return $this->exec("COMMIT") !== false;
|
||||
}
|
||||
|
||||
return $this->exec("RELEASE SAVEPOINT transaction_{$this->openedTransaction}") !== false;
|
||||
}
|
||||
|
||||
public function rollback() : bool
|
||||
{
|
||||
if ($this->openedTransaction > 1) {
|
||||
return $this->exec('ROLLBACK TO transaction_' . $this->openedTransaction--) !== false;
|
||||
}
|
||||
elseif ($this->openedTransaction-- === 1) {
|
||||
# We must do it manually since opening a transaction manually stucks PDO into thinking we've got no transaction opened
|
||||
return $this->exec('ROLLBACK') !== false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,27 @@
|
||||
|
||||
namespace Ulmus\Container;
|
||||
|
||||
use Ulmus\ConnectionAdapter;
|
||||
|
||||
class AdapterProxy
|
||||
{
|
||||
public array $connections = [];
|
||||
|
||||
public function __construct(...$arguments)
|
||||
{
|
||||
$this->connections = $arguments;
|
||||
$this->push(array_filter($arguments));
|
||||
}
|
||||
|
||||
public function push(array|ConnectionAdapter $connection) : void
|
||||
{
|
||||
foreach((array) $connection as $adapter) {
|
||||
foreach($this->connections as $existing) {
|
||||
if ($adapter === $existing) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$this->connections[] = $adapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,10 @@ class DatasetHandler
|
||||
}
|
||||
elseif ( $field->expectType('array') ) {
|
||||
if ( is_string($value)) {
|
||||
if (substr($value, 0, 1) === "a") {
|
||||
if (empty($value)) {
|
||||
yield $field->name => [];
|
||||
}
|
||||
elseif (substr($value, 0, 1) === "a") {
|
||||
|
||||
yield $field->name => unserialize($value);
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@ class Column
|
||||
{
|
||||
use \Ulmus\EntityTrait;
|
||||
|
||||
#[Field\Id]
|
||||
public ? id $srs_id;
|
||||
|
||||
#[Field(name: "TABLE_CATALOG", length: 512)]
|
||||
public string $tableCatalog;
|
||||
|
||||
@@ -64,24 +61,10 @@ class Column
|
||||
# #[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;
|
||||
|
||||
public function matchFieldDefinition(ReflectedProperty $definition) : bool
|
||||
{
|
||||
$nullable = $this->nullable === 'YES';
|
||||
|
||||
@@ -2,8 +2,28 @@
|
||||
|
||||
namespace Ulmus\Entity\Mysql;
|
||||
|
||||
use Ulmus\Attribute\Property\Field;
|
||||
|
||||
class Column extends \Ulmus\Entity\InformationSchema\Column
|
||||
{
|
||||
#[Field\Id]
|
||||
public ? id $srs_id;
|
||||
|
||||
#[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: "GENERATION_EXPRESSION", type: "longtext")]
|
||||
public ? string $generationExpression;
|
||||
|
||||
# TODO ! Handle FUNCTIONAL default value
|
||||
protected function canHaveDefaultValue(): bool
|
||||
{
|
||||
@@ -12,4 +32,5 @@ class Column extends \Ulmus\Entity\InformationSchema\Column
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -343,7 +343,20 @@ class EntityCollection extends \ArrayObject implements \JsonSerializable {
|
||||
$list[] = $entity instanceof \JsonSerializable ? $entity->jsonSerialize() : $entity->toArray($includeRelations);
|
||||
}
|
||||
|
||||
return $list;
|
||||
return $this->utf8ize($list, "UTF-8", "UTF-8");
|
||||
}
|
||||
|
||||
protected function utf8ize(mixed $content) {
|
||||
if (is_array($content)) {
|
||||
foreach ($content as $key => $value) {
|
||||
$content[$key] = $this->utf8ize($value);
|
||||
}
|
||||
}
|
||||
elseif (is_string($content)) {
|
||||
return mb_convert_encoding($content, "UTF-8", "UTF-8");
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function fromArray(array $datasets, ? string /*stringable*/ $entityClass = null) : self
|
||||
|
||||
+21
-7
@@ -43,12 +43,12 @@ trait EntityTrait {
|
||||
#[Ignore]
|
||||
public function initializeEntity(iterable|null $dataset = null) : void
|
||||
{
|
||||
$this->datasetHandler = new DatasetHandler(static::resolveEntity(), $this->entityStrictFieldsDeclaration);
|
||||
|
||||
if ($dataset) {
|
||||
$this->fromArray($dataset);
|
||||
}
|
||||
|
||||
$this->datasetHandler = new DatasetHandler(static::resolveEntity(), $this->entityStrictFieldsDeclaration);
|
||||
|
||||
$this->resetVirtualProperties();
|
||||
}
|
||||
|
||||
@@ -144,13 +144,27 @@ trait EntityTrait {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( null === $pkField = $this->resolveEntity()->getPrimaryKeyField($this) ) {
|
||||
throw new Exception\EntityPrimaryKeyUnknown(sprintf("Entity %s has no field containing attributes 'primary_key'", static::class));
|
||||
if ( null === $pkField = $this->resolveEntity()->getPrimaryKeyField() ) {
|
||||
if ( null !== $compoundKeyFields = $this->resolveEntity()->getCompoundKeyFields() ) {
|
||||
$loaded = false;
|
||||
|
||||
foreach ($compoundKeyFields->column as $column) {
|
||||
$field = $this->resolveEntity()->field($column);
|
||||
|
||||
if (! $field->allowsNull() ) {
|
||||
$loaded |= isset($this->{$field->name});
|
||||
}
|
||||
}
|
||||
|
||||
return $loaded;
|
||||
};
|
||||
}
|
||||
else {
|
||||
$key = key($pkField);
|
||||
return isset($this->$key);
|
||||
}
|
||||
|
||||
$key = key($pkField);
|
||||
|
||||
return isset($this->$key);
|
||||
throw new Exception\EntityPrimaryKeyUnknown(sprintf("Entity %s has no field containing attributes 'primary_key'", static::class));
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
|
||||
@@ -5,4 +5,4 @@ namespace Ulmus\Migration\Sql;
|
||||
class SqliteMigration
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Migration;
|
||||
|
||||
use Ulmus\Adapter\AdapterInterface;
|
||||
use Ulmus\Adapter\SqlFieldMapper;
|
||||
use Ulmus\ConnectionAdapter;
|
||||
use Ulmus\Entity\Mysql\Table;
|
||||
use Ulmus\Repository;
|
||||
|
||||
trait SqlMigrationTrait
|
||||
{
|
||||
protected array $QUERY_REPLACE = [
|
||||
'change' => "alter",
|
||||
];
|
||||
|
||||
public function schemaTable(ConnectionAdapter $adapter, $databaseName, string $tableName) : null|object
|
||||
{
|
||||
return \Ulmus\Entity\InformationSchema\Table::repository(Repository::DEFAULT_ALIAS, $adapter)
|
||||
->select(\Ulmus\Common\Sql::raw('this.*'))
|
||||
->where($this->escapeIdentifier('table_schema', AdapterInterface::IDENTIFIER_FIELD), $databaseName)
|
||||
->or($this->escapeIdentifier('table_catalog', AdapterInterface::IDENTIFIER_FIELD), $databaseName)
|
||||
->loadOneFromField($this->escapeIdentifier('table_name', AdapterInterface::IDENTIFIER_FIELD), $tableName);
|
||||
}
|
||||
|
||||
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
|
||||
{
|
||||
$mapper = new SqlFieldMapper($field);
|
||||
|
||||
return $typeOnly ? $mapper->type : $mapper->render();
|
||||
}
|
||||
|
||||
public function generateAlterColumn(FieldDefinition $definition, array $field) : string|\Stringable
|
||||
{
|
||||
if ($field['action'] === 'add') {
|
||||
if (! empty($field['previous'])) {
|
||||
$position = sprintf('AFTER %s', $this->escapeIdentifier($field['previous']->field, AdapterInterface::IDENTIFIER_FIELD));
|
||||
}
|
||||
else {
|
||||
$position = "FIRST";
|
||||
}
|
||||
}
|
||||
|
||||
return implode(" ", array_filter([
|
||||
strtoupper($field['action']),
|
||||
$this->escapeIdentifier($definition->getSqlName(), AdapterInterface::IDENTIFIER_FIELD),
|
||||
$definition->getSqlType(),
|
||||
$definition->getSqlParams(),
|
||||
$position ?? null,
|
||||
]));
|
||||
}
|
||||
|
||||
public function splitAlterQuery() : bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ class Select extends Fragment {
|
||||
|
||||
public bool $union = false;
|
||||
|
||||
public bool $isInternalSelect = false;
|
||||
|
||||
public ? int $top = null;
|
||||
|
||||
protected array $fields = [];
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Ulmus\Query\Sqlite;
|
||||
|
||||
use Ulmus\Query\Fragment;
|
||||
|
||||
class Truncate extends Fragment {
|
||||
|
||||
const SQL_TOKEN = "DELETE FROM";
|
||||
|
||||
public int $order = -100;
|
||||
|
||||
public string $table;
|
||||
|
||||
public function set(string $tableName) : self
|
||||
{
|
||||
$this->table = $tableName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() : string
|
||||
{
|
||||
return $this->renderSegments([
|
||||
static::SQL_TOKEN, $this->table,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class MysqlQueryBuilder extends SqlQueryBuilder
|
||||
$this->push($select);
|
||||
}
|
||||
|
||||
$select->distinct = $distinct;
|
||||
$select->distinct = $select->distinct || $distinct;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Ulmus\QueryBuilder\Sql;
|
||||
|
||||
use Ulmus\Query;
|
||||
use Ulmus\Adapter\AdapterInterface;
|
||||
|
||||
# Soon to extends SqlQueryBuilder
|
||||
class SqliteQueryBuilder extends MysqlQueryBuilder
|
||||
@@ -38,4 +39,41 @@ class SqliteQueryBuilder extends MysqlQueryBuilder
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function insert(array $fieldlist, string $table, ? string $alias = null, ? string $database = null, ? string $schema = null, bool $replace = false) : self
|
||||
{
|
||||
return parent::insert($fieldlist, $table, $alias, null, null, $replace);
|
||||
}
|
||||
|
||||
public function update(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
return parent::update($table, $alias, null, null);
|
||||
}
|
||||
|
||||
public function create(AdapterInterface $adapter, array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
return parent::create($adapter, $fieldlist, $table, null, null);
|
||||
}
|
||||
|
||||
public function alter(AdapterInterface $adapter, array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
return parent::alter($adapter, $fieldlist, $table, null, null);
|
||||
}
|
||||
|
||||
public function from(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
return parent::from($table, $alias, null, null);
|
||||
}
|
||||
|
||||
public function truncate(string $table, ? string $alias = null, ? string $database = null, ? string $schema = null) : self
|
||||
{
|
||||
$truncate = new Query\Sqlite\Truncate($this);
|
||||
|
||||
$this->push($truncate);
|
||||
|
||||
$truncate->set($table);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace Ulmus\QueryBuilder;
|
||||
use Ulmus\Query\QueryFragmentInterface;
|
||||
|
||||
# TODO -> Extract from MysqlQueryBuilder to build an ISO/IEC 9075:2023 compatible layer for a basic SQL QueryBuilder
|
||||
class SqlQueryBuilder implements QueryBuilderInterface
|
||||
abstract class SqlQueryBuilder implements QueryBuilderInterface
|
||||
{
|
||||
protected string $rendered;
|
||||
|
||||
|
||||
+61
-23
@@ -97,7 +97,7 @@ class Repository implements RepositoryInterface
|
||||
else {
|
||||
$pk = Ulmus::resolveEntity($this->entityClass)->getPrimaryKeyField();
|
||||
|
||||
if (count($pk) === 1) {
|
||||
if ($pk && count($pk) === 1) {
|
||||
$field = key($pk);
|
||||
|
||||
$this->select(Common\Sql::function('COUNT', Common\Sql::raw("DISTINCT " . $this->entityClass::field($field))));
|
||||
@@ -216,7 +216,12 @@ class Repository implements RepositoryInterface
|
||||
}
|
||||
else {
|
||||
if ( $primaryKeyDefinition === null ) {
|
||||
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
|
||||
if ( null !== $compoundKeyFields = Ulmus::resolveEntity($this->entityClass)->getCompoundKeyFields() ) {
|
||||
throw new \Exception("TO DO!");
|
||||
}
|
||||
else {
|
||||
throw new \Exception(sprintf("No primary key found for entity %s", $this->entityClass));
|
||||
}
|
||||
}
|
||||
|
||||
$diff = $fieldsAndValue ?? $this->generateWritableDataset($entity);
|
||||
@@ -319,6 +324,10 @@ class Repository implements RepositoryInterface
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($e1) || is_null($e2)) {
|
||||
return $e1 !== $e2;
|
||||
}
|
||||
|
||||
return (string) $e1 !== (string) $e2;
|
||||
});
|
||||
}
|
||||
@@ -407,24 +416,22 @@ class Repository implements RepositoryInterface
|
||||
|
||||
public function withJoin(string|array $fields, array $options = []) : self
|
||||
{
|
||||
if ( null === $this->queryBuilder->getFragment(Query\Select::class) ) {
|
||||
$selectObj = $this->queryBuilder->getFragment(Query\Select::class);
|
||||
$canSelect = empty($selectObj) || $selectObj->isInternalSelect === true;
|
||||
|
||||
if ( $canSelect ) {
|
||||
$select = $this->entityResolver->fieldList(EntityResolver::KEY_COLUMN_NAME, true);
|
||||
$this->select($this->entityClass::fields(array_map(fn($f) => $f['object']->name ?? $f['name'], $select)));
|
||||
}
|
||||
|
||||
# @TODO Apply FILTER annotation to this too !
|
||||
foreach(array_filter((array) $fields) as $item) {
|
||||
if ( isset($this->joined[$item]) ) {
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
$this->joined[$item] = true;
|
||||
}
|
||||
foreach(array_filter((array) $fields, fn($e) => ! isset($this->joined[$e])) as $item) {
|
||||
$this->joined[$item] = true;
|
||||
|
||||
$attribute = $this->entityResolver->searchFieldAnnotation($item, [ Join::class ]) ?:
|
||||
$this->entityResolver->searchFieldAnnotation($item, [ Relation::class ]);
|
||||
|
||||
$isRelation = ( $attribute instanceof Relation ) || ($attribute instanceof Relation);
|
||||
$isRelation = $attribute instanceof Relation;
|
||||
|
||||
if ($isRelation && ( $attribute->isManyToMany() )) {
|
||||
throw new \Exception("Many-to-many relation can not be preloaded within joins.");
|
||||
@@ -433,7 +440,7 @@ class Repository implements RepositoryInterface
|
||||
if ( $attribute ) {
|
||||
$alias = $attribute->alias ?? $item;
|
||||
|
||||
$entity = $attribute->entity ?? $this->entityResolver->reflectedClass->getProperties(true)[$item]->getTypes()[0]->type;
|
||||
$entity = $attribute->entity ?? $this->entityResolver->getPropertyEntityType($item);
|
||||
|
||||
foreach($entity::resolveEntity()->fieldList(Common\EntityResolver::KEY_COLUMN_NAME, true) as $key => $field) {
|
||||
if ( null === $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Relation\Ignore::class ]) ) {
|
||||
@@ -442,20 +449,21 @@ class Repository implements RepositoryInterface
|
||||
|
||||
$name = $entity::resolveEntity()->searchFieldAnnotation($field->name, [ Field::class ])->name ?? $field->name;
|
||||
|
||||
|
||||
$this->select("$escAlias.$fieldName as $alias\${$name}");
|
||||
if ($canSelect) {
|
||||
$this->select("$escAlias.$fieldName as $alias\${$name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->open();
|
||||
|
||||
if ( ! in_array(WithOptionEnum::SkipWhere, $options)) {
|
||||
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ] ) as $condition) {
|
||||
if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) {
|
||||
$this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
|
||||
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ] ) as $condition) {
|
||||
if ( is_object($condition->field) && ( $condition->field->entityClass !== $entity ) ) {
|
||||
$this->where(is_object($condition->field) ? $condition->field : $entity::field($condition->field), $condition->getValue(), $condition->operator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! in_array(WithOptionEnum::SkipHaving, $options)) {
|
||||
foreach ($this->entityResolver->searchFieldAnnotationList($item, [ Having::class ]) as $condition) {
|
||||
@@ -473,9 +481,9 @@ class Repository implements RepositoryInterface
|
||||
|
||||
$key = is_string($attribute->key) ? $this->entityClass::field($attribute->key) : $attribute->key;
|
||||
|
||||
$foreignKey = is_string($attribute->foreignKey) ? $entity::field($attribute->foreignKey, $alias) : $attribute->foreignKey;
|
||||
$foreignKey = $this->evalClosure($attribute->foreignKey, $entity, $alias);
|
||||
|
||||
$this->join("LEFT", $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias, $options) {
|
||||
$this->join($isRelation ? "LEFT" : $attribute->type, $entity::resolveEntity()->tableName(), $key, $foreignKey, $alias, function($join) use ($item, $entity, $alias, $options) {
|
||||
if ( ! in_array(WithOptionEnum::SkipJoinWhere, $options)) {
|
||||
foreach($this->entityResolver->searchFieldAnnotationList($item, [ Where::class ]) as $condition) {
|
||||
if ( ! is_object($condition->field) ) {
|
||||
@@ -506,6 +514,12 @@ class Repository implements RepositoryInterface
|
||||
}
|
||||
}
|
||||
|
||||
if ($canSelect) {
|
||||
if ( $selectObj ??= $this->queryBuilder->getFragment(Query\Select::class) ) {
|
||||
$selectObj->isInternalSelect = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -537,7 +551,7 @@ class Repository implements RepositoryInterface
|
||||
->selectJsonEntity($relationRelation->entity, $alias)->open();
|
||||
}
|
||||
else {
|
||||
$entity = $relation->entity ?? $this->entityResolver->properties[$item]['type'];
|
||||
$entity = $relation->entity ?? $this->entityResolver->getPropertyEntityType($name);
|
||||
$repository = $entity::repository()->selectJsonEntity($entity, $alias)->open();
|
||||
}
|
||||
|
||||
@@ -578,7 +592,7 @@ class Repository implements RepositoryInterface
|
||||
$order = $this->entityResolver->searchFieldAnnotationList($name, [ OrderBy::class ]);
|
||||
$where = $this->entityResolver->searchFieldAnnotationList($name, [ Where::class ]);
|
||||
|
||||
$baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->properties[$name]['type'];
|
||||
$baseEntity = $relation->entity ?? $relation->bridge ?? $this->entityResolver->getPropertyEntityType($name);
|
||||
$baseEntityResolver = $baseEntity::resolveEntity();
|
||||
|
||||
$property = ($baseEntityResolver->field($relation->foreignKey, 01, false) ?: $baseEntityResolver->field($relation->foreignKey, 02))['name'];
|
||||
@@ -632,6 +646,22 @@ class Repository implements RepositoryInterface
|
||||
}
|
||||
}
|
||||
|
||||
## AWAITING THE Closures in constant expression from PHP 8.5 !
|
||||
protected function evalClosure(string $content, string $entityClass, mixed $alias = self::DEFAULT_ALIAS) : mixed
|
||||
{
|
||||
if (is_string($content)) {
|
||||
if ( str_starts_with($content, 'fn(') ) {
|
||||
$closure = eval("return $content;");
|
||||
|
||||
return $closure($this);
|
||||
}
|
||||
|
||||
return $entityClass::field($content, $alias);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function filterServerRequest(SearchRequest\SearchRequestInterface $searchRequest, bool $count = true) : self
|
||||
{
|
||||
if ($count) {
|
||||
@@ -736,7 +766,15 @@ class Repository implements RepositoryInterface
|
||||
|
||||
public function instanciateEntity(? string $entityClass = null) : object
|
||||
{
|
||||
$entity = ( new \ReflectionClass($entityClass ?? $this->entityClass) )->newInstanceWithoutConstructor();
|
||||
$entityClass ??= $this->entityClass;
|
||||
|
||||
try {
|
||||
$entity = new $entityClass();
|
||||
}
|
||||
catch (\Throwable $ex) {
|
||||
$entity = ( new \ReflectionClass($entityClass) )->newInstanceWithoutConstructor();
|
||||
}
|
||||
|
||||
$entity->initializeEntity();
|
||||
|
||||
return $entity;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace Ulmus\Repository;
|
||||
|
||||
use Ulmus\{
|
||||
Ulmus, Query, Common\EntityResolver, Repository, Event,
|
||||
Ulmus, Query, Common\EntityResolver, Repository, Event, Common\Sql
|
||||
};
|
||||
|
||||
use Ulmus\Attribute\Property\{
|
||||
@@ -57,7 +57,7 @@ class RelationBuilder
|
||||
|
||||
}
|
||||
else {
|
||||
if ( $relation = $this->resolver->searchFieldAnnotation($name, [ Relation::class ] ) ) {
|
||||
if ( $relation = $this->resolver->searchFieldAnnotation($name, [ Relation::class, Join::class ] ) ) {
|
||||
return $this->instanciateEmptyObject($name, $relation);
|
||||
}
|
||||
elseif ($virtual = $this->resolveVirtual($name)) {
|
||||
@@ -276,11 +276,16 @@ class RelationBuilder
|
||||
if ( $relation->generateKey ) {
|
||||
$value = call_user_func_array($relation->generateKey, [ $this->entity ]);
|
||||
}
|
||||
else {
|
||||
elseif (isset($this->entity->$field)) {
|
||||
$value = $this->entity->$field;
|
||||
}
|
||||
|
||||
$this->repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey), $value );
|
||||
if (isset($value)) {
|
||||
$this->repository->where( is_object($relation->foreignKey) ? $relation->foreignKey : $baseEntity::field($relation->foreignKey), $value );
|
||||
}
|
||||
else {
|
||||
$this->repository->where(Sql::raw('TRUE'), Sql::raw('FALSE'));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->applyFilter($this->repository, $name);
|
||||
@@ -340,7 +345,7 @@ class RelationBuilder
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
protected function fieldIsNullable(string $name) : bool
|
||||
{
|
||||
return $this->resolver->reflectedClass->getProperties(true)[$name]->allowsNull();
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Ulmus\Repository;
|
||||
enum WithOptionEnum
|
||||
{
|
||||
case SkipWhere;
|
||||
case SkipHaving;
|
||||
case SkipHaving;
|
||||
case SkipFilter;
|
||||
case SkipOrderBy;
|
||||
case SkipJoinWhere;
|
||||
|
||||
@@ -20,6 +20,6 @@ abstract class SearchParameter {
|
||||
|
||||
public function getParameters() : array
|
||||
{
|
||||
return array_filter((array) $this->parameter);
|
||||
return array_filter((array) ( $this->parameter ?? [] ));
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,8 @@ trait SearchRequestFromRequestTrait
|
||||
case $attribute instanceof SearchManual:
|
||||
if ($attribute->toggle) {
|
||||
$this->$propertyName = !empty($value);
|
||||
} elseif ($value !== null) {
|
||||
}
|
||||
elseif ($value !== null) {
|
||||
foreach ($property->getTypes() as $type) {
|
||||
$enum = $type->type;
|
||||
|
||||
|
||||
Reference in new issue
Block a user