- Fixed conflicts

This commit is contained in:
Dave Mc Nicoll 2022-04-13 03:37:02 +00:00
commit 325704260b
31 changed files with 632 additions and 127 deletions

View File

@ -3,6 +3,7 @@
namespace Ulmus\Adapter; namespace Ulmus\Adapter;
use Ulmus\Common\PdoObject; use Ulmus\Common\PdoObject;
use Ulmus\Migration\FieldDefinition;
interface AdapterInterface { interface AdapterInterface {
public const IDENTIFIER_FIELD = 1; public const IDENTIFIER_FIELD = 1;
@ -10,11 +11,20 @@ interface AdapterInterface {
public const IDENTIFIER_DATABASE = 3; public const IDENTIFIER_DATABASE = 3;
public const IDENTIFIER_SCHEMA = 4; public const IDENTIFIER_SCHEMA = 4;
public const IDENTIFIER_VALUE = 5; public const IDENTIFIER_VALUE = 5;
public function connect() : object /* | PdoObject|mixed */; public function connect() : object /* | PdoObject|mixed */;
public function buildDataSourceName() : string; public function buildDataSourceName() : string;
public function setup(array $configuration) : void; public function setup(array $configuration) : void;
public function escapeIdentifier(string $segment, int $type) : string; public function escapeIdentifier(string $segment, int $type) : string;
public function defaultEngine() : ? string; public function defaultEngine() : ? string;
public function writableValue(/* mixed */ $value) /*: mixed*/;
public function writableValue(/* mixed */ $value); /*: mixed*/
/* public function databaseName() : string;
public function mapFieldType(FieldDefinition $field) : string;
public function schemaTable(string $databaseName, string $tableName) /*: object|EntityCollection
public function repositoryClass() : string;
public function queryBuilderClass() : string;
public function tableSyntax() : array; */
} }

View File

@ -0,0 +1,95 @@
<?php
namespace Ulmus\Adapter;
use Ulmus\{Entity\InformationSchema\Table, Migration\FieldDefinition, Repository, QueryBuilder};
trait DefaultAdapterTrait
{
public function repositoryClass() : string
{
return Repository::class;
}
public function queryBuilderClass() : string
{
return QueryBuilder::class;
}
public function tableSyntax() : array
{
return [
'ai' => "AUTO_INCREMENT",
'pk' => "PRIMARY KEY",
'unsigned' => "UNSIGNED",
];
}
public function databaseName() : string
{
return $this->database;
}
public function schemaTable(string $databaseName, string $tableName) /* : ? object */
{
return Table::repository()->where(Table::field('schema'), $databaseName)->loadOneFromField(Table::field('name'), $tableName);
}
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
{
$type = $field->type;
$length = $field->length;
if ( is_a($type, Entity\Field\Date::class, true) ) {
$type = "DATE";
}
elseif ( is_a($type, Entity\Field\Time::class, true) ) {
$type = "TIME";
}
elseif ( is_a($type, \DateTime::class, true) ) {
$type = "DATETIME";
}
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 $typeOnly ? $type : $type . ( $length ? "($length" . ( $precision ? ",$precision" : "" ) . ")" : "" );
}
}

View File

@ -5,9 +5,16 @@ namespace Ulmus\Adapter;
use Ulmus\Common\PdoObject; use Ulmus\Common\PdoObject;
use Ulmus\Exception\AdapterConfigurationException; use Ulmus\Exception\AdapterConfigurationException;
use Ulmus\Ulmus; use Ulmus\Ulmus;
use Ulmus\Migration\FieldDefinition;
use Ulmus\{Entity\InformationSchema\Table, Repository, QueryBuilder};
class MsSQL implements AdapterInterface { class MsSQL implements AdapterInterface {
use DefaultAdapterTrait;
const DSN_PREFIX = "sqlsrv";
public int $port; public int $port;
@ -130,7 +137,7 @@ class MsSQL implements AdapterInterface {
$parts[] = "WSID={$this->wsid}"; $parts[] = "WSID={$this->wsid}";
} }
return "sqlsrv:" . implode(';', $parts); return static::DSN_PREFIX . ":" . implode(';', $parts);
} }
public function setup(array $configuration) : void public function setup(array $configuration) : void

