- Merged stash after pull

This commit is contained in:
Dave M. 2020-10-16 15:27:54 +00:00
parent 114fa5be09
commit efa957fe58
20 changed files with 623 additions and 436 deletions

View File

@ -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;
}

View File

@ -196,4 +196,9 @@ class MsSQL implements AdapterInterface {
return "[" . str_replace(["[", "]"], [ "[[", "]]" ], $segment) . "]";
}
}
public function defaultEngine(): ? string
{
return null;
}
}

View File

@ -134,4 +134,8 @@ class MySQL implements AdapterInterface {
}
}
public function defaultEngine(): ? string
{
return "InnoDB";
}
}

View File

@ -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;
}
}
}

View File

@ -12,6 +12,8 @@ class Field implements \Ulmus\Annotation\Annotation {
public int $length;
public int $precision;
public array $attributes = [];
public bool $nullable = false;

View File

@ -14,6 +14,7 @@ class ForeignKey extends Id {
unset($this->nullable);
$this->attributes['primary_key'] = false;
$this->attributes['auto_increment'] = false;
}
}

View File

@ -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

View File

@ -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");
}

View File

@ -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);

View File

@ -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

View File

@ -0,0 +1,13 @@
<?php
namespace Ulmus\Container;
class AdapterProxy
{
public array $connections = [];
public function __construct(...$arguments)
{
$this->connections = $arguments;
}
}

View File

@ -0,0 +1,124 @@
<?php
namespace Ulmus\Entity\InformationSchema;
use Ulmus\Entity\Field\Datetime;
/**
* @Table('name' => "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;
}

View File

@ -0,0 +1,135 @@
<?php
namespace Ulmus\Entity\InformationSchema;
use Ulmus\EntityCollection,
Ulmus\Entity\Field\Datetime;
/**
* @Table('name' => "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;
}

View File

@ -0,0 +1,137 @@
<?php
namespace Ulmus\Migration;
use Ulmus\Annotation\Property\Field;
class FieldDefinition {
public bool $nullable;
public bool $builtIn;
public string $type;
public array $tags;
public /* ? string */ $default;
public ? int $precision;
public ? int $length;
public ? string $update;
public function __construct(array $data)
{
$this->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;
}
}

View File

View File

@ -1,404 +0,0 @@
<?php namespace Alive\Storage\Sql;
use Alive\{
constructor,
Arrayobj
};
class QueryBuilder {
protected $fields;
protected $where;
protected $order_by;
protected $group_by;
protected $limit;
public static $syntax = [
'against' => '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;
}
}

37
src/Query/Create.php Normal file
View File

@ -0,0 +1,37 @@
<?php
namespace Ulmus\Query;
use Ulmus\Annotation,
Ulmus\Common\EntityField;
class Create extends Fragment {
const SQL_TOKEN = "CREATE TABLE";
public string $table;
public string $engine;
public int $order = -80;
public bool $skipExisting = true;
public array $fieldList;
public function render() : string
{
return $this->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 . ")";
}
}

19
src/Query/Engine.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace Ulmus\Query;
class Engine extends Fragment {
const SQL_TOKEN = "ENGINE";
public int $order = 80;
public string $engine;
public function render() : string
{
return $this->renderSegments([
static::SQL_TOKEN, $this->engine,
], "=");
}
}

View File

@ -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
{

View File

@ -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);