Skip to content

Commit

Permalink
SQL: Custom SqlProcessorFactory, modifiers support, ArrayExpressionMo…
Browse files Browse the repository at this point in the history
…difier
  • Loading branch information
mabar committed Feb 28, 2021
1 parent f8ded66 commit 56690f9
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 14 deletions.
15 changes: 2 additions & 13 deletions src/ORM/Functions/JsonAnyKeyOrValueExistsFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Nextras\Orm\Collection\Functions\IQueryBuilderFunction;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use function array_key_last;
use function array_values;
use function assert;
use function count;
use function is_array;
Expand All @@ -31,18 +31,7 @@ public function processQueryBuilderExpression(

assert(is_array($value));

$inlineValue = '';
$last = array_key_last($value);
foreach ($value as $key => $item) {
$inlineValue .= "'{$item}'";
if ($key !== $last) {
$inlineValue .= ', ';
}
}

$inlineValue = "array[{$inlineValue}]";

return $expression->append('?| %raw', $inlineValue);
return $expression->append('?| %arrayExpression', array_values($value));
}

}
8 changes: 7 additions & 1 deletion src/SQL/DI/DbalExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function getConfigSchema(): Schema
return Expect::structure([
'debug' => Expect::bool(false),
'panelQueryExplain' => Expect::bool(true),
'sqlProcessorFactory' => Expect::anyOf(Expect::string(), Expect::type(Statement::class)),
'connections' => Expect::arrayOf(
Expect::structure([
'autowired' => Expect::bool(true),
Expand All @@ -43,7 +44,6 @@ public function getConfigSchema(): Schema
'database' => Expect::string(),
'connectionTz' => Expect::string(IDriver::TIMEZONE_AUTO_PHP_NAME),
'nestedTransactionsWithSavepoint' => Expect::bool(true),
'sqlProcessorFactory' => Expect::anyOf(Expect::string(), Expect::type(Statement::class)),

// mysql only
'charset' => Expect::string(),
Expand All @@ -70,6 +70,8 @@ public function loadConfiguration(): void
$builder = $this->getContainerBuilder();
$config = $this->config;

$sqlProcessorFactory = $config->sqlProcessorFactory;

foreach ($config->connections as $connectionName => $connectionConfig) {
$autowired = $connectionConfig['autowired'];
// Remove from Connection config compile-time only values
Expand All @@ -82,6 +84,10 @@ public function loadConfiguration(): void
}
}

if ($sqlProcessorFactory !== null) {
$connectionConfig['sqlProcessorFactory'] = $sqlProcessorFactory;
}

$definition = $builder->addDefinition($this->prefix('connection.' . $connectionName))
->setFactory(Connection::class, [
'config' => $connectionConfig,
Expand Down
86 changes: 86 additions & 0 deletions src/SQL/Modifier/ArrayExpressionModifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php declare(strict_types = 1);

namespace OriCMF\Core\SQL\Modifier;

use Nextras\Dbal\Connection;
use Orisai\Exceptions\Logic\InvalidArgument;
use function array_key_last;
use function assert;
use function get_debug_type;
use function is_array;
use function is_bool;
use function is_finite;
use function is_float;
use function is_int;
use function is_object;
use function is_string;
use function json_encode;
use function method_exists;
use function str_contains;
use function strlen;
use function substr;

final class ArrayExpressionModifier implements ExpressionModifier
{

/**
* @param mixed $value
* @return mixed
*/
public function valueToExpression($value, Connection $connection)
{
assert(is_array($value));

return "array [{$this->process($connection, $value)}]";
}

/**
* @param array<mixed> $value
*/
protected function process(Connection $connection, array $value): string

This comment has been minimized.

Copy link
@hrach

hrach Mar 8, 2021

Hey @mabar. I've reviewed the api and added SqlProcessor, otherwise it should be pretty easy to use. Tried solve your issue and i would go with more simple implementation: nextras/dbal@92d28a9#diff-ab562a723d045317f8d22acacebfd1fe785d271834414bc782fe72a0a3781c35R62-R73

This comment has been minimized.

Copy link
@mabar

mabar Mar 8, 2021

Author Member

Thanks a lot @hrach! Should be way simpler this way. I will try it, hopefully tomorrow and will let you know.

{
$driver = $connection->getDriver();
$valueSeparator = ', ';
$processed = '';
$last = array_key_last($value);
foreach ($value as $key => $item) {
if (is_array($item)) {
$processed .= $this->valueToExpression($item, $connection);
} elseif (is_string($item)) {
$processed .= $driver->convertStringToSql($item);
} elseif (is_int($item)) {
$processed .= (string) $item;
} elseif (is_float($item)) {
if (!is_finite($item)) {
if ($key === $last) {
$processed = substr($processed, 0, -strlen($valueSeparator));
}

continue;
}

$tmp = json_encode($item);
assert(is_string($tmp));
$processed .= $tmp . (!str_contains($tmp, '.') ? '.0' : '');
} elseif (is_bool($item)) {
$processed .= $driver->convertBoolToSql($item);
} elseif ($item === null) {
$processed .= 'NULL';
} elseif (is_object($item) && method_exists($item, '__toString')) {
$processed .= $driver->convertStringToSql((string) $item);
} else {
$itemType = get_debug_type($item);

throw InvalidArgument::create()
->withMessage("Value of type {$itemType} is not supported.");
}

if ($key !== $last) {
$processed .= $valueSeparator;
}
}

return $processed;
}

}
16 changes: 16 additions & 0 deletions src/SQL/Modifier/ExpressionModifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);

namespace OriCMF\Core\SQL\Modifier;

use Nextras\Dbal\Connection;

interface ExpressionModifier
{

/**
* @param mixed $value
* @return mixed
*/
public function valueToExpression($value, Connection $connection);

}
35 changes: 35 additions & 0 deletions src/SQL/SqlProcessorFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);

namespace OriCMF\Core\SQL;

use Nextras\Dbal\Connection;
use Nextras\Dbal\ISqlProcessorFactory;
use Nextras\Dbal\SqlProcessor;
use OriCMF\Core\SQL\Modifier\ExpressionModifier;

final class SqlProcessorFactory implements ISqlProcessorFactory
{

/** @var array<ExpressionModifier> */
private array $modifiers = [];

public function addModifier(string $modifier, ExpressionModifier $expressionModifier): void
{
$this->modifiers[$modifier] = $expressionModifier;
}

public function create(Connection $connection): SqlProcessor
{
$sqlProcessor = new SqlProcessor($connection->getDriver(), $connection->getPlatform());

foreach ($this->modifiers as $name => $expressionModifier) {
$sqlProcessor->setCustomModifier(
$name,
static fn ($value, string $modifier) => $expressionModifier->valueToExpression($value, $connection),
);
}

return $sqlProcessor;
}

}
9 changes: 9 additions & 0 deletions src/wiring.neon
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ console:

dbal:
debug: %debug.panels.dbal%
sqlProcessorFactory: @ori.core.sql.processorFactory

di:
debugger: %debug.panels.di.container%
Expand Down Expand Up @@ -62,6 +63,14 @@ services:
factory: Psr\Log\NullLogger
type: Psr\Log\LoggerInterface

# SQL
ori.core.sql.modifier.arrayExpression:
factory: OriCMF\Core\SQL\Modifier\ArrayExpressionModifier
ori.core.sql.processorFactory:
factory: OriCMF\Core\SQL\SqlProcessorFactory
setup:
- addModifier(arrayExpression, @ori.core.sql.modifier.arrayExpression)

# Config
ori.core.config.application:
factory: OriCMF\Core\Config\ApplicationConfig
Expand Down

0 comments on commit 56690f9

Please sign in to comment.