View File

@ -2,12 +2,19 @@
namespace Ulmus\Adapter; namespace Ulmus\Adapter;
use Ulmus\Entity\InformationSchema\Table;
use Ulmus\QueryBuilder;
use Ulmus\Repository;
use Ulmus\Common\PdoObject; use Ulmus\Common\PdoObject;
use Ulmus\Exception\AdapterConfigurationException; use Ulmus\Exception\AdapterConfigurationException;
use Ulmus\Ulmus; use Ulmus\Ulmus;
use Ulmus\Migration\FieldDefinition;
class MySQL implements AdapterInterface { class MySQL implements AdapterInterface {
use DefaultAdapterTrait;
const DSN_PREFIX = "mysql";
public string $hostname; public string $hostname;
@ -18,7 +25,7 @@ class MySQL implements AdapterInterface {
public string $password; public string $password;
public string $charset = "utf8mb4"; public string $charset = "utf8mb4";
public ? string $socket; public ? string $socket;
public int $port = 3306; public int $port = 3306;
@ -69,6 +76,9 @@ class MySQL implements AdapterInterface {
catch(PDOException $ex){ catch(PDOException $ex){
throw $ex; throw $ex;
} }
finally {
$this->password = str_repeat('*', strlen($this->password));
}
return $pdo; return $pdo;
} }
@ -92,7 +102,7 @@ class MySQL implements AdapterInterface {
$parts[] = "charset={$this->charset}"; $parts[] = "charset={$this->charset}";
} }
return "mysql:" . implode(';', $parts); return static::DSN_PREFIX . ":" . implode(';', $parts);
} }
public function setup(array $configuration) : void public function setup(array $configuration) : void

140
src/Adapter/SQLite.php Normal file
View File

