fix bug and add query yield

This commit is contained in:
Dennis Eichhorn 2023-10-05 10:12:56 +00:00
parent e873f909fd
commit cd9a5cdc11
5 changed files with 272 additions and 27 deletions

View File

@ -202,7 +202,9 @@ abstract class GrammarAbstract
$expression .= $element() . (\is_string($key) ? ' as ' . $key : '') . ', '; $expression .= $element() . (\is_string($key) ? ' as ' . $key : '') . ', ';
} elseif ($element instanceof BuilderAbstract) { } elseif ($element instanceof BuilderAbstract) {
$expression .= $element->toSql() . (\is_string($key) ? ' as ' . $key : '') . ', '; $expression .= $element->toSql() . (\is_string($key) ? ' as ' . $key : '') . ', ';
} else { } elseif (\is_int($element)) {
$expression .= $element . ', ';
}else {
throw new \InvalidArgumentException(); throw new \InvalidArgumentException();
} }
} }
@ -226,15 +228,18 @@ abstract class GrammarAbstract
$identifierStart = $this->systemIdentifierStart; $identifierStart = $this->systemIdentifierStart;
$identifierEnd = $this->systemIdentifierEnd; $identifierEnd = $this->systemIdentifierEnd;
// @todo: maybe the if/elseif has to get swapped in order. There could be a count(table.name) for example // The order of this if/elseif statement is important!!!
if ((\stripos($system, '.')) !== false) { if ($system === '*'
// The following code could have been handled with \explode more elegantly but \explode needs more memory and more time || \stripos($system, '(') !== false
// Normally this wouldn't be a problem but in this case there are so many function calls to this routine, || \is_numeric($system)
// that it makes sense to make this "minor" improvement. ) {
$identifierStart = '';
$identifierEnd = '';
} elseif ((\stripos($system, '.')) !== false) {
// This is actually slower than \explode(), despite knowing the first index // This is actually slower than \explode(), despite knowing the first index
//$split = [\substr($system, 0, $pos), \substr($system, $pos + 1)]; //$split = [\substr($system, 0, $pos), \substr($system, $pos + 1)];
// Faster! But might requires more memory?
$split = \explode('.', $system); $split = \explode('.', $system);
$identifierTwoStart = $identifierStart; $identifierTwoStart = $identifierStart;
@ -248,12 +253,6 @@ abstract class GrammarAbstract
return $identifierStart . $split[0] . $identifierEnd return $identifierStart . $split[0] . $identifierEnd
. '.' . '.'
. $identifierTwoStart . $split[1] . $identifierTwoEnd; . $identifierTwoStart . $split[1] . $identifierTwoEnd;
} elseif ($system === '*'
|| \stripos($system, '(') !== false
|| \is_numeric($system)
) {
$identifierStart = '';
$identifierEnd = '';
} }
return $identifierStart . $system . $identifierEnd; return $identifierStart . $system . $identifierEnd;

View File

