From 56690f9cad3a3b431ea64f11c2b01d3a948ac23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Barto=C5=A1?= Date: Sun, 28 Feb 2021 21:45:40 +0100 Subject: [PATCH] SQL: Custom SqlProcessorFactory, modifiers support, ArrayExpressionModifier --- .../JsonAnyKeyOrValueExistsFunction.php | 15 +--- src/SQL/DI/DbalExtension.php | 8 +- src/SQL/Modifier/ArrayExpressionModifier.php | 86 +++++++++++++++++++ src/SQL/Modifier/ExpressionModifier.php | 16 ++++ src/SQL/SqlProcessorFactory.php | 35 ++++++++ src/wiring.neon | 9 ++ 6 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 src/SQL/Modifier/ArrayExpressionModifier.php create mode 100644 src/SQL/Modifier/ExpressionModifier.php create mode 100644 src/SQL/SqlProcessorFactory.php diff --git a/src/ORM/Functions/JsonAnyKeyOrValueExistsFunction.php b/src/ORM/Functions/JsonAnyKeyOrValueExistsFunction.php index 3dd1b8a..22e5703 100644 --- a/src/ORM/Functions/JsonAnyKeyOrValueExistsFunction.php +++ b/src/ORM/Functions/JsonAnyKeyOrValueExistsFunction.php @@ -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; @@ -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)); } } diff --git a/src/SQL/DI/DbalExtension.php b/src/SQL/DI/DbalExtension.php index 5e0106c..ae4e9ed 100644 --- a/src/SQL/DI/DbalExtension.php +++ b/src/SQL/DI/DbalExtension.php @@ -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), @@ -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(), @@ -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 @@ -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, diff --git a/src/SQL/Modifier/ArrayExpressionModifier.php b/src/SQL/Modifier/ArrayExpressionModifier.php new file mode 100644 index 0000000..14c9026 --- /dev/null +++ b/src/SQL/Modifier/ArrayExpressionModifier.php @@ -0,0 +1,86 @@ +process($connection, $value)}]"; + } + + /** + * @param array $value + */ + protected function process(Connection $connection, array $value): string + { + $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; + } + +} diff --git a/src/SQL/Modifier/ExpressionModifier.php b/src/SQL/Modifier/ExpressionModifier.php new file mode 100644 index 0000000..9d23390 --- /dev/null +++ b/src/SQL/Modifier/ExpressionModifier.php @@ -0,0 +1,16 @@ + */ + 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; + } + +} diff --git a/src/wiring.neon b/src/wiring.neon index f2bb070..eefe0c9 100644 --- a/src/wiring.neon +++ b/src/wiring.neon @@ -19,6 +19,7 @@ console: dbal: debug: %debug.panels.dbal% + sqlProcessorFactory: @ori.core.sql.processorFactory di: debugger: %debug.panels.di.container% @@ -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