@ -0,0 +1,140 @@
<?php
namespace Ulmus\Adapter;
use Ulmus\Common\PdoObject;
use Ulmus\Entity\Sqlite\Table;
use Ulmus\Exception\AdapterConfigurationException;
use Ulmus\Migration\FieldDefinition;
use Ulmus\{ Repository, QueryBuilder };
class SQLite implements AdapterInterface {
use DefaultAdapterTrait;
const DSN_PREFIX = "sqlite";
public string $path;
public array $pragma;
public function __construct(
? string $path = null,
? array $pragma = null
) {
if ($path !== null) {
$this->path = $path;
}
if ($pragma !== null) {
$this->pragma = $pragma;
}
}
public function connect() : PdoObject
{
try {
$pdo = new PdoObject($this->buildDataSourceName(), null, null);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
}
catch(PDOException $ex){
throw $ex;
}
return $pdo;
}
public function buildDataSourceName() : string
{
$parts[] = $this->path;
return static::DSN_PREFIX . ":" . implode(';', $parts);
}
public function setup(array $configuration) : void
{
$this->path = $configuration['path'] ?? "";
$this->pragma = $configuration['pragma'] ?? [];
}
# https://sqlite.org/lang_keywords.html
public function escapeIdentifier(string $segment, int $type) : string
{
switch($type) {
case static::IDENTIFIER_DATABASE:
case static::IDENTIFIER_TABLE:
case static::IDENTIFIER_FIELD:
return '"' . trim(str_replace('"', '""', $segment), '"') . '"';
case static::IDENTIFIER_VALUE:
return "'$segment'";
}
}
public function defaultEngine(): ? string
{
return null;
}
public function databaseName() : string
{
$base = basename($this->path);
return substr($base, 0, strrpos($base, '.') ?: strlen($base));
}
public function schemaTable(string $databaseName, string $tableName) /* : ? object */
{
return Table::repository()->loadOneFromField(Table::field('tableName'), $tableName);
}
public function mapFieldType(FieldDefinition $field, bool $typeOnly = false) : string
{
$type = $field->type;
$length = $field->length;
if ( is_a($type, Entity\Field\Date::class, true) || is_a($type, Entity\Field\Time::class, true) || is_a($type, \DateTime::class, true) ) {
$type = "TEXT";
$length = strlen((string) $type);
}
else {
switch($type) {
case "bool":
$check = sprintf("CHECK (%s IN (0, 1))", $field->getColumnName());
case "bigint":
case "int":
$type = "INTEGER";
break;
case "array":
case "string":
$type = "TEXT";
$length = null;
break;
case "float":
$type = "REAL";
break;
default:
$type = "BLOB";
break;
}
}
return $typeOnly ? $type : $type . ( $length ? "($length" . ( $precision ? ",$precision" : "" ) . ")" : "" );
}
public function tableSyntax() : array
{
return [
'ai' => "AUTOINCREMENT",
'pk' => "PRIMARY KEY",
'unsigned' => "",
];
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Blob extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "blob", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Longblob extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "longblob", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Mediumblob extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "mediumblob", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace Ulmus\Annotation\Property\Field;
class Blob extends \Ulmus\Annotation\Property\Field
{
public function __construct(? string $type = "tinyblob", ? int $length = null)
{
parent::__construct($type, $length);
}
}

View File

@ -50,9 +50,9 @@ class EntityField implements WhereRawParameter
return false; return false;
} }
public static function generateCreateColumn($field) : string public static function generateCreateColumn(AdapterInterface $adapter, $field) : string
{ {
$definition = new FieldDefinition($field); $definition = new FieldDefinition($adapter, $field);
return implode(" ", [ return implode(" ", [
$definition->getSqlName(), $definition->getSqlName(),

View File

@ -164,7 +164,7 @@ class EntityResolver {
public function databaseName() : ? string public function databaseName() : ? string
{ {
return $this->tableAnnotation(false)->database ?? $this->databaseAdapter()->adapter()->database ?? null; return $this->tableAnnotation(false)->database ?? $this->databaseAdapter()->adapter()->databaseName() ?? null;
} }
public function sqlAdapter() : \Ulmus\ConnectionAdapter public function sqlAdapter() : \Ulmus\ConnectionAdapter

View File

@ -37,6 +37,8 @@ class ConnectionAdapter
} }
$this->adapter->setup($connection); $this->adapter->setup($connection);
unset($this->configuration['connections'][$this->name]);
} }
public function getConfiguration() : array public function getConfiguration() : array
@ -77,7 +79,7 @@ class ConnectionAdapter
*/ */
protected function instanciateAdapter($name) : AdapterInterface protected function instanciateAdapter($name) : AdapterInterface
{ {
$class = substr($name, 0, 2) === "\\" ? $name : "\\Ulmus\\Adapter\\$name"; $class = substr($name, 0, 2) === "\\" ? $name : sprintf("\\%s\\Adapter\\%s", __NAMESPACE__, $name);
return new $class(); return new $class();
} }

View File

@ -0,0 +1,43 @@
<?php
namespace Ulmus\Entity\Sqlite;
use Ulmus\EntityCollection;
/**
* @Table('name' => "sqlite_master")
*/
class Schema
{
use \Ulmus\EntityTrait;
/**
* @Id
*/
public ? string $name;
/**
* @Field
*/
public ? string $type;
/**
* @Field('name' => 'tbl_name')
*/
public ? string $tableName;
/**
* @Field
*/
public ? int $rootpage;
/**
* @Field
*/
public ? string $sql;
/**
* @Relation('oneToMany', 'key' => 'tableName', 'foreignKey' => 'tableName', 'entity' => Schema::class)
*/
public EntityCollection $columns;
}

View File

@ -0,0 +1,19 @@
<?php
namespace Ulmus\Entity\Sqlite;
use Ulmus\Repository;
class Table extends Schema
{
public static function repository(string $alias = Repository::DEFAULT_ALIAS): Repository
{
return new class(static::class, $alias) extends Repository\SqliteRepository
{
public function finalizeQuery(): void
{
$this->select(Table::field('tableName'))->groupBy(Table::field('tableName'));
}
};
}
}

View File

@ -146,7 +146,12 @@ class EntityCollection extends \ArrayObject {
return $this->filtersCollection(fn($obj) => is_a($obj, $className)); return $this->filtersCollection(fn($obj) => is_a($obj, $className));
} }
public function column($field, bool $unique = false) : array public function column($field, bool $uniqueValue = false, $keyField = null) : array
{
return $this->map($field, $keyField, $uniqueValue);
}
public function map($field, $keyField = null, bool $uniqueValue = false) : array
{ {
$list = []; $list = [];
@ -158,11 +163,23 @@ class EntityCollection extends \ArrayObject {
$value = $item->$field; $value = $item->$field;
} }
if ($unique && in_array($value, $list, true)) { if ($uniqueValue && in_array($value, $list, true)) {
continue; continue;
} }
$list[] = $value; if ( $keyField !== null ) {
if ( is_callable($keyField) ) {
$key = call_user_func_array($keyField, [ $item ]);
}
else {
$key = $item->$keyField;
}
$list[$key] = $value;
}
else {
$list[] = $value;
}
} }
return $list; return $list;

View File

@ -9,7 +9,7 @@ use Ulmus\Repository,
use Ulmus\Annotation\Classes\{ Method, Table, Collation, }; use Ulmus\Annotation\Classes\{ Method, Table, Collation, };
use Ulmus\Annotation\Property\{ Field, Filter, FilterJoin, Relation, OrderBy, Where, OrWhere, Join, Virtual, On, WithJoin, }; use Ulmus\Annotation\Property\{ Field, Filter, FilterJoin, Relation, OrderBy, Where, OrWhere, Join, Virtual, On, WithJoin, };
use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, Datetime as DateTime, Date, Time, Bigint, Tinyint, Text, Mediumtext, Longtext, }; use Ulmus\Annotation\Property\Field\{ Id, ForeignKey, CreatedAt, UpdatedAt, Datetime as DateTime, Date, Time, Bigint, Tinyint, Blob, Text, Mediumtext, Longtext, Tinyblob, Mediumblob, Longblob };
use Ulmus\Annotation\Property\Relation\{ Ignore as RelationIgnore }; use Ulmus\Annotation\Property\Relation\{ Ignore as RelationIgnore };
trait EntityTrait { trait EntityTrait {

View File

@ -2,6 +2,7 @@
namespace Ulmus\Migration; namespace Ulmus\Migration;
use Ulmus\Adapter\AdapterInterface;
use Ulmus\Annotation\Property\Field; use Ulmus\Annotation\Property\Field;
use Ulmus\Entity; use Ulmus\Entity;
@ -25,8 +26,12 @@ class FieldDefinition {
public ? string $update; public ? string $update;
public function __construct(array $data) public AdapterInterface $adapter;
public function __construct(AdapterInterface $adapter, array $data)
{ {
$this->adapter = $adapter;
$this->name = $data['name']; $this->name = $data['name'];
$this->builtIn = $data['builtin']; $this->builtIn = $data['builtin'];
$this->tags = $data['tags']; $this->tags = $data['tags'];
@ -46,109 +51,57 @@ class FieldDefinition {
public function getSqlType(bool $typeOnly = false) : string public function getSqlType(bool $typeOnly = false) : string
{ {
$type = $this->type; return $this->adapter->mapFieldType($this, $typeOnly);
$length = $this->length;
if ( is_a($type, Entity\Field\Date::class, true) ) {
$type = "DATE";
}
elseif ( is_a($type, Entity\Field\Time::class, true) ) {
$type = "TIME";
}
elseif ( is_a($type, \DateTime::class, true) ) {
$type = "DATETIME";
}
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 $typeOnly ? $type : $type . ( $length ? "($length" . ( $precision ? ",$precision" : "" ) . ")" : "" );
} }
public function getSqlParams() : string public function getSqlParams() : string
{ {
$default = $this->getDefault(); $default = $this->getDefault();
$syntax = $this->adapter->tableSyntax();
return implode(' ', array_filter([ return implode(' ', array_filter([
$this->isUnsigned() ? "UNSIGNED" : null, $this->isUnsigned() ? $syntax['unsigned'] : null,
$this->nullable ? "NULL" : "NOT NULL", $this->nullable ? "NULL" : "NOT NULL",
$this->update ? "ON UPDATE {$this->update}" : null, $this->update ? "ON UPDATE {$this->update}" : null,
$default ? "DEFAULT $default" : null, $default ? "DEFAULT $default" : null,
$this->isAutoIncrement() ? "AUTO_INCREMENT" : null, $this->isPrimaryKey() ? $syntax['pk'] : null,
$this->isPrimaryKey() ? "PRIMARY KEY" : null, $this->isAutoIncrement() ? $syntax['ai'] : null,
])); ]));
} }
protected function getFieldTag() : ? Field public function getFieldTag() : ? Field
{ {
$field = array_filter($this->tags, fn($item) => $item['object'] instanceof Field); $field = array_filter($this->tags, fn($item) => $item['object'] instanceof Field);
return array_pop($field)['object']; return array_pop($field)['object'];
} }
protected function getColumnName() : ? string public function getColumnName() : ? string
{ {
return $this->getFieldTag()->name ?? $this->name; return $this->getFieldTag()->name ?? $this->name;
} }
protected function getDefault() : ? string public function getDefault() : ? string
{ {
return $this->getFieldTag()->attributes['default'] ?? ( $this->nullable ? "NULL" : null ) ; return $this->getFieldTag()->attributes['default'] ?? ( $this->nullable ? "NULL" : null ) ;
} }
protected function isPrimaryKey() : bool public function isPrimaryKey() : bool
{ {
return $this->getFieldTag()->attributes['primary_key'] ?? false; return $this->getFieldTag()->attributes['primary_key'] ?? false;
} }
protected function isAutoIncrement() : bool public function isAutoIncrement() : bool
{ {
return $this->getFieldTag()->attributes['auto_increment'] ?? false; return $this->getFieldTag()->attributes['auto_increment'] ?? false;
} }
protected function isUnique() : bool public function isUnique() : bool
{ {
return $this->getFieldTag()->attributes['unique'] ?? false; return $this->getFieldTag()->attributes['unique'] ?? false;
} }
protected function isUnsigned() : bool public function isUnsigned() : bool
{ {
return $this->getFieldTag()->attributes['unsigned'] ?? false; return $this->getFieldTag()->attributes['unsigned'] ?? false;
} }

View File

@ -1,21 +0,0 @@
<?php
namespace Ulmus\Modeler;
class Schema {
public function __construct()
{
}
public function compare()
{
}
public function migrate()
{
}
}

View File

@ -2,6 +2,7 @@
namespace Ulmus\Query; namespace Ulmus\Query;
use Ulmus\Adapter\AdapterInterface;
use Ulmus\Annotation, use Ulmus\Annotation,
Ulmus\Common\EntityField; Ulmus\Common\EntityField;
@ -17,7 +18,13 @@ class Alter extends Fragment {
public bool $skipExisting = true; public bool $skipExisting = true;
public array $fieldList; public array $fieldList;
public AdapterInterface $adapter;
public function __construct(AdapterInterface $adapter) {
$this->adapter = $adapter;
}
public function render() : string public function render() : string
{ {
@ -29,7 +36,7 @@ class Alter extends Fragment {
public function renderFields() : string public function renderFields() : string
{ {
return "(" . PHP_EOL . implode("," . PHP_EOL, array_map(function($field) { return "(" . PHP_EOL . implode("," . PHP_EOL, array_map(function($field) {
return " " . EntityField::generateAlterColumn($field); return " " . EntityField::generateAlterColumn($this->adapter, $field);
}, $this->fieldList)) . PHP_EOL . ")"; }, $this->fieldList)) . PHP_EOL . ")";
} }
} }

View File

@ -2,6 +2,7 @@
namespace Ulmus\Query; namespace Ulmus\Query;
use Ulmus\Adapter\AdapterInterface;
use Ulmus\Annotation, use Ulmus\Annotation,
Ulmus\Common\EntityField; Ulmus\Common\EntityField;
@ -19,6 +20,12 @@ class Create extends Fragment {
public array $fieldList; public array $fieldList;
public AdapterInterface $adapter;
public function __construct(AdapterInterface $adapter) {
$this->adapter = $adapter;
}
public function render() : string public function render() : string
{ {
return $this->renderSegments([ return $this->renderSegments([
@ -29,7 +36,7 @@ class Create extends Fragment {
public function renderFields() : string public function renderFields() : string
{ {
return "(" . PHP_EOL . implode("," . PHP_EOL, array_map(function($field) { return "(" . PHP_EOL . implode("," . PHP_EOL, array_map(function($field) {
return " " . EntityField::generateCreateColumn($field); return " " . EntityField::generateCreateColumn($this->adapter, $field);
}, $this->fieldList)) . PHP_EOL . ")"; }, $this->fieldList)) . PHP_EOL . ")";
} }
} }

View File

@ -42,7 +42,6 @@ class Select extends Fragment {
if ( ! is_numeric($key) ) { if ( ! is_numeric($key) ) {
$value = sprintf(static::FIELD_AS, $value, $key); $value = sprintf(static::FIELD_AS, $value, $key);
} }
} }
return $this->renderSegments([ return $this->renderSegments([

View File

@ -0,0 +1,40 @@
<?php
namespace Ulmus\Query\Sqlite;
class Pragma extends \Ulmus\Query\Fragment {
const SQL_TOKEN = "PRAGMA %s%s";
public int $order = 15;
public /* object|string */ $pragma;
public $value;
public bool $callable;
public function set(/* object|Stringable */ $pragma, /* ? mixed */ $value = null, bool $callable = false) : self
{
$this->pragma = $pragma;
if ($value) {
$this->value = $value;
}
$this->callable = $callable;
return $this;
}
public function render() : string
{
if ( isset($this->value) ) {
$value = sprintf($this->callable ? " (%s)" : " = %s", $this->value);
}
return $this->renderSegments([
sprintf(static::SQL_TOKEN, $this->pragma, $value ?? "")
]);
}
}

View File

@ -329,7 +329,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
return $this; return $this;
} }
public function create(array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self public function create(Adapter\AdapterInterface $adapter, array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self
{ {
if ( null === $this->getFragment(Query\Create::class) ) { if ( null === $this->getFragment(Query\Create::class) ) {
if ( $schema ) { if ( $schema ) {
@ -340,7 +340,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
$table = "$database.$table"; $table = "$database.$table";
} }
$create = new Query\Create(); $create = new Query\Create($adapter);
$this->push($create); $this->push($create);
$create->fieldList = $fieldlist; $create->fieldList = $fieldlist;
@ -353,7 +353,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
return $this; return $this;
} }
public function alter(array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self public function alter(Adapter\AdapterInterface $adapter, array $fieldlist, string $table, ? string $database = null, ? string $schema = null) : self
{ {
if ( null === $this->getFragment(Query\Create::class) ) { if ( null === $this->getFragment(Query\Create::class) ) {
if ( $schema ) { if ( $schema ) {
@ -364,7 +364,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
$table = "$database.$table"; $table = "$database.$table";
} }
$alter = new Query\Alter(); $alter = new Query\Alter($adapter);
$this->push($alter); $this->push($alter);
$alter->fieldList = $fieldlist; $alter->fieldList = $fieldlist;
@ -406,7 +406,7 @@ class QueryBuilder implements Query\QueryBuilderInterface
$sql = []; $sql = [];
usort($this->queryStack, function($q1, $q2) { usort($this->queryStack, function($q1, $q2) {
return $q1->order() <=> $q2->order(); return (float) $q1->order <=> (float) $q2->order;
}); });
foreach($this->queryStack as $fragment) { foreach($this->queryStack as $fragment) {
@ -486,13 +486,12 @@ class QueryBuilder implements Query\QueryBuilderInterface
{ {
$this->values = $values; $this->values = $values;
} }
protected function nextJoinOrder() : float protected function nextJoinOrder() : float
{ {
$next = 0; $next = 0;
foreach($this->getFragments(Query\Join::class) as $join) { foreach($this->getFragments(Query\Join::class) as $join) {
$next = max($next, $join->joinOrder); $next = max($next, $join->joinOrder - Query\Join::ORDER_VALUE);
} }
return $next + 1; return $next + 1;

View File

@ -0,0 +1,34 @@
<?php
namespace Ulmus\QueryBuilder;
use Ulmus\QueryBuilder;
use Ulmus\Query;
class MssqlQueryBuilder extends QueryBuilder implements Ulmus\Query\QueryBuilderInterface
{
public function limit(int $value) : self
{
if ( null === $offset = $this->getFragment(Query\MsSQL\Offset::class) ) {
$offset = new Query\MsSQL\Offset();
$this->push($offset);
}
$offset->limit = $value;
return $this;
}
public function offset(int $value) : self
{
if ( null === $offset = $this->getFragment(Query\MsSQL\Offset::class) ) {
$offset = new Query\MsSQL\Offset();
$this->push($offset);
}
$offset->offset = $value;
return $this;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Ulmus\QueryBuilder;
use Ulmus\QueryBuilder;
use Ulmus\Query;
class SqliteQueryBuilder extends QueryBuilder implements Query\QueryBuilderInterface
{
public function pragma(/*object|Stringable*/ $name, $value = null, bool $callable = false) : self
{
if ( null !== ( $pragma = $this->getFragment(Query\Sqlite\Pragma::class) ) ) {
$pragma->set($name, $value, $callable);
}
else {
$pragma = new Query\Sqlite\Pragma();
$pragma->set($name, $value, $callable);
$this->push($pragma);
}
return $this;
}
}

View File

@ -28,7 +28,7 @@ class Repository
$this->alias = $alias; $this->alias = $alias;
$this->entityResolver = Ulmus::resolveEntity($entity); $this->entityResolver = Ulmus::resolveEntity($entity);
$this->adapter = $adapter ?? $this->entityResolver->databaseAdapter(); $this->adapter = $adapter ?? $this->entityResolver->databaseAdapter();
$this->queryBuilder = new QueryBuilder(); $this->queryBuilder = Ulmus::queryBuilder($entity);
} }
public function __clone() public function __clone()
@ -248,8 +248,6 @@ class Repository
return $update ? (bool) $update->rowCount() : false; return $update ? (bool) $update->rowCount() : false;
} }
} }
} }
public function replace(/*object|array*/ $entity, ? array $fieldsAndValue = null) : bool public function replace(/*object|array*/ $entity, ? array $fieldsAndValue = null) : bool
@ -882,13 +880,7 @@ class Repository
public function createSqlQuery() : self public function createSqlQuery() : self
{ {
if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) { if ( null === $this->queryBuilder->getFragment(Query\Create::class) ) {
$this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName()); $this->queryBuilder->create($this->adapter->adapter(), $this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $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; return $this;
@ -897,7 +889,7 @@ class Repository
public function alterSqlQuery(array $fields) : self public function alterSqlQuery(array $fields) : self
{ {
if ( null === $this->queryBuilder->getFragment(Query\Alter::class) ) { if ( null === $this->queryBuilder->getFragment(Query\Alter::class) ) {
$this->queryBuilder->create($this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName()); $this->queryBuilder->create($this->adapter->adapter(), $this->escapeFieldList($this->entityResolver->fieldList(EntityResolver::KEY_ENTITY_NAME, true)), $this->escapeTable($this->entityResolver->tableName()), $this->entityResolver->schemaName());
} }
@ -933,7 +925,6 @@ class Repository
return new $this->entityClass(); return new $this->entityClass();
} }
public function hasFilters() : bool public function hasFilters() : bool
{ {
return isset($this->queryBuilder->where); return isset($this->queryBuilder->where);

View File

@ -5,7 +5,7 @@ namespace Ulmus\Repository;
use Ulmus\{Repository, Query, Ulmus, Common}; use Ulmus\{Repository, Query, Ulmus, Common};
class MssqlRepository extends Repository { class MssqlRepository extends Repository {
/*
protected function finalizeQuery() : void protected function finalizeQuery() : void
{ {
if ( null !== $offset = $this->queryBuilder->getFragment(Query\Offset::class) ) { if ( null !== $offset = $this->queryBuilder->getFragment(Query\Offset::class) ) {
@ -39,6 +39,30 @@ class MssqlRepository extends Repository {
$this->queryBuilder->removeFragment($limit); $this->queryBuilder->removeFragment($limit);
} }
} }
*/
protected function finalizeQuery() : void
{
if ( null !== $offset = $this->queryBuilder->getFragment(Query\MsSQL\Offset::class) ) {
# an order by is mandatory for mssql offset/limit
if ( null === $order = $this->queryBuilder->getFragment(Query\OrderBy::class) ) {
$this->orderBy("(SELECT 0)");
}
if ( empty ($offset->offset ) ) {
if ( null !== $select = $this->queryBuilder->getFragment(Query\Select::class) ) {
$select->top = $offset->limit;
}
elseif ( null !== $delete = $this->queryBuilder->getFragment(Query\Delete::class) ) {
$delete->top = $offset->limit;
}
$this->queryBuilder->removeFragment($offset);
}
elseif ( empty($offset->limit) ) {
throw new \Exception("Your offset query fragment is missing a LIMIT value.");
}
}
}
protected function serverRequestCountRepository() : Repository protected function serverRequestCountRepository() : Repository
{ {

View File

@ -0,0 +1,26 @@
<?php
namespace Ulmus\Repository;
use Ulmus\{Common\EntityResolver, ConnectionAdapter, QueryBuilder, Repository, Query, Ulmus};
class MysqlRepository extends Repository {
public function pragma(/*object|Stringable*/ $pragma, $argument = null, bool $callable = false) : self
{
$this->queryBuilder->pragma($pragma, $argument, $callable);
return $this;
}
public function createSqlQuery() : self
{
if ( null === $this->queryBuilder->getFragment(Query\Engine::class) ) {
if ( $engine = $this->entityResolver->tableAnnotation()->engine ?? $this->entityResolver->databaseAdapter()->adapter()->defaultEngine() ) {
$this->queryBuilder->engine($engine);
}
}
return parent::createSqlQuery();
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Ulmus\Repository;
use Ulmus\{ConnectionAdapter, QueryBuilder, Repository, Query, Ulmus};
class SqliteRepository extends Repository {
public function pragma(/*object|Stringable*/ $pragma, $argument = null, bool $callable = false) : self
{
$this->queryBuilder->pragma($pragma, $argument, $callable);
return $this;
}
protected function finalizeQuery() : void
{
}
protected function serverRequestCountRepository() : Repository
{
return new static($this->entityClass, $this->alias, $this->adapter);
}
}

View File

@ -41,4 +41,10 @@ trait SearchRequestPaginationTrait {
{ {
return $this->pageCount() > 1; return $this->pageCount() > 1;
} }
public function skipCount(bool $value) : self
{
$this->skipCount = $value;
return $this;
}
} }

View File

@ -107,14 +107,18 @@ abstract class Ulmus
return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass); return static::$resolved[$entityClass] ?? static::$resolved[$entityClass] = new Common\EntityResolver($entityClass);
} }
public static function repository(...$arguments) : Repository public static function repository(string $entityClass, ...$arguments) : Repository
{ {
return new static::$repositoryClass(...$arguments); $cls = $entityClass::resolveEntity()->sqlAdapter()->adapter()->repositoryClass();
return new $cls($entityClass, ...$arguments);
} }
public static function queryBuilder(...$arguments) : Query\QueryBuilderInterface public static function queryBuilder($entityClass, ...$arguments) : Query\QueryBuilderInterface
{ {
return new static::$queryBuilderClass(...$arguments); $cls = $entityClass::resolveEntity()->sqlAdapter()->adapter()->queryBuilderClass();
return new $cls(...$arguments);
} }
public static function instanciateObject(string $type, array $arguments) : object public static function instanciateObject(string $type, array $arguments) : object