@ -209,6 +209,23 @@ class DataMapperFactory
return $reader->get(); return $reader->get();
} }
/**
* Create read mapper
*
* @param ConnectionAbstract $db Database connection
*
* @return ReadMapper<T>
*
* @since 1.0.0
*/
public static function yield(ConnectionAbstract $db = null) : ReadMapper
{
/** @var ReadMapper<T> $reader */
$reader = new ReadMapper(new static(), $db ?? self::$db);
return $reader->yield();
}
/** /**
* Create read mapper * Create read mapper
* *
@ -268,6 +285,20 @@ class DataMapperFactory
return (new ReadMapper(new static(), $db ?? self::$db))->exists(); return (new ReadMapper(new static(), $db ?? self::$db))->exists();
} }
/**
* Create read mapper
*
* @param ConnectionAbstract $db Database connection
*
* @return ReadMapper
*
* @since 1.0.0
*/
public static function has(ConnectionAbstract $db = null) : ReadMapper
{
return (new ReadMapper(new static(), $db ?? self::$db))->has();
}
/** /**
* Create read mapper * Create read mapper
* *

View File

@ -28,6 +28,8 @@ abstract class MapperType extends Enum
{ {
public const GET = 1; public const GET = 1;
public const GET_YIELD = 2;
public const GET_ALL = 4; public const GET_ALL = 4;
public const FIND = 7; public const FIND = 7;
@ -40,6 +42,8 @@ abstract class MapperType extends Enum
public const MODEL_EXISTS = 13; public const MODEL_EXISTS = 13;
public const MODEL_HAS_RELATION = 14;
// -------------------------------------------- // // -------------------------------------------- //
public const CREATE = 1001; public const CREATE = 1001;

View File

@ -58,6 +58,22 @@ final class ReadMapper extends DataMapperAbstract
return $this; return $this;
} }
/**
* Create yield mapper
*
* This makes execute() return a single object or an array of object depending the result size
*
* @return self
*
* @since 1.0.0
*/
public function yield() : self
{
$this->type = MapperType::GET_YIELD;
return $this;
}
/** /**
* Get raw result set * Get raw result set
* *
@ -103,7 +119,7 @@ final class ReadMapper extends DataMapperAbstract
} }
/** /**
* Create count mapper * Create exists mapper
* *
* @return self * @return self
* *
@ -116,6 +132,20 @@ final class ReadMapper extends DataMapperAbstract
return $this; return $this;
} }
/**
* Create has mapper
*
* @return self
*
* @since 1.0.0
*/
public function has() : self
{
$this->type = MapperType::MODEL_HAS_RELATION;
return $this;
}
/** /**
* Create random mapper * Create random mapper
* *
@ -162,6 +192,9 @@ final class ReadMapper extends DataMapperAbstract
case MapperType::GET: case MapperType::GET:
/** @var null|Builder ...$options */ /** @var null|Builder ...$options */
return $this->executeGet(...$options); return $this->executeGet(...$options);
case MapperType::GET_YIELD:
/** @var null|Builder ...$options */
return $this->executeGetYield(...$options);
case MapperType::GET_RAW: case MapperType::GET_RAW:
/** @var null|Builder ...$options */ /** @var null|Builder ...$options */
return $this->executeGetRaw(...$options); return $this->executeGetRaw(...$options);
@ -174,6 +207,8 @@ final class ReadMapper extends DataMapperAbstract
return $this->executeCount(); return $this->executeCount();
case MapperType::MODEL_EXISTS: case MapperType::MODEL_EXISTS:
return $this->executeExists(); return $this->executeExists();
case MapperType::MODEL_HAS_RELATION:
return $this->executeHas();
default: default:
return null; return null;
} }
@ -204,9 +239,13 @@ final class ReadMapper extends DataMapperAbstract
$obj = []; $obj = [];
// Get remaining objects (not available in memory cache) or remaining where clauses. // Get remaining objects (not available in memory cache) or remaining where clauses.
$dbData = $this->executeGetRaw($query); //$dbData = $this->executeGetRaw($query);
foreach ($this->executeGetRawYield($query) as $row) {
if ($row === []) {
continue;
}
foreach ($dbData as $row) {
$value = $row[$this->mapper::PRIMARYFIELD . '_d' . $this->depth]; $value = $row[$this->mapper::PRIMARYFIELD . '_d' . $this->depth];
$obj[$value] = $this->mapper::createBaseModel($row); $obj[$value] = $this->mapper::createBaseModel($row);
@ -225,6 +264,34 @@ final class ReadMapper extends DataMapperAbstract
return $obj; return $obj;
} }
/**
* Execute mapper
*
* @param null|Builder $query Query to use instead of the internally generated query
* Careful, this doesn't merge with the internal query.
* If you want to merge it use ->query() instead
*
* @since 1.0.0
*/
public function executeGetYield(Builder $query = null)
{
$primaryKeys = [];
$memberOfPrimaryField = $this->mapper::COLUMNS[$this->mapper::PRIMARYFIELD]['internal'];
if (isset($this->where[$memberOfPrimaryField])) {
$keys = $this->where[$memberOfPrimaryField][0]['value'];
$primaryKeys = \array_merge(\is_array($keys) ? $keys : [$keys], $primaryKeys);
}
foreach ($this->executeGetRawYield($query) as $row) {
$obj = $this->mapper::createBaseModel($row);
$obj = $this->populateAbstract($row, $obj);
$this->loadHasManyRelations($obj);
yield $obj;
}
}
/** /**
* Execute mapper * Execute mapper
* *
@ -263,6 +330,47 @@ final class ReadMapper extends DataMapperAbstract
return $results === false ? [] : $results; return $results === false ? [] : $results;
} }
/**
* Execute mapper
*
* @param null|Builder $query Query to use instead of the internally generated query
* Careful, this doesn't merge with the internal query.
* If you want to merge it use ->query() instead
*
* @return array
*
* @since 1.0.0
*/
public function executeGetRawYield(Builder $query = null)
{
$query ??= $this->getQuery();
try {
$sth = $this->db->con->prepare($query->toSql());
if ($sth === false) {
yield [];
return;
}
$sth->execute();
while ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
yield $row;
}
} catch (\Throwable $t) {
\phpOMS\Log\FileLogger::getInstance()->error(
\phpOMS\Log\FileLogger::MSG_FULL, [
'message' => $t->getMessage() . ':' . $query->toSql(),
'line' => __LINE__,
'file' => self::class,
]
);
yield [];
}
}
/** /**
* Execute mapper * Execute mapper
* *
@ -304,17 +412,33 @@ final class ReadMapper extends DataMapperAbstract
/** /**
* Check if any element exists * Check if any element exists
* *
* @return int * @return bool
* *
* @since 1.0.0 * @since 1.0.0
*/ */
public function executeExists() : bool public function executeExists() : bool
{ {
$query = $this->getQuery(null, ['1']); $query = $this->getQuery(null, [1]);
return ($query->execute()?->fetchColumn() ?? 0) > 0; return ($query->execute()?->fetchColumn() ?? 0) > 0;
} }
/**
* Check if any element exists
*
* @return bool
*
* @since 1.0.0
*/
public function executeHas() : bool
{
$obj = isset($this->where[$this->mapper::COLUMNS[$this->mapper::PRIMARYFIELD]['internal']])
? $this->mapper::createNullModel($this->where[$this->mapper::COLUMNS[$this->mapper::PRIMARYFIELD]['internal']][0]['value'])
: $this->columns([1])->executeGet();
return $this->hasManyRelations($obj);
}
/** /**
* Get random object * Get random object
* *
@ -348,10 +472,18 @@ final class ReadMapper extends DataMapperAbstract
: $columns; : $columns;
foreach ($columns as $key => $values) { foreach ($columns as $key => $values) {
if (\is_string($values)) { if (\is_string($values) || \is_int($values)) {
$query->selectAs($key, $values); if (\is_int($key)) {
$query->select($values);
} else {
$query->selectAs($key, $values);
}
} elseif (($values['writeonly'] ?? false) === false || isset($this->with[$values['internal']])) { } elseif (($values['writeonly'] ?? false) === false || isset($this->with[$values['internal']])) {
$query->selectAs($this->mapper::TABLE . '_d' . $this->depth . '.' . $key, $key . '_d' . $this->depth); if (\is_int($key)) {
$query->select($key);
} else {
$query->selectAs($this->mapper::TABLE . '_d' . $this->depth . '.' . $key, $key . '_d' . $this->depth);
}
} }
} }
@ -472,7 +604,7 @@ final class ReadMapper extends DataMapperAbstract
$where1->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, $comparison, $where['value'], 'and'); $where1->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, $comparison, $where['value'], 'and');
$where2 = new Builder($this->db); $where2 = new Builder($this->db);
$where2->select('1') $where2->select('1') // @todo: why is this in quotes?
->from($this->mapper::TABLE . '_d' . $this->depth) ->from($this->mapper::TABLE . '_d' . $this->depth)
->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, 'in', $alt); ->where($this->mapper::TABLE . '_d' . $this->depth . '.' . $col, 'in', $alt);
@ -991,4 +1123,87 @@ final class ReadMapper extends DataMapperAbstract
} }
} }
} }
/**
* Checks if object has certain relations
*
* @param object $obj Object to check
*
* @return bool
*
* @since 1.0.0
*/
public function hasManyRelations(object $obj) : bool
{
if (empty($this->with)) {
return true;
}
$primaryKey = $this->mapper::getObjectId($obj);
if (empty($primaryKey)) {
return false;
}
$refClass = null;
// @todo: check if there are more cases where the relation is already loaded with joins etc.
// there can be pseudo has many elements like localizations. They are has manies but these are already loaded with joins!
foreach ($this->with as $member => $withData) {
if (isset($this->mapper::HAS_MANY[$member])) {
$many = $this->mapper::HAS_MANY[$member];
if (isset($many['column'])) {
continue;
}
// @todo: withData doesn't store this directly, it is in [0]['private] ?!?!
$isPrivate = $withData['private'] ?? false;
$objectMapper = $this->createRelationMapper($many['mapper']::exists(db: $this->db), $member);
if ($many['external'] === null/* same as $many['table'] !== $many['mapper']::TABLE */) {
$objectMapper->where($many['mapper']::COLUMNS[$many['self']]['internal'], $primaryKey);
} else {
$query = new Builder($this->db, true);
$query->leftJoin($many['table'])
->on($many['mapper']::TABLE . '_d1.' . $many['mapper']::PRIMARYFIELD, '=', $many['table'] . '.' . $many['external'])
->where($many['table'] . '.' . $many['self'], '=', $primaryKey);
// Cannot use join, because join only works on members and we don't have members for a relation table
// This is why we need to create a "base" query which contians the join on table columns
$objectMapper->query($query);
}
$objects = $objectMapper->execute();
if (empty($objects) || $objects === false) {
return false;
}
return true;
} elseif (isset($this->mapper::OWNS_ONE[$member])
|| isset($this->mapper::BELONGS_TO[$member])
) {
$relation = isset($this->mapper::OWNS_ONE[$member])
? $this->mapper::OWNS_ONE[$member]
: $this->mapper::BELONGS_TO[$member];
if (\count($withData) < 2) {
continue;
}
/** @var ReadMapper $relMapper */
$relMapper = $this->createRelationMapper($relation['mapper']::reader($this->db), $member);
$isPrivate = $withData['private'] ?? false;
if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refProp = $refClass->getProperty($member);
return $relMapper->hasManyRelations($refProp->getValue($obj));
} else {
return $relMapper->hasManyRelations($obj->{$member});
}
}
}
}
} }

View File

@ -280,11 +280,7 @@ class Builder extends BuilderAbstract
/** @var mixed[] $columns */ /** @var mixed[] $columns */
/** @var mixed $column */ /** @var mixed $column */
foreach ($columns as $column) { foreach ($columns as $column) {
if (\is_string($column) || $column instanceof self) { $this->selects[] = $column;
$this->selects[] = $column;
} else {
throw new \InvalidArgumentException();
}
} }
return $this; return $this;