From 2d021de4b0385caf2caef4550d5a6af1dd3936aa Mon Sep 17 00:00:00 2001 From: romanpravda <59819062+romanpravda@users.noreply.github.com> Date: Tue, 22 Sep 2020 13:35:17 +0300 Subject: [PATCH] Adding the ability to work with tags servers and the ability to set alias for joined table (#30) * allowed to use proxy servers and to set alias for join * added more tests, dev dependency and refactored some php docs * fix travis and code style fixes * another style fixes * another code style fixes * fix test for getting last query statistics from builder * update readme * fix readme * refactor for using server with tags instead of proxy servers * fix onCluster function * some small refactor * change clickhouse client version --- .travis.yml | 2 +- README.md | 47 +- composer.json | 5 +- src/Exceptions/BuilderException.php | 4 +- src/Integrations/Laravel/Builder.php | 35 +- src/Integrations/Laravel/Connection.php | 221 ++++--- src/Query/ArrayJoinClause.php | 4 +- src/Query/BaseBuilder.php | 556 +++++++++--------- src/Query/Builder.php | 52 +- src/Query/Column.php | 14 +- src/Query/From.php | 18 +- src/Query/Grammar.php | 110 ++-- src/Query/JoinClause.php | 22 + src/Query/Limit.php | 6 +- .../Traits/ArrayJoinComponentCompiler.php | 2 +- src/Query/Traits/ColumnCompiler.php | 2 +- src/Query/Traits/ColumnsComponentCompiler.php | 4 +- src/Query/Traits/FormatComponentCompiler.php | 2 +- src/Query/Traits/FromComponentCompiler.php | 2 +- src/Query/Traits/GroupsComponentCompiler.php | 6 +- src/Query/Traits/HavingsComponentCompiler.php | 2 +- src/Query/Traits/OrdersComponentCompiler.php | 2 +- .../Traits/PreWheresComponentCompiler.php | 2 +- src/Query/Traits/SampleComponentCompiler.php | 2 +- src/Query/Traits/TupleCompiler.php | 2 +- .../TwoElementsLogicExpressionsCompiler.php | 2 +- src/Query/Traits/UnionsComponentCompiler.php | 2 +- src/Query/Traits/WheresComponentCompiler.php | 2 +- src/Query/Tuple.php | 4 +- src/Query/TwoElementsLogicExpression.php | 8 +- src/functions.php | 21 +- tests/BuilderTest.php | 201 ++++--- tests/ExceptionsTest.php | 2 +- tests/FromTest.php | 2 +- tests/FunctionsTest.php | 6 +- tests/GrammarTest.php | 3 + tests/LaravelIntegrationTest.php | 339 ++++++++--- tests/TwoElementsLogicExpressionTest.php | 2 +- 38 files changed, 1014 insertions(+), 704 deletions(-) diff --git a/.travis.yml b/.travis.yml index ba781bf..eb6ccea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ before_script: - composer install --prefer-source --dev - docker run -p 9000:9000 -p 8123:8123 -d --name some-clickhouse-server --ulimit nofile=262144:262144 yandex/clickhouse-server -script: phpunit --coverage-clover ./tests/logs/clover.xml +script: vendor/bin/phpunit --coverage-clover ./tests/logs/clover.xml after_script: - php vendor/bin/php-coveralls -v diff --git a/README.md b/README.md index 35adc72..d951af2 100644 --- a/README.md +++ b/README.md @@ -148,11 +148,11 @@ I think there no need for additional words) ### Joins ```php -$builder->from('table')->join('another_table', 'any', 'left', ['column1', 'column2'], true); +$builder->from('table')->join('another_table', 'any', 'left', ['column1', 'column2'], true, 'alias'); ``` ```sql -SELECT * FROM `table` GLOBAL ANY LEFT JOIN `another_table` USING `column1`, `column2` +SELECT * FROM `table` GLOBAL ANY LEFT JOIN `another_table` AS `alias` USING `column1`, `column2` ``` For performing subquery as first argument you can pass closure or builder. @@ -567,6 +567,43 @@ Example with cluster: ] ``` +Example with server with tag: + +```php +'connections' => [ + 'clickhouse' => [ + 'driver' => 'clickhouse', + 'servers' => [ + [ + 'host' => 'ch-00.domain.com', + 'port' => '', + 'database' => '', + 'username' => '', + 'password' => '', + 'options' => [ + 'timeout' => 10, + 'protocol' => 'https', + 'tags' => [ + 'tag' + ], + ], + ], + [ + 'host' => 'ch-01.domain.com', + 'port' => '', + 'database' => '', + 'username' => '', + 'password' => '', + 'options' => [ + 'timeout' => 10, + 'protocol' => 'https' + ], + ], + ], + ], +] +``` + Choose server without cluster: ```php @@ -585,6 +622,12 @@ Choose cluster: DB::connection('clickhouse')->onCluster('test')->select(...); ``` +Use server with tag: + +```php +DB::connection('clickhouse')->usingServerWithTag('tag')->select(...); +``` + You can use both `servers` and `clusters` config directives and choose on which server query should be executed via `onCluster` and `using` methods. If you want to choose server outside cluster, you should just call `onCluster(null)` and then call `using` method. You can diff --git a/composer.json b/composer.json index 378b59d..3d08a29 100644 --- a/composer.json +++ b/composer.json @@ -30,14 +30,15 @@ "require": { "php": "~7.1", "myclabs/php-enum": "^1.5", - "the-tinderbox/clickhouse-php-client": "~2.0.0" + "the-tinderbox/clickhouse-php-client": "~2.1.0" }, "require-dev": { "illuminate/database": "5.*", "phpunit/phpunit": "^6.1", "mockery/mockery": "^0.9.9", "illuminate/events": "5.*", - "illuminate/config": "5.*" + "illuminate/config": "5.*", + "illuminate/pagination": "5.*" }, "extra": { "laravel": { diff --git a/src/Exceptions/BuilderException.php b/src/Exceptions/BuilderException.php index bbb5fa8..7fb9998 100644 --- a/src/Exceptions/BuilderException.php +++ b/src/Exceptions/BuilderException.php @@ -8,9 +8,9 @@ public static function cannotDetermineAliasForColumn() { return new static('Cannot determine alias for the column'); } - + public static function noTableStructureProvided() { - return new static("No structure provided for insert in memory table"); + return new static('No structure provided for insert in memory table'); } } diff --git a/src/Integrations/Laravel/Builder.php b/src/Integrations/Laravel/Builder.php index eae548f..e64d468 100644 --- a/src/Integrations/Laravel/Builder.php +++ b/src/Integrations/Laravel/Builder.php @@ -44,15 +44,15 @@ public function __construct(Connection $connection) */ public function get() { - if (! empty($this->async)) { + if (!empty($this->async)) { return $this->connection->selectAsync($this->toAsyncQueries()); } else { return $this->connection->select($this->toSql(), [], $this->getFiles()); } } - + /** - * Returns Query instance + * Returns Query instance. * * @param array $settings * @@ -67,7 +67,7 @@ public function toQuery(array $settings = []): Query * Performs compiled sql for count rows only. May be used for pagination * Works only without async queries. * - * @param string $column Column to pass into count() aggregate function + * @throws \Tinderbox\Clickhouse\Exceptions\ClientException * * @return int|mixed */ @@ -76,7 +76,7 @@ public function count() $builder = $this->getCountQuery(); $result = $builder->get(); - if (! empty($this->groups)) { + if (!empty($this->groups)) { return count($result); } else { return $result[0]['count'] ?? 0; @@ -84,7 +84,9 @@ public function count() } /** - * Perform query and get first row + * Perform query and get first row. + * + * @throws \Tinderbox\Clickhouse\Exceptions\ClientException * * @return mixed|null|\Tinderbox\Clickhouse\Query\Result */ @@ -100,7 +102,7 @@ public function first() * * @return self */ - public function newQuery() : Builder + public function newQuery(): Builder { return new static($this->connection); } @@ -117,9 +119,9 @@ public function newQuery() : Builder */ public function insertFiles(array $columns, array $files, string $format = Format::CSV, int $concurrency = 5): array { - return $this->connection->insertFiles((string)$this->getFrom()->getTable(), $columns, $files, $format, $concurrency); + return $this->connection->insertFiles((string) $this->getFrom()->getTable(), $columns, $files, $format, $concurrency); } - + /** * Insert in table data from files. * @@ -132,9 +134,9 @@ public function insertFiles(array $columns, array $files, string $format = Forma public function insertFile(array $columns, $file, string $format = Format::CSV): bool { $file = $this->prepareFile($file); - + $result = $this->connection->insertFiles($this->getFrom()->getTable(), $columns, [$file], $format); - + return $result[0][0]; } @@ -143,6 +145,8 @@ public function insertFile(array $columns, $file, string $format = Format::CSV): * * @param array $values * + * @throws \Tinderbox\ClickhouseBuilder\Exceptions\GrammarException + * * @return bool */ public function insert(array $values) @@ -151,7 +155,7 @@ public function insert(array $values) return false; } - if (! is_array(reset($values))) { + if (!is_array(reset($values))) { $values = [$values]; } /* * Here, we will sort the insert keys for every record so that each insert is @@ -174,6 +178,8 @@ public function insert(array $values) /** * Performs ALTER TABLE `table` DELETE query. * + * @throws \Tinderbox\ClickhouseBuilder\Exceptions\GrammarException + * * @return int */ public function delete() @@ -188,6 +194,7 @@ public function delete() * @param int $perPage * * @throws \Tinderbox\Clickhouse\Exceptions\ClientException + * * @return LengthAwarePaginator */ public function paginate(int $page = 1, int $perPage = 15): LengthAwarePaginator @@ -214,7 +221,7 @@ public function paginate(int $page = 1, int $perPage = 15): LengthAwarePaginator * * @return QueryStatistic */ - public function getLastQueryStatistics() : QueryStatistic + public function getLastQueryStatistics(): QueryStatistic { return $this->getConnection()->getLastQueryStatistic(); } @@ -224,7 +231,7 @@ public function getLastQueryStatistics() : QueryStatistic * * @return Connection */ - public function getConnection() : Connection + public function getConnection(): Connection { return $this->connection; } diff --git a/src/Integrations/Laravel/Connection.php b/src/Integrations/Laravel/Connection.php index 04b506b..6a8cf38 100644 --- a/src/Integrations/Laravel/Connection.php +++ b/src/Integrations/Laravel/Connection.php @@ -23,66 +23,69 @@ class Connection extends \Illuminate\Database\Connection * @var Client */ protected $client; - + /** * Given config. * * @var array */ protected $config; - + /** * All of the queries run against the connection. * * @var array */ protected $queryLog = []; - + /** * Indicates whether queries are being logged. * * @var bool */ protected $loggingQueries = false; - + /** * The event dispatcher instance. * * @var \Illuminate\Contracts\Events\Dispatcher */ protected $events; - + /** * Indicates if the connection is in a "dry run". * * @var bool */ protected $pretending = false; - + /** - * Last executed query statistic + * Last executed query statistic. * * @var \Tinderbox\Clickhouse\Query\QueryStatistic */ protected $lastQueryStatistic; - + /** * Create a new database connection instance. * * Config should be like this structure for server: * * @param array $config + * + * @throws \Tinderbox\Clickhouse\Exceptions\ClusterException + * @throws \Tinderbox\Clickhouse\Exceptions\ServerProviderException */ public function __construct(array $config) { $this->config = $config; - + $serverProvider = $this->assembleServerProvider($config); - + $transport = $this->createTransport($config['transportOptions'] ?? []); $this->client = $this->createClientFor($serverProvider, $transport); } - + /** * Returns given config. * @@ -95,27 +98,28 @@ public function getConfig($option = null) if (is_null($option)) { return $this->config; } - + return $this->config[$option] ?? null; } - + /** - * Returns statistic for last query + * Returns statistic for last query. * - * @return array|\Tinderbox\Clickhouse\Query\QueryStatistic * @throws \Tinderbox\ClickhouseBuilder\Exceptions\BuilderException + * + * @return array|\Tinderbox\Clickhouse\Query\QueryStatistic */ public function getLastQueryStatistic() { if (is_null($this->lastQueryStatistic)) { throw new BuilderException('Run query before trying to get statistic'); } - + return $this->lastQueryStatistic; } - + /** - * Sets last query statistic + * Sets last query statistic. * * @param array|\Tinderbox\Clickhouse\Query\QueryStatistic $queryStatistic */ @@ -123,11 +127,11 @@ protected function setLastQueryStatistic($queryStatistic) { $this->lastQueryStatistic = $queryStatistic; } - + /** * Creates Clickhouse client. * - * @param mixed $server + * @param mixed $server * @param TransportInterface $transport * * @return Client @@ -136,15 +140,15 @@ protected function createClientFor($server, TransportInterface $transport) { return new Client($server, $transport); } - + /** - * Creates transport + * Creates transport. * - * @param array $options + * @param array $options * * @return \Tinderbox\Clickhouse\Interfaces\TransportInterface */ - protected function createTransport(array $options) : TransportInterface + protected function createTransport(array $options): TransportInterface { $client = $options['client'] ?? null; @@ -152,12 +156,15 @@ protected function createTransport(array $options) : TransportInterface return new HttpTransport($client, $options); } - + /** * Assemble ServerProvider. * * @param array $config * + * @throws \Tinderbox\Clickhouse\Exceptions\ClusterException + * @throws \Tinderbox\Clickhouse\Exceptions\ServerProviderException + * * @return ServerProvider */ protected function assembleServerProvider(array $config) @@ -169,27 +176,28 @@ protected function assembleServerProvider(array $config) return $serverProvider; } - + foreach ($config['clusters'] ?? [] as $clusterName => $servers) { $cluster = new Cluster( - $clusterName, array_map( + $clusterName, + array_map( function ($server) { return $this->assembleServer($server); }, $servers ) ); - + $serverProvider->addCluster($cluster); } - + foreach ($config['servers'] ?? [] as $server) { $serverProvider->addServer($this->assembleServer($server)); } return $serverProvider; } - + /** * Assemble Server instance from array. * @@ -206,17 +214,24 @@ protected function assembleServer(array $server): Server /* @var string $password */ /* @var array $options */ extract($server); - + if (isset($options)) { $protocol = $options['protocol'] ?? null; - + $tags = $options['tags'] ?? []; + $options = new ServerOptions(); - + if (!is_null($protocol)) { $options->setProtocol($protocol); } + + if (is_array($tags) && !empty($tags)) { + foreach ($tags as $tag) { + $options->addTag($tag); + } + } } - + return new Server( $host, $port ?? null, @@ -226,7 +241,7 @@ protected function assembleServer(array $server): Server $options ?? null ); } - + /** * Get a new query builder instance. * @@ -236,12 +251,12 @@ public function query() { return new Builder($this); } - + /** * Begin a fluent query against a database table. * * @param \Closure|Builder|string $table - * @param string|null $as + * @param string|null $as * * @return \Tinderbox\ClickhouseBuilder\Integrations\Laravel\Builder */ @@ -249,7 +264,7 @@ public function table($table, $as = null) { return $this->query()->from($table, $as); } - + /** * Get a new raw query expression. * @@ -261,7 +276,7 @@ public function raw($value) { return new Expression($value); } - + /** * Start a new database transaction. * @@ -273,7 +288,21 @@ public function beginTransaction() { throw NotSupportedException::transactions(); } - + + /** + * Sets Clickhouse client. + * + * @var Client + * + * @return self + */ + public function setClient(Client $client) + { + $this->client = $client; + + return $this; + } + /** * Returns Clickhouse client. * @@ -283,7 +312,7 @@ public function getClient(): Client { return $this->client; } - + /** * Run a select statement against the database. * @@ -296,14 +325,14 @@ public function getClient(): Client public function select($query, $bindings = [], $tables = []) { $result = $this->getClient()->readOne($query, $tables); - + $this->logQuery($result->getQuery()->getQuery(), [], $result->getStatistic()->getTime()); - + $this->setLastQueryStatistic($result->getStatistic()); - + return $result->getRows(); } - + /** * Run a select statements in async mode. * @@ -321,23 +350,23 @@ public function selectAsync(array $queries) $results = $this->getClient()->read($queries); $statistic = []; - + foreach ($results as $i => $result) { /* @var \Tinderbox\Clickhouse\Query\Result $result */ /* @var Query $query */ $query = $result->getQuery(); - + $this->logQuery($query->getQuery(), [], $result->getStatistic()->getTime()); - + $results[$i] = $result->getRows(); $statistic[$i] = $result->getStatistic(); } - + $this->setLastQueryStatistic($statistic); - + return $results; } - + /** * Commit the active database transaction. * @@ -349,7 +378,7 @@ public function commit() { throw NotSupportedException::transactions(); } - + /** * Rollback the active database transaction. * @@ -361,19 +390,17 @@ public function rollBack($toLevel = null) { throw NotSupportedException::transactions(); } - + /** * Get the number of active transactions. * * @throws NotSupportedException - * - * @return int */ public function transactionLevel() { throw NotSupportedException::transactions(); } - + /** * Execute a Closure within a transaction. * @@ -388,7 +415,7 @@ public function transaction(\Closure $callback, $attempts = 1) { throw NotSupportedException::transactions(); } - + /** * Run an insert statement against the database. * @@ -400,34 +427,34 @@ public function transaction(\Closure $callback, $attempts = 1) public function insert($query, $bindings = []) { $startTime = microtime(true); - + $result = $this->getClient()->writeOne($query); - + $this->logQuery($query, $bindings, microtime(true) - $startTime); - + return $result; } - + /** * Run async insert queries from local CSV or TSV files. * - * @param string $table - * @param array $columns - * @param array $files - * @param null $format - * @param int $concurrency + * @param string $table + * @param array $columns + * @param array $files + * @param null|string $format + * @param int $concurrency * * @return array */ public function insertFiles($table, array $columns, array $files, $format = Format::CSV, $concurrency = 5) { $result = $this->getClient()->writeFiles($table, $columns, $files, $format, [], $concurrency); - - $this->logQuery("INSERT ".count($files)." FILES INTO {$table}", []); - + + $this->logQuery('INSERT '.count($files)." FILES INTO {$table}", []); + return $result; } - + /** * Run an update statement against the database. * @@ -440,7 +467,7 @@ public function update($query, $bindings = []) { throw NotSupportedException::update(); } - + /** * Run a delete statement against the database. * @@ -453,7 +480,7 @@ public function delete($query, $bindings = []) { return $this->statement($query); } - + /** * Run an SQL statement and get the number of rows affected. * @@ -466,13 +493,13 @@ public function affectingStatement($query, $bindings = []) { throw new NotSupportedException('This type of queries is not supported'); } - + /** * Run a select statement and return a single result. * * @param string $query * @param array $bindings - * @param array $tables + * @param array $tables * * @return mixed */ @@ -480,7 +507,7 @@ public function selectOne($query, $bindings = [], $tables = []) { return $this->select($query, $bindings, $tables); } - + /** * Execute an SQL statement and return the boolean result. * @@ -492,14 +519,14 @@ public function selectOne($query, $bindings = [], $tables = []) public function statement($query, $bindings = []) { $start = microtime(true); - + $result = $this->getClient()->writeOne($query); - + $this->logQuery($query, $bindings, microtime(true) - $start); - + return $result; } - + /** * Run a raw, unprepared query against the PDO connection. * @@ -511,7 +538,7 @@ public function unprepared($query) { return $this->statement($query); } - + /** * Choose server to perform queries. * @@ -522,24 +549,24 @@ public function unprepared($query) public function using(string $hostname): self { $this->getClient()->using($hostname); - + return $this; } - + /** * Choose cluster to perform queries. * - * @param string $clusterName + * @param string|null $clusterName * * @return Connection */ - public function onCluster(string $clusterName): self + public function onCluster(?string $clusterName): self { $this->getClient()->onCluster($clusterName); - + return $this; } - + /** * Choose random server for each query. * @@ -548,16 +575,30 @@ public function onCluster(string $clusterName): self public function usingRandomServer(): self { $this->getClient()->usingRandomServer(); - + + return $this; + } + + /** + * Choose server with tag for queries. + * + * @param string $tag + * + * @return Connection + */ + public function usingServerWithTag(string $tag): self + { + $this->getClient()->usingServerWithTag($tag); + return $this; } - + /** - * Returns server on which query will be executed + * Returns server on which query will be executed. * * @return Server */ - public function getServer() : Server + public function getServer(): Server { return $this->getClient()->getServer(); } diff --git a/src/Query/ArrayJoinClause.php b/src/Query/ArrayJoinClause.php index 1f0e8b8..cadde21 100644 --- a/src/Query/ArrayJoinClause.php +++ b/src/Query/ArrayJoinClause.php @@ -5,7 +5,7 @@ class ArrayJoinClause { /** - * Identifier of array to join + * Identifier of array to join. * * @var Expression|Identifier */ @@ -35,7 +35,7 @@ public function __construct(BaseBuilder $query) * * @return ArrayJoinClause */ - public function array($arrayIdentifier) : self + public function array($arrayIdentifier): self { if (is_string($arrayIdentifier)) { $arrayIdentifier = new Identifier($arrayIdentifier); diff --git a/src/Query/BaseBuilder.php b/src/Query/BaseBuilder.php index e4f1335..a4b8447 100644 --- a/src/Query/BaseBuilder.php +++ b/src/Query/BaseBuilder.php @@ -21,137 +21,137 @@ abstract class BaseBuilder * @var Column[] */ protected $columns = []; - + /** * Table to select from. * * @var From|null */ protected $from = null; - + /** * Sample expression. * * @var float|null */ protected $sample; - + /** * Join clause. * * @var JoinClause */ protected $join; - + /** * Array join clause. * * @var ArrayJoinClause */ protected $arrayJoin; - + /** * Prewhere statements. * * @var TwoElementsLogicExpression[] */ protected $prewheres = []; - + /** * Where statements. * * @var TwoElementsLogicExpression[] */ protected $wheres = []; - + /** * Groupings. * * @var array */ protected $groups = []; - + /** * Having statements. * * @var TwoElementsLogicExpression[] */ protected $havings = []; - + /** * Order statements. * * @var array */ protected $orders = []; - + /** * Limit. * * @var Limit|null */ protected $limit; - + /** * Limit n by statement. * * @var Limit|null */ protected $limitBy; - + /** * Queries to union. * * @var array */ protected $unions = []; - + /** * Query format. * * @var Format|null */ protected $format; - + /** * Grammar to build query parts. * * @var Grammar */ protected $grammar; - + /** * Queries which must be run asynchronous. * * @var array */ protected $async = []; - + /** - * Files which should be sent on server to store into temporary table + * Files which should be sent on server to store into temporary table. * * @var array */ protected $files = []; - + /** - * Cluster name + * Cluster name. * * @var string */ protected $onCluster; /** - * File representing values which should be inserted in table + * File representing values which should be inserted in table. * * @var FileInterface */ protected $values; - + protected $clusterName; - + protected $serverHostname; - + /** * Set columns for select statement. * @@ -162,40 +162,36 @@ abstract class BaseBuilder public function select(...$columns) { $columns = isset($columns[0]) && is_array($columns[0]) ? $columns[0] : $columns; - + if (empty($columns)) { $columns[] = '*'; } - + $this->columns = $this->processColumns($columns); - + return $this; } - + /** - * Returns query for count total rows without limit - * - * @param string $column + * Returns query for count total rows without limit. * * @return static */ public function getCountQuery() { $without = ['columns' => [], 'limit' => null]; - + if (empty($this->groups)) { $without['orders'] = []; } - - $builder = $this->cloneWithout($without)->select(raw('count() as `count`')); - - return $builder; + + return $this->cloneWithout($without)->select(raw('count() as `count`')); } - + /** * Clone the query without the given properties. * - * @param array $except + * @param array $except * * @return static */ @@ -210,7 +206,7 @@ function ($clone) use ($except) { } ); } - + /** * Add columns to exist select statement. * @@ -221,16 +217,16 @@ function ($clone) use ($except) { public function addSelect(...$columns) { $columns = isset($columns[0]) && is_array($columns[0]) ? $columns[0] : $columns; - + if (empty($columns)) { $columns[] = '*'; } - + $this->columns = array_merge($this->columns, $this->processColumns($columns)); - + return $this; } - + /** * Prepares columns given by user to Column objects. * @@ -242,53 +238,53 @@ public function addSelect(...$columns) protected function processColumns(array $columns, bool $withAliases = true): array { $result = []; - + foreach ($columns as $column => $value) { if ($value instanceof Closure) { $columnName = $column; $column = (new Column($this)); - + if (!is_int($columnName)) { $column->name($columnName); } - + $column = tap($column, $value); - + if ($column->getSubQuery()) { $column->query($column->getSubQuery()); } } - + if ($value instanceof BaseBuilder) { $alias = is_string($column) ? $column : null; $column = (new Column($this))->query($value); - + if (!is_null($alias) && $withAliases) { $column->as($alias); } } - + if (is_int($column)) { $column = $value; $value = null; } - + if (!$column instanceof Column) { $alias = is_string($value) ? $value : null; - + $column = (new Column($this))->name($column); - + if (!is_null($alias) && $withAliases) { $column->as($alias); } } - + $result[] = $column; } - + return $result; } - + /** * Sets table to from statement. * @@ -301,16 +297,16 @@ protected function processColumns(array $columns, bool $withAliases = true): arr public function from($table, string $alias = null, bool $isFinal = null) { $this->from = new From($this); - + /* * If builder instance given, then we assume that from section should contain sub-query */ if ($table instanceof BaseBuilder) { $this->from->query($table); - + $this->files = array_merge($this->files, $table->getFiles()); } - + /* * If closure given, then we call it and pass From object as argument to * set up From object in callback @@ -318,7 +314,7 @@ public function from($table, string $alias = null, bool $isFinal = null) if ($table instanceof Closure) { $table($this->from); } - + /* * If given anything that is not builder instance or callback. For example, string, * then we assume that table name was given. @@ -326,25 +322,25 @@ public function from($table, string $alias = null, bool $isFinal = null) if (!$table instanceof Closure && !$table instanceof BaseBuilder) { $this->from->table($table); } - + if (!is_null($alias)) { $this->from->as($alias); } - + if (!is_null($isFinal)) { $this->from->final($isFinal); } - + /* * If subQuery method was executed on From object, then we take subQuery and "execute" it */ if (!is_null($this->from->getSubQuery())) { $this->from->query($this->from->getSubQuery()); } - + return $this; } - + /** * Alias for from method. * @@ -358,7 +354,7 @@ public function table($table, string $alias = null, bool $isFinal = null) { return $this->from($table, $alias, $isFinal); } - + /** * Set sample expression. * @@ -369,10 +365,10 @@ public function table($table, string $alias = null, bool $isFinal = null) public function sample(float $coefficient) { $this->sample = $coefficient; - + return $this; } - + /** * Add queries to union with. * @@ -385,16 +381,16 @@ public function unionAll($query) if ($query instanceof Closure) { $query = tap($this->newQuery(), $query); } - + if ($query instanceof BaseBuilder) { $this->unions[] = $query; } else { throw new \InvalidArgumentException('Argument for unionAll must be closure or builder instance.'); } - + return $this; } - + /** * Set alias for table in from statement. * @@ -405,10 +401,10 @@ public function unionAll($query) public function as(string $alias) { $this->from->as($alias); - + return $this; } - + /** * As method alias. * @@ -420,7 +416,7 @@ public function alias(string $alias) { return $this->as($alias); } - + /** * Sets final option on from statement. * @@ -431,10 +427,10 @@ public function alias(string $alias) public function final(bool $final = true) { $this->from->final($final); - + return $this; } - + /** * Sets on cluster option for query. * @@ -445,10 +441,10 @@ public function final(bool $final = true) public function onCluster(string $clusterName) { $this->onCluster = $clusterName; - + return $this; } - + /** * Add array join to query. * @@ -460,10 +456,10 @@ public function arrayJoin($arrayIdentifier) { $this->arrayJoin = new ArrayJoinClause($this); $this->arrayJoin->array($arrayIdentifier); - + return $this; } - + /** * Add join to query. * @@ -472,6 +468,7 @@ public function arrayJoin($arrayIdentifier) * @param string|null $type Left or inner * @param array|null $using Columns to use for join * @param bool $global Global distribution for right table + * @param string|null $alias Alias of joined table or sub-query * * @return static */ @@ -480,19 +477,20 @@ public function join( string $strict = null, string $type = null, array $using = null, - bool $global = false + bool $global = false, + ?string $alias = null ) { $this->join = new JoinClause($this); - + /* * If builder instance given, then we assume that sub-query should be used as table in join */ if ($table instanceof BaseBuilder) { $this->join->query($table); - + $this->files = array_merge($this->files, $table->getFiles()); } - + /* * If closure given, then we call it and pass From object as argument to * set up JoinClause object in callback @@ -500,7 +498,7 @@ public function join( if ($table instanceof Closure) { $table($this->join); } - + /* * If given anything that is not builder instance or callback. For example, string, * then we assume that table name was given. @@ -508,31 +506,35 @@ public function join( if (!$table instanceof Closure && !$table instanceof BaseBuilder) { $this->join->table($table); } - + /* * If using was given, then merge it with using given before, in closure */ if (!is_null($using)) { $this->join->addUsing($using); } - + if (!is_null($strict) && is_null($this->join->getStrict())) { $this->join->strict($strict); } - + if (!is_null($type) && is_null($this->join->getType())) { $this->join->type($type); } - + + if (!is_null($alias) && is_null($this->join->getAlias())) { + $this->join->as($alias); + } + $this->join->distributed($global); - + if (!is_null($this->join->getSubQuery())) { $this->join->query($this->join->getSubQuery()); } - + return $this; } - + /** * Left join. * @@ -542,14 +544,15 @@ public function join( * @param string|null $strict * @param array|null $using * @param bool $global + * @param string|null $alias * * @return static */ - public function leftJoin($table, string $strict = null, array $using = null, bool $global = false) + public function leftJoin($table, string $strict = null, array $using = null, bool $global = false, ?string $alias = null) { - return $this->join($table, $strict ?? JoinStrict::ALL, JoinType::LEFT, $using, $global); + return $this->join($table, $strict ?? JoinStrict::ALL, JoinType::LEFT, $using, $global, $alias); } - + /** * Inner join. * @@ -559,14 +562,15 @@ public function leftJoin($table, string $strict = null, array $using = null, boo * @param string|null $strict * @param array|null $using * @param bool $global + * @param string|null $alias * * @return static */ - public function innerJoin($table, string $strict = null, array $using = null, bool $global = false) + public function innerJoin($table, string $strict = null, array $using = null, bool $global = false, ?string $alias = null) { - return $this->join($table, $strict ?? JoinStrict::ALL, JoinType::INNER, $using, $global); + return $this->join($table, $strict ?? JoinStrict::ALL, JoinType::INNER, $using, $global, $alias); } - + /** * Any left join. * @@ -575,14 +579,15 @@ public function innerJoin($table, string $strict = null, array $using = null, bo * @param string|self|Closure $table * @param array|null $using * @param bool $global + * @param string|null $alias * * @return static */ - public function anyLeftJoin($table, array $using = null, bool $global = false) + public function anyLeftJoin($table, array $using = null, bool $global = false, ?string $alias = null) { - return $this->join($table, JoinStrict::ANY, JoinType::LEFT, $using, $global); + return $this->join($table, JoinStrict::ANY, JoinType::LEFT, $using, $global, $alias); } - + /** * All left join. * @@ -591,14 +596,15 @@ public function anyLeftJoin($table, array $using = null, bool $global = false) * @param string|self|Closure $table * @param array|null $using * @param bool $global + * @param string|null $alias * * @return static */ - public function allLeftJoin($table, array $using = null, bool $global = false) + public function allLeftJoin($table, array $using = null, bool $global = false, ?string $alias = null) { - return $this->join($table, JoinStrict::ALL, JoinType::LEFT, $using, $global); + return $this->join($table, JoinStrict::ALL, JoinType::LEFT, $using, $global, $alias); } - + /** * Any inner join. * @@ -607,14 +613,15 @@ public function allLeftJoin($table, array $using = null, bool $global = false) * @param string|self|Closure $table * @param array|null $using * @param bool $global + * @param string|null $alias * * @return static */ - public function anyInnerJoin($table, array $using = null, bool $global = false) + public function anyInnerJoin($table, array $using = null, bool $global = false, ?string $alias = null) { - return $this->join($table, JoinStrict::ANY, JoinType::INNER, $using, $global); + return $this->join($table, JoinStrict::ANY, JoinType::INNER, $using, $global, $alias); } - + /** * All inner join. * @@ -623,14 +630,15 @@ public function anyInnerJoin($table, array $using = null, bool $global = false) * @param string|self|Closure $table * @param array|null $using * @param bool $global + * @param string|null $alias * * @return static */ - public function allInnerJoin($table, array $using = null, bool $global = false) + public function allInnerJoin($table, array $using = null, bool $global = false, ?string $alias = null) { - return $this->join($table, JoinStrict::ALL, JoinType::INNER, $using, $global); + return $this->join($table, JoinStrict::ALL, JoinType::INNER, $using, $global, $alias); } - + /** * Get two elements logic expression to put it in the right place. * @@ -653,23 +661,23 @@ protected function assembleTwoElementsLogicExpression( string $section ): TwoElementsLogicExpression { $expression = new TwoElementsLogicExpression($this); - + /* * If user passed TwoElementsLogicExpression as first argument, then we assume that user has set up himself. */ if ($column instanceof TwoElementsLogicExpression && is_null($value)) { return $column; } - + if ($column instanceof TwoElementsLogicExpression && $value instanceof TwoElementsLogicExpression) { $expression->firstElement($column); $expression->secondElement($value); $expression->operator($operator); $expression->concatOperator($concatOperator); - + return $expression; } - + /* * If closure, then we pass fresh query builder inside and based on their state after evaluating try to assume * what user expects to perform. @@ -678,40 +686,40 @@ protected function assembleTwoElementsLogicExpression( */ if ($column instanceof Closure) { $query = tap($this->newQuery(), $column); - + if (is_null($query->getFrom()) && empty($query->getColumns())) { $expression->firstElement($query->{"get{$section}"}()); } else { $expression->firstElement(new Expression("({$query->toSql()})")); } } - + /* * If as column was passed builder instance, than we perform subquery in first element position. */ if ($column instanceof BaseBuilder) { $expression->firstElementQuery($column); } - + /* * If builder instance given as value, then we assume that sub-query should be used there. */ if ($value instanceof BaseBuilder || $value instanceof Closure) { $expression->secondElementQuery($value); } - + /* * Set up other parameters if none of them was set up before in TwoElementsLogicExpression object */ if (is_null($expression->getFirstElement()) && !is_null($column)) { $expression->firstElement(is_string($column) ? new Identifier($column) : $column); } - + if (is_null($expression->getSecondElement()) && !is_null($value)) { if (is_array($value) && count($value) === 2 && Operator::isValid($operator) && in_array( - $operator, - [Operator::BETWEEN, Operator::NOT_BETWEEN] - ) + $operator, + [Operator::BETWEEN, Operator::NOT_BETWEEN] + ) ) { $value = (new TwoElementsLogicExpression($this)) ->firstElement($value[0]) @@ -719,27 +727,27 @@ protected function assembleTwoElementsLogicExpression( ->secondElement($value[1]) ->concatOperator($concatOperator); } - + if (is_array($value) && Operator::isValid($operator) && in_array( - $operator, - [Operator::IN, Operator::NOT_IN] - ) + $operator, + [Operator::IN, Operator::NOT_IN] + ) ) { $value = new Tuple($value); } - + $expression->secondElement($value); } - + $expression->concatOperator($concatOperator); - + if (is_string($operator)) { $expression->operator($operator); } - + return $expression; } - + /** * Prepare operator for where and prewhere statement. * @@ -753,19 +761,19 @@ protected function prepareValueAndOperator($value, $operator, $useDefault = fals { if ($useDefault) { $value = $operator; - + if (is_array($value)) { $operator = Operator::IN; } else { $operator = Operator::EQUALS; } - + return [$value, $operator]; } - + return [$value, $operator]; } - + /** * Add prewhere statement. * @@ -779,7 +787,7 @@ protected function prepareValueAndOperator($value, $operator, $useDefault = fals public function preWhere($column, $operator = null, $value = null, string $concatOperator = Operator:: AND) { list($value, $operator) = $this->prepareValueAndOperator($value, $operator, func_num_args() == 2); - + $this->prewheres[] = $this->assembleTwoElementsLogicExpression( $column, $operator, @@ -787,10 +795,10 @@ public function preWhere($column, $operator = null, $value = null, string $conca $concatOperator, 'prewheres' ); - + return $this; } - + /** * Add prewhere statement "as is". * @@ -802,7 +810,7 @@ public function preWhereRaw(string $expression) { return $this->preWhere(new Expression($expression)); } - + /** * Add prewhere statement "as is", but with OR operator. * @@ -814,7 +822,7 @@ public function orPreWhereRaw(string $expression) { return $this->preWhere(new Expression($expression), null, null, Operator:: OR); } - + /** * Add prewhere statement but with OR operator. * @@ -827,10 +835,10 @@ public function orPreWhereRaw(string $expression) public function orPreWhere($column, $operator = null, $value = null) { list($value, $operator) = $this->prepareValueAndOperator($value, $operator, func_num_args() == 2); - + return $this->prewhere($column, $operator, $value, Operator:: OR); } - + /** * Add prewhere statement with IN operator. * @@ -844,16 +852,16 @@ public function orPreWhere($column, $operator = null, $value = null) public function preWhereIn($column, $values, $boolean = Operator:: AND, $not = false) { $type = $not ? Operator::NOT_IN : Operator::IN; - + if (is_array($values)) { $values = new Tuple($values); } elseif (is_string($values) && isset($this->files[$values])) { $values = new Identifier($values); } - + return $this->preWhere($column, $type, $values, $boolean); } - + /** * Add prewhere statement with IN operator and OR operator. * @@ -866,7 +874,7 @@ public function orPreWhereIn($column, $values) { return $this->preWhereIn($column, $values, Operator:: OR); } - + /** * Add prewhere statement with NOT IN operator. * @@ -880,7 +888,7 @@ public function preWhereNotIn($column, $values, $boolean = Operator:: AND) { return $this->preWhereIn($column, $values, $boolean, true); } - + /** * Add prewhere statement with NOT IN operator and OR operator. * @@ -894,7 +902,7 @@ public function orPreWhereNotIn($column, $values, $boolean = Operator:: OR) { return $this->preWhereNotIn($column, $values, $boolean); } - + /** * Add prewhere statement with BETWEEN simulation. * @@ -908,10 +916,10 @@ public function orPreWhereNotIn($column, $values, $boolean = Operator:: OR) public function preWhereBetween($column, array $values, $boolean = Operator:: AND, $not = false) { $type = $not ? Operator::NOT_BETWEEN : Operator::BETWEEN; - + return $this->preWhere($column, $type, [$values[0], $values[1]], $boolean); } - + /** * Add prewhere statement with BETWEEN simulation, but with column names as value. * @@ -925,10 +933,10 @@ public function preWhereBetween($column, array $values, $boolean = Operator:: AN public function preWhereBetweenColumns($column, array $values, $boolean = Operator:: AND, $not = false) { $type = $not ? Operator::NOT_BETWEEN : Operator::BETWEEN; - + return $this->preWhere($column, $type, [new Identifier($values[0]), new Identifier($values[1])], $boolean); } - + /** * Add prewhere statement with NOT BETWEEN simulation, but with column names as value. * @@ -947,7 +955,7 @@ public function preWhereNotBetweenColumns($column, array $values, $boolean = Ope $boolean ); } - + /** * Add prewhere statement with BETWEEN simulation, but with column names as value and OR operator. * @@ -960,7 +968,7 @@ public function orPreWhereBetweenColumns($column, array $values) { return $this->preWhereBetweenColumns($column, $values, Operator:: OR); } - + /** * Add prewhere statement with NOT BETWEEN simulation, but with column names as value and OR operator. * @@ -973,7 +981,7 @@ public function orPreWhereNotBetweenColumns($column, array $values) { return $this->preWhereNotBetweenColumns($column, $values, Operator:: OR); } - + /** * Add prewhere statement with BETWEEN simulation and OR operator. * @@ -986,7 +994,7 @@ public function orPreWhereBetween($column, array $values) { return $this->preWhereBetween($column, $values, Operator:: OR); } - + /** * Add prewhere statement with NOT BETWEEN simulation. * @@ -1000,7 +1008,7 @@ public function preWhereNotBetween($column, array $values, $boolean = Operator:: { return $this->preWhereBetween($column, $values, $boolean, true); } - + /** * Add prewhere statement with NOT BETWEEN simulation and OR operator. * @@ -1013,7 +1021,7 @@ public function orPreWhereNotBetween($column, array $values) { return $this->preWhereNotBetween($column, $values, Operator:: OR); } - + /** * Add where statement. * @@ -1027,7 +1035,7 @@ public function orPreWhereNotBetween($column, array $values) public function where($column, $operator = null, $value = null, string $concatOperator = Operator:: AND) { list($value, $operator) = $this->prepareValueAndOperator($value, $operator, func_num_args() == 2); - + $this->wheres[] = $this->assembleTwoElementsLogicExpression( $column, $operator, @@ -1035,10 +1043,10 @@ public function where($column, $operator = null, $value = null, string $concatOp $concatOperator, 'wheres' ); - + return $this; } - + /** * Add where statement "as is". * @@ -1050,7 +1058,7 @@ public function whereRaw(string $expression) { return $this->where(new Expression($expression)); } - + /** * Add where statement "as is" with OR operator. * @@ -1062,7 +1070,7 @@ public function orWhereRaw(string $expression) { return $this->where(new Expression($expression), null, null, Operator:: OR); } - + /** * Add where statement with OR operator. * @@ -1075,10 +1083,10 @@ public function orWhereRaw(string $expression) public function orWhere($column, $operator = null, $value = null) { list($value, $operator) = $this->prepareValueAndOperator($value, $operator, func_num_args() == 2); - + return $this->where($column, $operator, $value, Operator:: OR); } - + /** * Add where statement with IN operator. * @@ -1105,7 +1113,7 @@ public function whereIn($column, $values, $boolean = Operator:: AND, $not = fals return $this->where($column, $type, $values, $boolean); } - + /** * Add where statement with GLOBAL option and IN operator. * @@ -1119,16 +1127,16 @@ public function whereIn($column, $values, $boolean = Operator:: AND, $not = fals public function whereGlobalIn($column, $values, $boolean = Operator:: AND, $not = false) { $type = $not ? Operator::GLOBAL_NOT_IN : Operator::GLOBAL_IN; - + if (is_array($values)) { $values = new Tuple($values); } elseif (is_string($values) && isset($this->files[$values])) { $values = new Identifier($values); } - + return $this->where($column, $type, $values, $boolean); } - + /** * Add where statement with GLOBAL option and IN operator and OR operator. * @@ -1141,7 +1149,7 @@ public function orWhereGlobalIn($column, $values) { return $this->whereGlobalIn($column, $values, Operator:: OR); } - + /** * Add where statement with GLOBAL option and NOT IN operator. * @@ -1155,7 +1163,7 @@ public function whereGlobalNotIn($column, $values, $boolean = Operator:: AND) { return $this->whereGlobalIn($column, $values, $boolean, true); } - + /** * Add where statement with GLOBAL option and NOT IN operator and OR operator. * @@ -1169,7 +1177,7 @@ public function orWhereGlobalNotIn($column, $values, $boolean = Operator:: OR) { return $this->whereGlobalNotIn($column, $values, $boolean); } - + /** * Add where statement with IN operator and OR operator. * @@ -1182,7 +1190,7 @@ public function orWhereIn($column, $values) { return $this->whereIn($column, $values, Operator:: OR); } - + /** * Add where statement with NOT IN operator. * @@ -1196,7 +1204,7 @@ public function whereNotIn($column, $values, $boolean = Operator:: AND) { return $this->whereIn($column, $values, $boolean, true); } - + /** * Add where statement with NOT IN operator and OR operator. * @@ -1210,7 +1218,7 @@ public function orWhereNotIn($column, $values, $boolean = Operator:: OR) { return $this->whereNotIn($column, $values, $boolean); } - + /** * Add where statement with BETWEEN simulation. * @@ -1224,10 +1232,10 @@ public function orWhereNotIn($column, $values, $boolean = Operator:: OR) public function whereBetween($column, array $values, $boolean = Operator:: AND, $not = false) { $operator = $not ? Operator::NOT_BETWEEN : Operator::BETWEEN; - + return $this->where($column, $operator, [$values[0], $values[1]], $boolean); } - + /** * Add where statement with BETWEEN simulation, but with column names as value. * @@ -1241,10 +1249,10 @@ public function whereBetween($column, array $values, $boolean = Operator:: AND, public function whereBetweenColumns($column, array $values, $boolean = Operator:: AND, $not = false) { $type = $not ? Operator::NOT_BETWEEN : Operator::BETWEEN; - + return $this->where($column, $type, [new Identifier($values[0]), new Identifier($values[1])], $boolean); } - + /** * Add where statement with BETWEEN simulation, but with column names as value and OR operator. * @@ -1257,7 +1265,7 @@ public function orWhereBetweenColumns($column, array $values) { return $this->whereBetweenColumns($column, $values, Operator:: OR); } - + /** * Add where statement with BETWEEN simulation and OR operator. * @@ -1270,7 +1278,7 @@ public function orWhereBetween($column, array $values) { return $this->whereBetween($column, $values, Operator:: OR); } - + /** * Add where statement with NOT BETWEEN simulation. * @@ -1284,7 +1292,7 @@ public function whereNotBetween($column, array $values, $boolean = Operator:: AN { return $this->whereBetween($column, $values, $boolean, true); } - + /** * Add prewhere statement with NOT BETWEEN simulation and OR operator. * @@ -1297,7 +1305,7 @@ public function orWhereNotBetween($column, array $values) { return $this->whereNotBetween($column, $values, Operator:: OR); } - + /** * Add having statement. * @@ -1311,7 +1319,7 @@ public function orWhereNotBetween($column, array $values) public function having($column, $operator = null, $value = null, string $concatOperator = Operator:: AND) { list($value, $operator) = $this->prepareValueAndOperator($value, $operator, func_num_args() == 2); - + $this->havings[] = $this->assembleTwoElementsLogicExpression( $column, $operator, @@ -1319,10 +1327,10 @@ public function having($column, $operator = null, $value = null, string $concatO $concatOperator, 'havings' ); - + return $this; } - + /** * Add having statement "as is". * @@ -1334,7 +1342,7 @@ public function havingRaw(string $expression) { return $this->having(new Expression($expression)); } - + /** * Add having statement "as is" with OR operator. * @@ -1346,7 +1354,7 @@ public function orHavingRaw(string $expression) { return $this->having(new Expression($expression), null, null, Operator:: OR); } - + /** * Add having statement with OR operator. * @@ -1359,10 +1367,10 @@ public function orHavingRaw(string $expression) public function orHaving($column, $operator = null, $value = null) { list($value, $operator) = $this->prepareValueAndOperator($value, $operator, func_num_args() == 2); - + return $this->having($column, $operator, $value, Operator:: OR); } - + /** * Add having statement with IN operator. * @@ -1376,16 +1384,16 @@ public function orHaving($column, $operator = null, $value = null) public function havingIn($column, $values, $boolean = Operator:: AND, $not = false) { $type = $not ? Operator::NOT_IN : Operator::IN; - + if (is_array($values)) { $values = new Tuple($values); } elseif (is_string($values) && isset($this->files[$values])) { $values = new Identifier($values); } - + return $this->having($column, $type, $values, $boolean); } - + /** * Add having statement with IN operator and OR operator. * @@ -1398,7 +1406,7 @@ public function orHavingIn($column, $values) { return $this->havingIn($column, $values, Operator:: OR); } - + /** * Add having statement with NOT IN operator. * @@ -1412,7 +1420,7 @@ public function havingNotIn($column, $values, $boolean = Operator:: AND) { return $this->havingIn($column, $values, $boolean, true); } - + /** * Add having statement with NOT IN operator and OR operator. * @@ -1426,7 +1434,7 @@ public function orHavingNotIn($column, $values, $boolean = Operator:: OR) { return $this->havingNotIn($column, $values, $boolean); } - + /** * Add having statement with BETWEEN simulation. * @@ -1440,10 +1448,10 @@ public function orHavingNotIn($column, $values, $boolean = Operator:: OR) public function havingBetween($column, array $values, $boolean = Operator:: AND, $not = false) { $operator = $not ? Operator::NOT_BETWEEN : Operator::BETWEEN; - + return $this->having($column, $operator, [$values[0], $values[1]], $boolean); } - + /** * Add having statement with BETWEEN simulation, but with column names as value. * @@ -1457,10 +1465,10 @@ public function havingBetween($column, array $values, $boolean = Operator:: AND, public function havingBetweenColumns($column, array $values, $boolean = Operator:: AND, $not = false) { $type = $not ? Operator::NOT_BETWEEN : Operator::BETWEEN; - + return $this->having($column, $type, [new Identifier($values[0]), new Identifier($values[1])], $boolean); } - + /** * Add having statement with BETWEEN simulation, but with column names as value and OR operator. * @@ -1473,7 +1481,7 @@ public function orHavingBetweenColumns($column, array $values) { return $this->havingBetweenColumns($column, $values, Operator:: OR); } - + /** * Add having statement with BETWEEN simulation and OR operator. * @@ -1486,7 +1494,7 @@ public function orHavingBetween($column, array $values) { return $this->havingBetween($column, $values, Operator:: OR); } - + /** * Add having statement with NOT BETWEEN simulation. * @@ -1500,7 +1508,7 @@ public function havingNotBetween($column, array $values, $boolean = Operator:: A { return $this->havingBetween($column, $values, $boolean, true); } - + /** * Add having statement with NOT BETWEEN simulation and OR operator. * @@ -1513,7 +1521,7 @@ public function orHavingNotBetween($column, array $values) { return $this->havingNotBetween($column, $values, Operator:: OR); } - + /** * Add dictionary value to select statement. * @@ -1529,16 +1537,16 @@ public function addSelectDict(string $dict, string $attribute, $key, string $as if (is_null($as)) { $as = $attribute; } - + $id = is_array($key) ? 'tuple('.implode( - ', ', - array_map([$this->grammar, 'wrap'], $key) - ).')' : $this->grammar->wrap($key); - + ', ', + array_map([$this->grammar, 'wrap'], $key) + ).')' : $this->grammar->wrap($key); + return $this ->addSelect(new Expression("dictGetString('{$dict}', '{$attribute}', {$id}) as `{$as}`")); } - + /** * Add where on dictionary value in where statement. * @@ -1560,12 +1568,12 @@ public function whereDict( string $concatOperator = Operator:: AND ) { $this->addSelectDict($dict, $attribute, $key); - + list($value, $operator) = $this->prepareValueAndOperator($value, $operator, func_num_args() == 4); - + return $this->where($attribute, $operator, $value, $concatOperator); } - + /** * Add where on dictionary value in where statement and OR operator. * @@ -1585,10 +1593,10 @@ public function orWhereDict( $value = null ) { list($value, $operator) = $this->prepareValueAndOperator($value, $operator, func_num_args() == 4); - + return $this->whereDict($dict, $attribute, $key, $operator, $value, Operator:: OR); } - + /** * Add request which must be runned asynchronous. * @@ -1601,20 +1609,20 @@ public function asyncWithQuery($asyncQueries = null) if (is_null($asyncQueries)) { return $this->async[] = $this->newQuery(); } - + if ($asyncQueries instanceof Closure) { $asyncQueries = tap($this->newQuery(), $asyncQueries); } - + if ($asyncQueries instanceof BaseBuilder) { $this->async[] = $asyncQueries; } else { throw new \InvalidArgumentException('Argument for async method must be Closure, Builder or nothing'); } - + return $this; } - + /** * Add limit statement. * @@ -1626,10 +1634,10 @@ public function asyncWithQuery($asyncQueries = null) public function limit(int $limit, int $offset = null) { $this->limit = new Limit($limit, $offset); - + return $this; } - + /** * Add limit n by statement. * @@ -1641,12 +1649,12 @@ public function limit(int $limit, int $offset = null) public function limitBy(int $count, ...$columns) { $columns = isset($columns[0]) && is_array($columns[0]) ? $columns[0] : $columns; - + $this->limitBy = new Limit($count, null, $this->processColumns($columns, false)); - + return $this; } - + /** * Alias for limit method. * @@ -1659,7 +1667,7 @@ public function take(int $limit, int $offset = null) { return $this->limit($limit, $offset); } - + /** * Alias for limitBy method. * @@ -1672,7 +1680,7 @@ public function takeBy(int $count, ...$columns) { return $this->limitBy($count, ...$columns); } - + /** * Add group by statement. * @@ -1683,14 +1691,14 @@ public function takeBy(int $count, ...$columns) public function groupBy(...$columns) { $columns = isset($columns[0]) && is_array($columns[0]) ? $columns[0] : $columns; - + $this->groups = $this->processColumns($columns, false); - + return $this; } - + /** - * Add group by statement to exist group statements + * Add group by statement to exist group statements. * * @param $columns * @@ -1699,12 +1707,12 @@ public function groupBy(...$columns) public function addGroupBy(...$columns) { $columns = isset($columns[0]) && is_array($columns[0]) ? $columns[0] : $columns; - + $this->groups = array_merge($this->groups, $this->processColumns($columns, false)); - + return $this; } - + /** * Add order by statement. * @@ -1717,14 +1725,14 @@ public function addGroupBy(...$columns) public function orderBy($column, string $direction = 'asc', string $collate = null) { $column = $this->processColumns([$column], false)[0]; - + $direction = new OrderDirection(strtoupper($direction)); - + $this->orders[] = [$column, $direction, $collate]; - + return $this; } - + /** * Add order by statement "as is". * @@ -1736,10 +1744,10 @@ public function orderByRaw(string $expression) { $column = $this->processColumns([new Expression($expression)], false)[0]; $this->orders[] = [$column, null, null]; - + return $this; } - + /** * Add ASC order statement. * @@ -1752,7 +1760,7 @@ public function orderByAsc($column, string $collate = null) { return $this->orderBy($column, OrderDirection::ASC, $collate); } - + /** * Add DESC order statement. * @@ -1765,7 +1773,7 @@ public function orderByDesc($column, string $collate = null) { return $this->orderBy($column, OrderDirection::DESC, $collate); } - + /** * Set query result format. * @@ -1776,10 +1784,10 @@ public function orderByDesc($column, string $collate = null) public function format(string $format) { $this->format = new Format(strtoupper($format)); - + return $this; } - + /** * Get the SQL representation of the query. * @@ -1789,7 +1797,7 @@ public function toSql(): string { return $this->grammar->compileSelect($this); } - + /** * Get an array of the SQL queries from all added async builders. * @@ -1799,12 +1807,13 @@ public function toAsyncSqls(): array { return array_map( function ($query) { + /** @var self $query */ return ['query' => $query->toSql(), 'files' => $query->getFiles()]; }, $this->getAsyncQueries() ); } - + /** * Get an array of the SQL queries from all added async builders. * @@ -1814,12 +1823,13 @@ public function toAsyncQueries(): array { return array_map( function ($query) { + /** @var self $query */ return $query->toQuery(); }, $this->getAsyncQueries() ); } - + /** * Get columns for select statement. * @@ -1829,7 +1839,7 @@ public function getColumns(): array { return $this->columns; } - + /** * Get order statements. * @@ -1839,7 +1849,7 @@ public function getOrders(): array { return $this->orders; } - + /** * Get group statements. * @@ -1849,7 +1859,7 @@ public function getGroups(): array { return $this->groups; } - + /** * Get having statements. * @@ -1859,7 +1869,7 @@ public function getHavings(): array { return $this->havings; } - + /** * Get prewhere statements. * @@ -1869,7 +1879,7 @@ public function getPreWheres(): array { return $this->prewheres; } - + /** * Get where statements. * @@ -1879,9 +1889,9 @@ public function getWheres(): array { return $this->wheres; } - + /** - * Get cluster name + * Get cluster name. * * @return null|string */ @@ -1889,7 +1899,7 @@ public function getOnCluster(): ?string { return $this->onCluster; } - + /** * Get From object. * @@ -1899,9 +1909,9 @@ public function getFrom(): ?From { return $this->from; } - + /** - * Get ArrayJoinClause + * Get ArrayJoinClause. * * @return null|ArrayJoinClause */ @@ -1909,7 +1919,7 @@ public function getArrayJoin(): ?ArrayJoinClause { return $this->arrayJoin; } - + /** * Get JoinClause. * @@ -1919,7 +1929,7 @@ public function getJoin(): ?JoinClause { return $this->join; } - + /** * Get limit statement. * @@ -1929,7 +1939,7 @@ public function getLimit(): ?Limit { return $this->limit; } - + /** * Get limit by statement. * @@ -1939,7 +1949,7 @@ public function getLimitBy(): ?Limit { return $this->limitBy; } - + /** * Get sample statement. * @@ -1949,7 +1959,7 @@ public function getSample(): ?float { return $this->sample; } - + /** * Get query unions. * @@ -1959,7 +1969,7 @@ public function getUnions(): array { return $this->unions; } - + /** * Get format. * @@ -1969,18 +1979,18 @@ public function getFormat(): ?Format { return $this->format; } - + /** - * Add file with data to query + * Add file with data to query. * - * @param TempTable $file + * @param TempTable $file * * @return $this */ public function addFile(TempTable $file) { $this->files[$file->getName()] = $file; - + return $this; } @@ -1989,13 +1999,13 @@ public function values($values) $this->values = $this->prepareFile($values); } - public function getValues() : FileInterface + public function getValues(): FileInterface { return $this->values; } - + /** - * Returns files which should be sent on server + * Returns files which should be sent on server. * * @return array */ @@ -2003,7 +2013,7 @@ public function getFiles(): array { return $this->files; } - + /** * Gather all builders from builder. Including nested in async builders. * @@ -2012,22 +2022,22 @@ public function getFiles(): array public function getAsyncQueries(): array { $result = []; - + foreach ($this->async as $query) { $result = array_merge($query->getAsyncQueries(), $result); } - + return array_merge([$this], $result); } /** - * Prepares file + * Prepares file. * * @param $file * * @return File|FileFromString */ - protected function prepareFile($file) : FileInterface + protected function prepareFile($file): FileInterface { $file = file_from($file); diff --git a/src/Query/Builder.php b/src/Query/Builder.php index f8438f4..f9cab5a 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -14,7 +14,7 @@ class Builder extends BaseBuilder * @var \Tinderbox\Clickhouse\Client */ protected $client; - + /** * Builder constructor. * @@ -41,9 +41,9 @@ public function get(array $settings = []) return $this->client->readOne($this->toSql(), $this->getFiles(), $settings); } } - + /** - * Returns Query instance + * Returns Query instance. * * @param array $settings * @@ -53,7 +53,7 @@ public function toQuery(array $settings = []): Query { return new Query($this->client->getServer(), $this->toSql(), $this->getFiles(), $settings); } - + /** * Performs compiled sql for count rows only. May be used for pagination * Works only without async queries. @@ -64,14 +64,14 @@ public function count() { $builder = $this->getCountQuery(); $result = $builder->get(); - + if (!empty($this->groups)) { return count($result); } else { return $result[0]['count'] ?? 0; } } - + /** * Makes clean instance of builder. * @@ -85,12 +85,11 @@ public function newQuery(): self /** * Insert in table data from files. * - * @param array $columns - * @param array $files + * @param array $columns + * @param array $files * @param string $format - * @param int $concurrency - * - * @param array $settings + * @param int $concurrency + * @param array $settings * * @return array */ @@ -99,27 +98,26 @@ public function insertFiles(array $columns, array $files, string $format = Forma foreach ($files as $i => $file) { $files[$i] = $this->prepareFile($file); } - + return $this->client->writeFiles($this->getFrom()->getTable(), $columns, $files, $format, $settings, $concurrency); } /** * Insert in table data from files. * - * @param array $columns + * @param array $columns * @param string|\Tinderbox\Clickhouse\Interfaces\FileInterface $file - * @param string $format - * - * @param array $settings + * @param string $format + * @param array $settings * * @return bool */ public function insertFile(array $columns, $file, string $format = Format::CSV, array $settings = []): bool { $file = $this->prepareFile($file); - + $result = $this->client->writeFiles($this->getFrom()->getTable(), $columns, [$file], $format, $settings); - + return $result[0][0]; } @@ -137,7 +135,7 @@ public function insert(array $values) if (empty($values)) { return false; } - + if (!is_array(reset($values))) { $values = [$values]; } /* @@ -154,10 +152,12 @@ public function insert(array $values) return $this->client->writeOne($this->grammar->compileInsert($this, $values)); } - + /** * Performs ALTER TABLE `table` DELETE query. * + * @throws \Tinderbox\ClickhouseBuilder\Exceptions\GrammarException + * * @return bool */ public function delete() @@ -166,9 +166,9 @@ public function delete() $this->grammar->compileDelete($this) ); } - + /** - * Executes query to create table + * Executes query to create table. * * @param $tableName * @param string $engine @@ -180,9 +180,9 @@ public function createTable($tableName, string $engine, array $structure) { return $this->client->writeOne($this->grammar->compileCreateTable($tableName, $engine, $structure)); } - + /** - * Executes query to create table if table does not exists + * Executes query to create table if table does not exists. * * @param $tableName * @param string $engine @@ -194,12 +194,12 @@ public function createTableIfNotExists($tableName, string $engine, array $struct { return $this->client->writeOne($this->grammar->compileCreateTable($tableName, $engine, $structure, true)); } - + public function dropTable($tableName) { return $this->client->writeOne($this->grammar->compileDropTable($tableName)); } - + public function dropTableIfExists($tableName) { return $this->client->writeOne($this->grammar->compileDropTable($tableName, true)); diff --git a/src/Query/Column.php b/src/Query/Column.php index 6ef210c..0c19abf 100644 --- a/src/Query/Column.php +++ b/src/Query/Column.php @@ -63,7 +63,7 @@ public function __construct(BaseBuilder $query) * * @return Column */ - public function name($columnName) : self + public function name($columnName): self { if ($columnName instanceof \Closure) { $columnName = tap(new static($this->query), $columnName); @@ -85,7 +85,7 @@ public function name($columnName) : self * * @return Column */ - public function as(string $alias) : self + public function as(string $alias): self { $this->alias = new Identifier($alias); @@ -99,7 +99,7 @@ public function as(string $alias) : self * * @return Column */ - public function alias(string $alias) : self + public function alias(string $alias): self { return $this->as($alias); } @@ -139,7 +139,7 @@ public function getColumnName() * * @return Identifier|null */ - public function getAlias() : ?Identifier + public function getAlias(): ?Identifier { return $this->alias; } @@ -149,7 +149,7 @@ public function getAlias() : ?Identifier * * @return array */ - public function getFunctions() : array + public function getFunctions(): array { return $this->functions; } @@ -159,7 +159,7 @@ public function getFunctions() : array * * @return Builder|null */ - public function getSubQuery() : ?Builder + public function getSubQuery(): ?Builder { return $this->subQuery; } @@ -252,7 +252,7 @@ public function multiple($value) * * @return Builder */ - public function subQuery() : Builder + public function subQuery(): Builder { return $this->subQuery = $this->query->newQuery(); } diff --git a/src/Query/From.php b/src/Query/From.php index 8b24bcb..0db6859 100644 --- a/src/Query/From.php +++ b/src/Query/From.php @@ -56,7 +56,7 @@ public function __construct(BaseBuilder $query) * * @return From */ - public function table($table) : self + public function table($table): self { if (is_string($table)) { $table = new Identifier($table); @@ -74,7 +74,7 @@ public function table($table) : self * * @return From */ - public function as(string $alias) : self + public function as(string $alias): self { $this->alias = new Identifier($alias); @@ -90,7 +90,7 @@ public function as(string $alias) : self * * @return From */ - public function final(bool $isFinal = true) : self + public function final(bool $isFinal = true): self { $this->final = $isFinal; @@ -108,7 +108,7 @@ public function final(bool $isFinal = true) : self * * @return From */ - public function remote(string $expression, string $database, string $table, string $user = null, string $password = null) : self + public function remote(string $expression, string $database, string $table, string $user = null, string $password = null): self { $remote = "remote('{$expression}', {$database}, {$table}"; @@ -134,7 +134,7 @@ public function remote(string $expression, string $database, string $table, stri * * @return From */ - public function merge(string $database, string $regexp) : self + public function merge(string $database, string $regexp): self { return $this->table(new Expression("merge({$database}, '{$regexp}')")); } @@ -172,7 +172,7 @@ public function query($query = null) * * @return BaseBuilder */ - public function subQuery() : BaseBuilder + public function subQuery(): BaseBuilder { return $this->subQuery = $this->query->newQuery(); } @@ -192,7 +192,7 @@ public function getTable() * * @return Identifier */ - public function getAlias() : ?Identifier + public function getAlias(): ?Identifier { return $this->alias; } @@ -202,7 +202,7 @@ public function getAlias() : ?Identifier * * @return bool */ - public function getFinal() : ?bool + public function getFinal(): ?bool { return $this->final; } @@ -212,7 +212,7 @@ public function getFinal() : ?bool * * @return null|BaseBuilder */ - public function getSubQuery() : ?BaseBuilder + public function getSubQuery(): ?BaseBuilder { return $this->subQuery; } diff --git a/src/Query/Grammar.php b/src/Query/Grammar.php index 6d575af..38242b9 100644 --- a/src/Query/Grammar.php +++ b/src/Query/Grammar.php @@ -23,22 +23,22 @@ class Grammar { - use ColumnsComponentCompiler, - FromComponentCompiler, - ArrayJoinComponentCompiler, - JoinComponentCompiler, - TwoElementsLogicExpressionsCompiler, - WheresComponentCompiler, - PreWheresComponentCompiler, - HavingsComponentCompiler, - SampleComponentCompiler, - GroupsComponentCompiler, - OrdersComponentCompiler, - LimitComponentCompiler, - LimitByComponentCompiler, - UnionsComponentCompiler, - FormatComponentCompiler, - TupleCompiler; + use ColumnsComponentCompiler; + use FromComponentCompiler; + use ArrayJoinComponentCompiler; + use JoinComponentCompiler; + use TwoElementsLogicExpressionsCompiler; + use WheresComponentCompiler; + use PreWheresComponentCompiler; + use HavingsComponentCompiler; + use SampleComponentCompiler; + use GroupsComponentCompiler; + use OrdersComponentCompiler; + use LimitComponentCompiler; + use LimitByComponentCompiler; + use UnionsComponentCompiler; + use FormatComponentCompiler; + use TupleCompiler; protected $selectComponents = [ 'columns', @@ -73,15 +73,15 @@ public function compileSelect(BaseBuilder $query) $sql = []; foreach ($this->selectComponents as $component) { - $compileMethod = 'compile' . ucfirst($component) . 'Component'; - $component = 'get' . ucfirst($component); + $compileMethod = 'compile'.ucfirst($component).'Component'; + $component = 'get'.ucfirst($component); - if (! is_null($query->$component()) && ! empty($query->$component())) { + if (!is_null($query->$component()) && !empty($query->$component())) { $sql[$component] = $this->$compileMethod($query, $query->$component()); } } - return trim('SELECT ' . trim(implode(' ', $sql))); + return trim('SELECT '.trim(implode(' ', $sql))); } /** @@ -94,7 +94,7 @@ public function compileSelect(BaseBuilder $query) * * @return string */ - public function compileInsert(BaseBuilder $query, $values) : string + public function compileInsert(BaseBuilder $query, $values): string { $result = []; @@ -109,14 +109,14 @@ public function compileInsert(BaseBuilder $query, $values) : string if (is_null($table)) { throw GrammarException::missedTableForInsert(); } - + $format = $query->getFormat() ?? Format::VALUES; - + if ($format == Format::VALUES) { $columns = array_map(function ($col) { return is_string($col) ? new Identifier($col) : null; }, array_keys($values[0])); - + $columns = array_filter($columns); } @@ -127,18 +127,18 @@ public function compileInsert(BaseBuilder $query, $values) : string if ($columns !== '') { $result[] = "({$columns})"; } - - $result[] = 'FORMAT ' . $format; - + + $result[] = 'FORMAT '.$format; + if ($format == Format::VALUES) { $result[] = $this->compileInsertValues($values); } - + return implode(' ', $result); } - + /** - * Compiles create table query + * Compiles create table query. * * @param $tableName * @param string $engine @@ -147,56 +147,56 @@ public function compileInsert(BaseBuilder $query, $values) : string * * @return string */ - public function compileCreateTable($tableName, string $engine, array $structure, $ifNotExists = false) : string + public function compileCreateTable($tableName, string $engine, array $structure, $ifNotExists = false): string { if ($tableName instanceof Identifier) { - $tableName = (string)$tableName; + $tableName = (string) $tableName; } - - return "CREATE TABLE ".($ifNotExists ? "IF NOT EXISTS " : "")."{$tableName} ({$this->compileTableStructure($structure)}) ENGINE = {$engine}"; + + return 'CREATE TABLE '.($ifNotExists ? 'IF NOT EXISTS ' : '')."{$tableName} ({$this->compileTableStructure($structure)}) ENGINE = {$engine}"; } - + /** - * Compiles drop table query + * Compiles drop table query. * * @param $tableName * @param bool $ifExists * * @return string */ - public function compileDropTable($tableName, $ifExists = false) : string + public function compileDropTable($tableName, $ifExists = false): string { if ($tableName instanceof Identifier) { - $tableName = (string)$tableName; + $tableName = (string) $tableName; } - - return "DROP TABLE ".($ifExists ? "IF EXISTS " : "")."{$tableName}"; + + return 'DROP TABLE '.($ifExists ? 'IF EXISTS ' : '')."{$tableName}"; } - + /** - * Assembles table structure + * Assembles table structure. * * @param array $structure * * @return string */ - public function compileTableStructure(array $structure) : string + public function compileTableStructure(array $structure): string { $result = []; - + foreach ($structure as $column => $type) { $result[] = $column.' '.$type; } - + return implode(', ', $result); } - + public function compileInsertValues($values) { return implode(', ', array_map(function ($value) { - return '(' . implode(', ', array_map(function ($value) { - return $this->wrap($value); - }, $value)) . ')'; + return '('.implode(', ', array_map(function ($value) { + return $this->wrap($value); + }, $value)).')'; }, $values)); } @@ -205,8 +205,9 @@ public function compileInsertValues($values) * * @param BaseBuilder $query * - * @return string * @throws GrammarException + * + * @return string */ public function compileDelete(BaseBuilder $query) { @@ -214,13 +215,13 @@ public function compileDelete(BaseBuilder $query) $sql = "ALTER TABLE {$this->wrap($query->getFrom()->getTable())}"; - if (! is_null($query->getOnCluster())) { + if (!is_null($query->getOnCluster())) { $sql .= " ON CLUSTER {$query->getOnCluster()}"; } $sql .= ' DELETE'; - if (! is_null($query->getWheres()) && ! empty($query->getWheres())) { + if (!is_null($query->getWheres()) && !empty($query->getWheres())) { $sql .= " {$this->compileWheresComponent($query, $query->getWheres())}"; } else { throw GrammarException::missedWhereForDelete(); @@ -244,9 +245,10 @@ public function wrap($value) return array_map([$this, 'wrap'], $value); } elseif (is_string($value)) { $value = addslashes($value); + return "'{$value}'"; } elseif ($value instanceof Identifier) { - $value = (string)$value; + $value = (string) $value; if (strpos(strtolower($value), '.') !== false) { return implode('.', array_map(function ($element) { @@ -269,7 +271,7 @@ public function wrap($value) return $value; } - return '`' . str_replace('`', '``', $value) . '`'; + return '`'.str_replace('`', '``', $value).'`'; } elseif (is_numeric($value)) { return $value; } else { diff --git a/src/Query/JoinClause.php b/src/Query/JoinClause.php index 2bb003b..11488d2 100644 --- a/src/Query/JoinClause.php +++ b/src/Query/JoinClause.php @@ -85,6 +85,12 @@ public function __construct(BaseBuilder $query) public function table($table): self { if (is_string($table)) { + list($table, $alias) = $this->decomposeJoinExpressionToTableAndAlias($table); + + if (!is_null($alias)) { + $this->as($alias); + } + $table = new Identifier($table); } elseif ($table instanceof BaseBuilder) { $table = new Expression("({$table->toSql()})"); @@ -361,4 +367,20 @@ function ($element) { $array ); } + + /** + * Tries to decompose string join expression to table name and alias. + * + * @param string $table + * + * @return array + */ + private function decomposeJoinExpressionToTableAndAlias(string $table): array + { + if (strpos(strtolower($table), ' as ') !== false) { + return array_map('trim', preg_split('/\s+as\s+/i', $table)); + } + + return [$table, null]; + } } diff --git a/src/Query/Limit.php b/src/Query/Limit.php index 5918f8c..ed6063e 100644 --- a/src/Query/Limit.php +++ b/src/Query/Limit.php @@ -44,7 +44,7 @@ public function __construct(int $limit, int $offset = null, array $by = []) * * @return int */ - public function getLimit() : ?int + public function getLimit(): ?int { return $this->limit; } @@ -54,7 +54,7 @@ public function getLimit() : ?int * * @return int */ - public function getOffset() : ?int + public function getOffset(): ?int { return $this->offset; } @@ -64,7 +64,7 @@ public function getOffset() : ?int * * @return array */ - public function getBy() : array + public function getBy(): array { return $this->by; } diff --git a/src/Query/Traits/ArrayJoinComponentCompiler.php b/src/Query/Traits/ArrayJoinComponentCompiler.php index 9653464..750cc4e 100644 --- a/src/Query/Traits/ArrayJoinComponentCompiler.php +++ b/src/Query/Traits/ArrayJoinComponentCompiler.php @@ -15,7 +15,7 @@ trait ArrayJoinComponentCompiler * * @return string */ - protected function compileArrayJoinComponent(Builder $query, ArrayJoinClause $join) : string + protected function compileArrayJoinComponent(Builder $query, ArrayJoinClause $join): string { $result = []; $result[] = 'ARRAY JOIN'; diff --git a/src/Query/Traits/ColumnCompiler.php b/src/Query/Traits/ColumnCompiler.php index 944f506..f7e9354 100644 --- a/src/Query/Traits/ColumnCompiler.php +++ b/src/Query/Traits/ColumnCompiler.php @@ -14,7 +14,7 @@ trait ColumnCompiler * * @return string */ - public function compileColumn(Column $column) : string + public function compileColumn(Column $column): string { $result = ''; diff --git a/src/Query/Traits/ColumnsComponentCompiler.php b/src/Query/Traits/ColumnsComponentCompiler.php index aebcc96..726682f 100644 --- a/src/Query/Traits/ColumnsComponentCompiler.php +++ b/src/Query/Traits/ColumnsComponentCompiler.php @@ -17,10 +17,10 @@ trait ColumnsComponentCompiler * * @return string */ - private function compileColumnsComponent(BaseBuilder $builder, array $columns) : string + private function compileColumnsComponent(BaseBuilder $builder, array $columns): string { $compiledColumns = []; - + foreach ($columns as $column) { $compiledColumns[] = $this->compileColumn($column); } diff --git a/src/Query/Traits/FormatComponentCompiler.php b/src/Query/Traits/FormatComponentCompiler.php index 859f4b0..c6378a3 100644 --- a/src/Query/Traits/FormatComponentCompiler.php +++ b/src/Query/Traits/FormatComponentCompiler.php @@ -14,7 +14,7 @@ trait FormatComponentCompiler * * @return string */ - public function compileFormatComponent(Builder $builder, $format) : string + public function compileFormatComponent(Builder $builder, $format): string { return "FORMAT {$format}"; } diff --git a/src/Query/Traits/FromComponentCompiler.php b/src/Query/Traits/FromComponentCompiler.php index e83700c..c81d3b2 100644 --- a/src/Query/Traits/FromComponentCompiler.php +++ b/src/Query/Traits/FromComponentCompiler.php @@ -16,7 +16,7 @@ trait FromComponentCompiler * * @return string */ - public function compileFromComponent(BaseBuilder $builder, From $from) : string + public function compileFromComponent(BaseBuilder $builder, From $from): string { $this->verifyFrom($from); diff --git a/src/Query/Traits/GroupsComponentCompiler.php b/src/Query/Traits/GroupsComponentCompiler.php index 3a5532b..ce315b3 100644 --- a/src/Query/Traits/GroupsComponentCompiler.php +++ b/src/Query/Traits/GroupsComponentCompiler.php @@ -15,14 +15,14 @@ trait GroupsComponentCompiler * * @return string */ - private function compileGroupsComponent(Builder $builder, array $columns) : string + private function compileGroupsComponent(Builder $builder, array $columns): string { $compiledColumns = []; - + foreach ($columns as $column) { $compiledColumns[] = $this->compileColumn($column); } - + if (!empty($compiledColumns) && !in_array('*', $compiledColumns, true)) { return 'GROUP BY '.implode(', ', $compiledColumns); } else { diff --git a/src/Query/Traits/HavingsComponentCompiler.php b/src/Query/Traits/HavingsComponentCompiler.php index 3abc2af..39bd92c 100644 --- a/src/Query/Traits/HavingsComponentCompiler.php +++ b/src/Query/Traits/HavingsComponentCompiler.php @@ -15,7 +15,7 @@ trait HavingsComponentCompiler * * @return string */ - public function compileHavingsComponent(Builder $builder, array $havings) : string + public function compileHavingsComponent(Builder $builder, array $havings): string { $result = $this->compileTwoElementLogicExpressions($havings); diff --git a/src/Query/Traits/OrdersComponentCompiler.php b/src/Query/Traits/OrdersComponentCompiler.php index d5270a1..8e36bdf 100644 --- a/src/Query/Traits/OrdersComponentCompiler.php +++ b/src/Query/Traits/OrdersComponentCompiler.php @@ -14,7 +14,7 @@ trait OrdersComponentCompiler * * @return string */ - public function compileOrdersComponent(Builder $builder, array $orders) : string + public function compileOrdersComponent(Builder $builder, array $orders): string { $columns = []; diff --git a/src/Query/Traits/PreWheresComponentCompiler.php b/src/Query/Traits/PreWheresComponentCompiler.php index 8177e89..2d79612 100644 --- a/src/Query/Traits/PreWheresComponentCompiler.php +++ b/src/Query/Traits/PreWheresComponentCompiler.php @@ -15,7 +15,7 @@ trait PreWheresComponentCompiler * * @return string */ - public function compilePrewheresComponent(Builder $builder, array $preWheres) : string + public function compilePrewheresComponent(Builder $builder, array $preWheres): string { $result = $this->compileTwoElementLogicExpressions($preWheres); diff --git a/src/Query/Traits/SampleComponentCompiler.php b/src/Query/Traits/SampleComponentCompiler.php index d415eb6..625613f 100644 --- a/src/Query/Traits/SampleComponentCompiler.php +++ b/src/Query/Traits/SampleComponentCompiler.php @@ -14,7 +14,7 @@ trait SampleComponentCompiler * * @return string */ - public function compileSampleComponent(Builder $builder, float $sample = null) : string + public function compileSampleComponent(Builder $builder, float $sample = null): string { return "SAMPLE {$sample}"; } diff --git a/src/Query/Traits/TupleCompiler.php b/src/Query/Traits/TupleCompiler.php index d5d65db..c283a8a 100644 --- a/src/Query/Traits/TupleCompiler.php +++ b/src/Query/Traits/TupleCompiler.php @@ -13,7 +13,7 @@ trait TupleCompiler * * @return string */ - public function compileTuple(Tuple $tuple) : string + public function compileTuple(Tuple $tuple): string { return implode(', ', array_map([$this, 'wrap'], $tuple->getElements())); } diff --git a/src/Query/Traits/TwoElementsLogicExpressionsCompiler.php b/src/Query/Traits/TwoElementsLogicExpressionsCompiler.php index bc86c0c..c520531 100644 --- a/src/Query/Traits/TwoElementsLogicExpressionsCompiler.php +++ b/src/Query/Traits/TwoElementsLogicExpressionsCompiler.php @@ -18,7 +18,7 @@ trait TwoElementsLogicExpressionsCompiler * * @return string */ - private function compileTwoElementLogicExpressions(array $wheres) : string + private function compileTwoElementLogicExpressions(array $wheres): string { $result = []; diff --git a/src/Query/Traits/UnionsComponentCompiler.php b/src/Query/Traits/UnionsComponentCompiler.php index a907346..668dfd2 100644 --- a/src/Query/Traits/UnionsComponentCompiler.php +++ b/src/Query/Traits/UnionsComponentCompiler.php @@ -14,7 +14,7 @@ trait UnionsComponentCompiler * * @return string */ - public function compileUnionsComponent(Builder $builder, array $unions) : string + public function compileUnionsComponent(Builder $builder, array $unions): string { return 'UNION ALL '. implode(' UNION ALL ', array_map(function ($query) { diff --git a/src/Query/Traits/WheresComponentCompiler.php b/src/Query/Traits/WheresComponentCompiler.php index aa9b0d9..f0ed3bc 100644 --- a/src/Query/Traits/WheresComponentCompiler.php +++ b/src/Query/Traits/WheresComponentCompiler.php @@ -15,7 +15,7 @@ trait WheresComponentCompiler * * @return string */ - public function compileWheresComponent(Builder $builder, array $wheres) : string + public function compileWheresComponent(Builder $builder, array $wheres): string { $result = $this->compileTwoElementLogicExpressions($wheres); diff --git a/src/Query/Tuple.php b/src/Query/Tuple.php index 450f63a..241957d 100644 --- a/src/Query/Tuple.php +++ b/src/Query/Tuple.php @@ -29,7 +29,7 @@ public function __construct(array $elements = []) * * @return array */ - public function getElements() : array + public function getElements(): array { return $this->elements; } @@ -41,7 +41,7 @@ public function getElements() : array * * @return Tuple */ - public function addElements(...$elements) : self + public function addElements(...$elements): self { $this->elements = array_merge($this->elements, array_flatten($elements)); diff --git a/src/Query/TwoElementsLogicExpression.php b/src/Query/TwoElementsLogicExpression.php index 5ab3937..0966c8b 100644 --- a/src/Query/TwoElementsLogicExpression.php +++ b/src/Query/TwoElementsLogicExpression.php @@ -118,7 +118,7 @@ public function concatOperator(string $operator) * * @return TwoElementsLogicExpression */ - public function firstElementQuery($query) : self + public function firstElementQuery($query): self { if ($query instanceof \Closure) { $query = tap($this->query->newQuery(), $query); @@ -138,7 +138,7 @@ public function firstElementQuery($query) : self * * @return TwoElementsLogicExpression */ - public function secondElementQuery($query) : self + public function secondElementQuery($query): self { if ($query instanceof \Closure) { $query = tap($this->query->newQuery(), $query); @@ -166,7 +166,7 @@ public function getFirstElement() * * @return mixed */ - public function getOperator() : ?Operator + public function getOperator(): ?Operator { return $this->operator; } @@ -186,7 +186,7 @@ public function getSecondElement() * * @return mixed */ - public function getConcatenationOperator() : Operator + public function getConcatenationOperator(): Operator { return $this->concatenationOperator; } diff --git a/src/functions.php b/src/functions.php index 87dcb13..3bc72e7 100644 --- a/src/functions.php +++ b/src/functions.php @@ -1,7 +1,7 @@ newQuery()->dropTableIfExists($tableName); $builder->newQuery()->createTableIfNotExists($tableName, 'Memory', $structure); @@ -105,7 +106,7 @@ function into_memory_table($builder, $structure = null): bool } } -if (! function_exists('file_from')) { +if (!function_exists('file_from')) { function file_from($file): \Tinderbox\Clickhouse\Interfaces\FileInterface { if (is_string($file) && is_file($file)) { diff --git a/tests/BuilderTest.php b/tests/BuilderTest.php index 7092306..c005b69 100644 --- a/tests/BuilderTest.php +++ b/tests/BuilderTest.php @@ -24,7 +24,7 @@ class BuilderTest extends TestCase { use MockeryPHPUnitIntegration; - public function getBuilder() : Builder + public function getBuilder(): Builder { return new Builder(m::mock(Client::class)); } @@ -99,33 +99,33 @@ public function test_select_column_closure() { $builder = $this->getBuilder(); - $builder->select(['column' => function ($column) use ($builder) { + $builder->select(['column' => function ($column) { $this->assertInstanceOf(Column::class, $column); $column->name('myColumn')->as('myAlias'); }]); $this->assertEquals('SELECT `myColumn` AS `myAlias`', $builder->toSql()); - $builder->select(['column' => function ($column) use ($builder) { + $builder->select(['column' => function ($column) { $this->assertInstanceOf(Column::class, $column); }]); $this->assertEquals('SELECT `column`', $builder->toSql()); - $builder->select(function ($column) use ($builder) { + $builder->select(function ($column) { $this->assertInstanceOf(Column::class, $column); }); $this->assertEquals('SELECT', $builder->toSql()); - $builder->select(function ($column) use ($builder) { + $builder->select(function ($column) { $this->assertInstanceOf(Column::class, $column); $column->name('myColumn')->as('myAlias'); }); $this->assertEquals('SELECT `myColumn` AS `myAlias`', $builder->toSql()); - $builder->select([function ($column) use ($builder) { + $builder->select([function ($column) { $this->assertInstanceOf(Column::class, $column); $column->name('myColumn')->as('myAlias'); }]); @@ -330,6 +330,15 @@ public function test_join_simple() $builder->from('table')->join('table2', 'any', 'left', ['column'], 'global'); $this->assertEquals('SELECT * FROM `table` GLOBAL ANY LEFT JOIN `table2` USING `column`', $builder->toSql()); + $builder->from('table')->join('table2', 'any', 'left', ['column'], 'global', 'table3'); + $this->assertEquals('SELECT * FROM `table` GLOBAL ANY LEFT JOIN `table2` AS `table3` USING `column`', $builder->toSql()); + + $builder->from('table')->join('table2 as table3', 'any', 'left', ['column'], 'global', 'table4'); + $this->assertEquals('SELECT * FROM `table` GLOBAL ANY LEFT JOIN `table2` AS `table3` USING `column`', $builder->toSql()); + + $builder->from('table')->join('table2 as table3 as table', 'any', 'left', ['column'], 'global', 'table4'); + $this->assertEquals('SELECT * FROM `table` GLOBAL ANY LEFT JOIN `table2` AS `table3` USING `column`', $builder->toSql()); + $builder->from('table')->leftJoin('table2', 'all', ['column']); $this->assertEquals('SELECT * FROM `table` ALL LEFT JOIN `table2` USING `column`', $builder->toSql()); @@ -615,6 +624,12 @@ public function test_where_ins() $builder->addFile(new TempTable('_numbers', '', ['number' => 'UInt64']))->select('column')->from('table')->whereGlobalIn('column', '_numbers'); $this->assertEquals('SELECT `column` FROM `table` WHERE `column` GLOBAL IN `_numbers`', $builder->toSql()); + + $builder = $this->getBuilder()->from('table')->whereIn('column', []); + $this->assertEquals('SELECT * FROM `table` WHERE 0 = 1', $builder->toSql()); + + $builder = $this->getBuilder()->from('table')->whereNotIn('column', []); + $this->assertEquals('SELECT * FROM `table`', $builder->toSql()); } public function test_where_between() @@ -809,54 +824,54 @@ public function test_readOne_and_read() { $server = new Server('127.0.0.1'); $client = new Client((new ServerProvider())->addServer($server)); - + $client->write([ ['query' => 'drop table if exists default.builder_test'], ['query' => 'create table if not exists default.builder_test (number UInt64) engine = Memory'], ], 1); - + $builder = new Builder($client); $result = $builder ->table('system.tables') ->where('database', '=', 'default') - ->where('name', '=','builder_test')->get(); - + ->where('name', '=', 'builder_test')->get(); + $this->assertEquals(1, count($result->rows), 'Correctly returns result of query'); - + $client->write([ ['query' => 'drop table if exists default.builder_test'], ['query' => 'drop table if exists default.builder_test2'], ['query' => 'create table if not exists default.builder_test (number UInt64) engine = Memory'], ['query' => 'create table if not exists default.builder_test2 (number UInt64) engine = Memory'], ], 1); - + $builder = new Builder($client); - + $result = $builder ->table('system.tables') ->where('database', '=', 'default') - ->where('name', '=','builder_test') - ->asyncWithQuery(function($builder) { + ->where('name', '=', 'builder_test') + ->asyncWithQuery(function ($builder) { $builder ->table('system.tables') ->where('database', '=', 'default') - ->where('name', '=','builder_test2'); + ->where('name', '=', 'builder_test2'); })->get(); - + $this->assertTrue(count($result[0]->rows) && count($result[0]->rows), 'Correctly returns result of query'); - + $client->write([ ['query' => 'drop table if exists default.builder_test'], ], 1); - + $query = $builder->newQuery()->from(raw('numbers(0,10)')); $query->asyncWithQuery()->table(raw('numbers(10,10)')); - + $result = $query->get(); - + $this->assertEquals(2, count($result)); - $this->assertEquals(['0','1','2','3','4','5','6','7','8','9'], array_column($result[0]->rows, 'number')); - $this->assertEquals(['10','11','12','13','14','15','16','17','18','19'], array_column($result[1]->rows, 'number')); + $this->assertEquals(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], array_column($result[0]->rows, 'number')); + $this->assertEquals(['10', '11', '12', '13', '14', '15', '16', '17', '18', '19'], array_column($result[1]->rows, 'number')); $this->expectException(\InvalidArgumentException::class); @@ -867,12 +882,12 @@ public function test_insert() { $server = new Server('127.0.0.1'); $client = new Client((new ServerProvider())->addServer($server)); - + $client->write([ ['query' => 'drop table if exists default.builder_test'], ['query' => 'create table if not exists default.builder_test (number UInt64, string String) engine = Memory'], ], 1); - + $builder = new Builder($client); $builder->table('builder_test')->insert([[ @@ -882,60 +897,60 @@ public function test_insert() 'number' => 2, 'string' => 'value2', ]]); - + $result = $builder->table('builder_test')->orderBy('number')->get(); - + $this->assertTrue($result->rows[0]['number'] == 1 && $result->rows[1]['number'] == 2, 'Correctly inserts data into table with values format and specified columns'); - + $client->write([ ['query' => 'drop table if exists default.builder_test'], ['query' => 'create table if not exists default.builder_test (number UInt64, string String) engine = Memory'], ], 1); - + $builder = new Builder($client); - + $builder->table('builder_test')->insert([ - [1, 'value1'], [2, 'value2'] + [1, 'value1'], [2, 'value2'], ]); - + $result = $builder->table('builder_test')->orderBy('number')->get(); - + $this->assertTrue($result->rows[0]['number'] == 1 && $result->rows[1]['number'] == 2, 'Correctly inserts data into table with values format without columns'); - + $client->write([ ['query' => 'drop table if exists default.builder_test'], ['query' => 'create table if not exists default.builder_test (number UInt64, string String) engine = Memory'], ], 1); - + $builder = new Builder($client); - + $builder->table('builder_test')->insert([1, 'value1']); - + $result = $builder->table('builder_test')->orderBy('number')->get(); - + $this->assertTrue($result->rows[0]['number'] == 1, 'Correctly inserts data into table with values format and one row'); - + $client->write([ ['query' => 'drop table if exists default.builder_test'], ['query' => 'create table if not exists default.builder_test (number UInt64, string String) engine = Memory'], ], 1); - + $builder = new Builder($client); - + $builder->table('builder_test')->insert(['number' => 1, 'string' => 'value1']); - + $result = $builder->table('builder_test')->orderBy('number')->get(); - + $this->assertTrue($result->rows[0]['number'] == 1, 'Correctly inserts data into table with values format and one row with columns'); $this->assertFalse($builder->table('table')->insert([]), 'Fails to insert empty dataset'); } - - protected function putInTempFile(string $content) : string + + protected function putInTempFile(string $content): string { $fileName = tempnam(sys_get_temp_dir(), 'builder_'); file_put_contents($fileName, $content); - + return $fileName; } @@ -943,36 +958,36 @@ public function test_insert_files() { $server = new Server('127.0.0.1'); $client = new Client((new ServerProvider())->addServer($server)); - + $client->write([ ['query' => 'drop table if exists default.builder_test'], ['query' => 'create table if not exists default.builder_test (number UInt64, string String) engine = Memory'], ], 1); - + $realFiles = [ $this->putInTempFile('5'.PHP_EOL.'6'.PHP_EOL), $this->putInTempFile('7'.PHP_EOL.'8'.PHP_EOL), $this->putInTempFile('9'.PHP_EOL.'10'.PHP_EOL), ]; - + $files = [ '1'.PHP_EOL.'2'.PHP_EOL, new FileFromString('3'.PHP_EOL.'4'.PHP_EOL), new File($realFiles[0]), new TempTable('test', new File($realFiles[1]), ['number' => 'UInt64']), - $realFiles[2] + $realFiles[2], ]; - + $builder = new Builder($client); $builder->table('builder_test')->insertFiles(['number'], $files, Format::TSV, 5); - + $builder = new Builder($client); $result = $builder->table('builder_test')->orderBy('number')->get(); - + $this->assertEquals(10, count($result->rows), 'Correctly inserts all types of files'); - + $this->expectException(\TypeError::class); - + $builder = new Builder($client); $builder->table('builder_test')->insertFiles(['number'], [new \Exception('test')], Format::TSV, 5); } @@ -1001,126 +1016,126 @@ public function testCompileAsyncQueries() $builder3, ], $sqls); } - + protected function createBuilder() { $serverProvider = new ServerProvider(); $serverProvider->addServer(new Server('localhost', 8123, 'default')); $client = new Client($serverProvider); - + return new Builder($client); } - + public function testDelete() { $builder = $this->createBuilder(); $builder->dropTableIfExists('test'); $builder->createTable('test', 'MergeTree order by number', [ - 'number' => 'UInt64' + 'number' => 'UInt64', ]); - - $builder->newQuery()->table('test')->insertFile(['number'], new FileFromString('0'.PHP_EOL.'1'.PHP_EOL.'2')); - + + $builder->newQuery()->table('test')->insertFile(['number'], new FileFromString('0'.PHP_EOL.'1'.PHP_EOL.'2')); + /* * We have to sleep for 3 seconds while mutation in progress */ sleep(3); - + $builder->newQuery()->table('test')->where('number', '=', 1)->delete(); - + $result = $builder->newQuery()->table('test')->count(); - + $this->assertEquals(2, $result); } - + public function testDropTable() { $builder = $this->createBuilder(); $builder->dropTableIfExists('test'); $builder->createTable('test', 'MergeTree order by number', [ - 'number' => 'UInt64' + 'number' => 'UInt64', ]); $builder->dropTable('test'); - + $result = $builder->newQuery()->table('system.tables')->where('name', '=', 'test')->count(); - + $this->assertEquals(0, $result); } - + public function testCount() { $result = $this->createBuilder()->table(raw('numbers(0,10)'))->count(); - + $this->assertEquals(10, $result); - + $result = $this->createBuilder()->newQuery()->table(raw('numbers(0,10)'))->groupBy(raw('number % 2'))->count(); - + $this->assertEquals(2, $result); } - + public function testOnCluster() { $builder = $this->getBuilder(); $builder->onCluster('test'); - + $this->assertEquals('test', $builder->getOnCluster(), 'Can execute query on cluster'); } - + public function testArrayJoin() { $builder = $this->getBuilder(); $builder->table('test')->arrayJoin('someArr'); - + $this->assertEquals('SELECT * FROM `test` ARRAY JOIN `someArr`', $builder->toSql()); } - + public function testAddFile() { $builder = $this->getBuilder(); $builder->addFile(new TempTable('_numbers', '', ['number' => 'UInt64'])); $builder->addFile(new TempTable('_numbers2', '', ['number' => 'UInt64'])); - + $this->assertEquals(2, count($builder->getFiles())); $this->assertArrayHasKey('_numbers', $builder->getFiles()); $this->assertArrayHasKey('_numbers2', $builder->getFiles()); } - + public function testToAsyncSqlsAndQueries() { $builder = $this->createBuilder(); $builder->table('system.tables') ->where('database', '=', 'default') - ->where('name', '=','builder_test1'); - - $builder->asyncWithQuery(function($builder) { + ->where('name', '=', 'builder_test1'); + + $builder->asyncWithQuery(function ($builder) { $builder ->table('system.tables') ->where('database', '=', 'default') - ->where('name', '=','builder_test2'); + ->where('name', '=', 'builder_test2'); }); - - $builder->asyncWithQuery(function($builder) { + + $builder->asyncWithQuery(function ($builder) { $builder ->table('system.tables') ->where('database', '=', 'default') - ->where('name', '=','builder_test3'); + ->where('name', '=', 'builder_test3'); }); - + $sqls = $builder->toAsyncSqls(); $queries = $builder->toAsyncQueries(); - + $this->assertEquals(3, count($sqls)); $this->assertEquals(3, count($queries)); - + $sqls = array_column($sqls, 'query'); - $queries = array_map(function(Query $query) { + $queries = array_map(function (Query $query) { return $query->getQuery(); }, $queries); - + $this->assertContains('SELECT * FROM `system`.`tables` WHERE `database` = \'default\' AND `name` = \'builder_test1\'', $sqls); $this->assertContains('SELECT * FROM `system`.`tables` WHERE `database` = \'default\' AND `name` = \'builder_test2\'', $sqls); $this->assertContains('SELECT * FROM `system`.`tables` WHERE `database` = \'default\' AND `name` = \'builder_test3\'', $sqls); - + $this->assertContains('SELECT * FROM `system`.`tables` WHERE `database` = \'default\' AND `name` = \'builder_test1\'', $queries); $this->assertContains('SELECT * FROM `system`.`tables` WHERE `database` = \'default\' AND `name` = \'builder_test2\'', $queries); $this->assertContains('SELECT * FROM `system`.`tables` WHERE `database` = \'default\' AND `name` = \'builder_test3\'', $queries); diff --git a/tests/ExceptionsTest.php b/tests/ExceptionsTest.php index 7cae664..9e2c3cb 100644 --- a/tests/ExceptionsTest.php +++ b/tests/ExceptionsTest.php @@ -17,7 +17,7 @@ class ExceptionsTest extends TestCase { use MockeryPHPUnitIntegration; - public function getBuilder() : Builder + public function getBuilder(): Builder { return new Builder(m::mock(Client::class)); } diff --git a/tests/FromTest.php b/tests/FromTest.php index cec6ad6..3ff6e3f 100644 --- a/tests/FromTest.php +++ b/tests/FromTest.php @@ -14,7 +14,7 @@ class FromTest extends TestCase { use MockeryPHPUnitIntegration; - public function getBuilder() : Builder + public function getBuilder(): Builder { return new Builder(m::mock(Client::class)); } diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 4dae1db..284e26b 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -65,7 +65,7 @@ public function testRaw() $this->assertInstanceOf(Expression::class, $expression); } - protected function putInTempFile(string $content) : string + protected function putInTempFile(string $content): string { $fileName = tempnam(sys_get_temp_dir(), 'builder_'); file_put_contents($fileName, $content); @@ -88,7 +88,7 @@ public function testInsertIntoMemory() '1'.PHP_EOL.'2'.PHP_EOL, new FileFromString('3'.PHP_EOL.'4'.PHP_EOL), new File($realFiles[0]), - $realFiles[2] + $realFiles[2], ]; $client->write([ @@ -141,7 +141,7 @@ public function testFileFrom() new FileFromString('3'.PHP_EOL.'4'.PHP_EOL), new File($realFiles[0]), new TempTable('test', new File($realFiles[1]), ['number' => 'UInt64']), - $realFiles[2] + $realFiles[2], ]; foreach ($files as $file) { diff --git a/tests/GrammarTest.php b/tests/GrammarTest.php index 3274e11..70f558e 100644 --- a/tests/GrammarTest.php +++ b/tests/GrammarTest.php @@ -265,6 +265,9 @@ public function testCompileSelect() $select = $grammar->compileSelect($this->getBuilder()->anyLeftJoin('table', ['column'], true)); $this->assertEquals('SELECT * GLOBAL ANY LEFT JOIN `table` USING `column`', $select); + $select = $grammar->compileSelect($this->getBuilder()->anyLeftJoin('table', ['column'], true, 'another_table')); + $this->assertEquals('SELECT * GLOBAL ANY LEFT JOIN `table` AS `another_table` USING `column`', $select); + $select = $grammar->compileSelect($this->getBuilder()->anyLeftJoin(function (JoinClause $join) { $join->table($this->getBuilder()->table('test')->select('column')); }, ['column'], true)); diff --git a/tests/LaravelIntegrationTest.php b/tests/LaravelIntegrationTest.php index 44d5697..11dfcfb 100644 --- a/tests/LaravelIntegrationTest.php +++ b/tests/LaravelIntegrationTest.php @@ -7,13 +7,20 @@ use Illuminate\Database\DatabaseServiceProvider; use Illuminate\Events\EventServiceProvider; use PHPUnit\Framework\TestCase; +use Tinderbox\Clickhouse\Client; use Tinderbox\Clickhouse\Common\FileFromString; +use Tinderbox\Clickhouse\Interfaces\TransportInterface; +use Tinderbox\Clickhouse\Query; +use Tinderbox\Clickhouse\Query\QueryStatistic; +use Tinderbox\Clickhouse\Query\Result; +use Tinderbox\Clickhouse\Server; +use Tinderbox\Clickhouse\ServerProvider; +use Tinderbox\Clickhouse\Transport\HttpTransport; use Tinderbox\ClickhouseBuilder\Exceptions\BuilderException; use Tinderbox\ClickhouseBuilder\Exceptions\NotSupportedException; use Tinderbox\ClickhouseBuilder\Integrations\Laravel\Builder; use Tinderbox\ClickhouseBuilder\Integrations\Laravel\ClickhouseServiceProvider; use Tinderbox\ClickhouseBuilder\Integrations\Laravel\Connection; -use Tinderbox\ClickhouseBuilder\Query\Enums\Format; use Tinderbox\ClickhouseBuilder\Query\Expression; class LaravelIntegrationTest extends TestCase @@ -32,8 +39,8 @@ public function getSimpleConfig() 'timeout' => 10, 'protocol' => 'http', ], - ] - ] + ], + ], ]; } @@ -55,9 +62,9 @@ public function getClusterConfig() 'database' => 'default', 'username' => 'default', 'password' => '', - 'options'=> [ - 'timeout'=> 10 - ] + 'options' => [ + 'timeout '=> 10, + ], ], 'server3' => [ 'host' => 'not_local_host', @@ -65,11 +72,67 @@ public function getClusterConfig() 'database' => 'default', 'username' => 'default', 'password' => '', - 'options'=> [ - 'timeout'=> 10 - ] + 'options' => [ + 'timeout' => 10, + ], ], - ] + ], + ], + ]; + } + + public function getSimpleConfigWithServerWithTag() + { + return [ + 'servers' => [ + [ + 'host' => 'with-tag', + 'port' => 8123, + 'database' => 'default', + 'username' => 'default', + 'password' => '', + 'options' => [ + 'tags' => [ + 'tag', + ], + ], + ], + [ + 'host' => 'without-tag', + 'port' => 8123, + 'database' => 'default', + 'username' => 'default', + 'password' => '', + ], + ], + ]; + } + + public function getClusterConfigWithServerWithTag() + { + return [ + 'clusters' => [ + 'test' => [ + [ + 'host' => 'with-tag', + 'port' => 8123, + 'database' => 'default', + 'username' => 'default', + 'password' => '', + 'options' => [ + 'tags' => [ + 'tag', + ], + ], + ], + [ + 'host' => 'without-tag', + 'port' => 8123, + 'database' => 'default', + 'username' => 'default', + 'password' => '', + ], + ], ], ]; } @@ -110,23 +173,53 @@ public function test_connection_construct() { $simpleConnection = new Connection($this->getSimpleConfig()); $clusterConnection = new Connection($this->getClusterConfig()); - + $clusterConnection->onCluster('test')->usingRandomServer(); - + $simpleClient = $simpleConnection->getClient(); $clusterClient = $clusterConnection->getClient(); - + $clusterServer = $clusterClient->getServer(); $secondClusterServer = $clusterClient->getServer(); - + while ($secondClusterServer === $clusterServer) { $secondClusterServer = $clusterClient->getServer(); } - + $this->assertNotSame($clusterServer, $secondClusterServer); $this->assertEquals($simpleClient->getServer(), $simpleClient->getServer()); } + public function test_connection_with_server_with_tags() + { + $simpleConnection = new Connection($this->getSimpleConfigWithServerWithTag()); + $clusterConnection = new Connection($this->getClusterConfigWithServerWithTag()); + + $simpleConnection->usingServerWithTag('tag'); + $clusterConnection->onCluster('test')->usingServerWithTag('tag'); + + $simpleServerWithTag = $simpleConnection->getClient()->getServer(); + $clusterServerWithTag = $clusterConnection->getClient()->getServer(); + + $simpleConnection->usingRandomServer(); + $clusterConnection->onCluster('test')->usingRandomServer(); + + $simpleServer = $simpleConnection->getClient()->getServer(); + $clusterServer = $clusterConnection->getClient()->getServer(); + + while ($simpleServer === $simpleServerWithTag) { + $simpleServer = $simpleConnection->getClient()->getServer(); + } + + while ($clusterServer === $simpleServerWithTag) { + $clusterServer = $clusterConnection->getClient()->getServer(); + } + + $this->assertTrue(true); + $this->assertEquals('with-tag', $simpleServerWithTag->getHost()); + $this->assertEquals('with-tag', $clusterServerWithTag->getHost()); + } + public function test_connection_get_config() { $connection = new Connection($this->getSimpleConfig()); @@ -168,7 +261,7 @@ public function test_connection_select() public function test_connection_select_one() { $connection = new Connection($this->getSimpleConfig()); - + $result = $connection->selectOne('select * from numbers(0, 10)'); $this->assertEquals(10, count($result)); } @@ -177,15 +270,15 @@ public function test_connection_statement() { $connection = new Connection($this->getSimpleConfig()); $connection->statement('drop table if exists test'); - + $result = $connection->select("select count() as count from system.tables where name = 'test'"); $this->assertEquals(0, $result[0]['count']); - + $connection->statement('create table test (test String) Engine = Memory'); - + $result = $connection->select("select count() as count from system.tables where name = 'test'"); $this->assertEquals(1, $result[0]['count']); - + $connection->statement('drop table if exists test'); $result = $connection->select("select count() as count from system.tables where name = 'test'"); $this->assertEquals(0, $result[0]['count']); @@ -195,15 +288,15 @@ public function test_connection_unprepared() { $connection = new Connection($this->getSimpleConfig()); $connection->unprepared('drop table if exists test'); - + $result = $connection->select('select count() as count from system.tables where name = \'test\''); $this->assertEquals(0, $result[0]['count']); - + $connection->statement('create table test (test String) Engine = Memory'); - + $result = $connection->select('select count() as count from system.tables where name = \'test\''); $this->assertEquals(1, $result[0]['count']); - + $connection->statement('drop table if exists test'); $result = $connection->select('select count() as count from system.tables where name = \'test\''); $this->assertEquals(0, $result[0]['count']); @@ -212,15 +305,15 @@ public function test_connection_unprepared() public function test_connection_select_async() { $connection = new Connection($this->getSimpleConfig()); - + $result = $connection->selectAsync([ ['query' => 'select * from numbers(0, 10)'], ['query' => 'select * from numbers(10, 10)'], ]); - + $this->assertEquals(2, count($result)); - $this->assertEquals(['0','1','2','3','4','5','6','7','8','9'], array_column($result[0], 'number')); - $this->assertEquals(['10','11','12','13','14','15','16','17','18','19'], array_column($result[1], 'number')); + $this->assertEquals(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], array_column($result[0], 'number')); + $this->assertEquals(['10', '11', '12', '13', '14', '15', '16', '17', '18', '19'], array_column($result[1], 'number')); } public function test_connection_insert() @@ -228,12 +321,12 @@ public function test_connection_insert() $connection = new Connection($this->getSimpleConfig()); $connection->statement('drop table if exists test'); $connection->statement('create table test (number UInt64) engine = Memory'); - + $result = $connection->insert('insert into test (number) values (?), (?), (?)', [0, 1, 2]); $this->assertTrue($result); - + $result = $connection->select('select * from test'); - + $this->assertEquals(3, count($result)); } @@ -242,21 +335,21 @@ public function test_connection_insert_files() $connection = new Connection($this->getSimpleConfig()); $connection->statement('drop table if exists test'); $connection->statement('create table test (number UInt64) engine = Memory'); - + $result = $connection->insertFiles('test', ['number'], [ - new FileFromString('0'.PHP_EOL.'1'.PHP_EOL.'2') + new FileFromString('0'.PHP_EOL.'1'.PHP_EOL.'2'), ]); $this->assertTrue($result[0][0]); - + $result = $connection->select('select * from test'); - + $this->assertEquals(3, count($result)); } - + /* * Not supported functions */ - + public function test_connection_begin_transaction() { $connection = new Connection($this->getSimpleConfig()); @@ -277,23 +370,23 @@ public function test_connection_commit() $this->expectException(NotSupportedException::class); $connection->commit(); } - + public function test_last_query_statistic() { $connection = new Connection($this->getSimpleConfig()); $connection->table($connection->raw('numbers(0,10)'))->select('number')->get(); - + $firstStatistic = $connection->getLastQueryStatistic(); - + $connection->table($connection->raw('numbers(0,10000)'))->select('number')->get(); - + $secondStatistic = $connection->getLastQueryStatistic(); - + $this->assertNotSame($firstStatistic, $secondStatistic); - + $this->expectException(BuilderException::class); $this->expectExceptionMessage('Run query before trying to get statistic'); - + $connection = new Connection($this->getSimpleConfig()); $connection->getLastQueryStatistic(); } @@ -306,15 +399,15 @@ public function test_connection_delete() */ $connection = new Connection($this->getSimpleConfig()); $connection->delete('drop table if exists test'); - + $result = $connection->select("select count() as count from system.tables where name = 'test'"); $this->assertEquals(0, $result[0]['count']); - + $connection->delete('create table test (test String) Engine = Memory'); - + $result = $connection->select("select count() as count from system.tables where name = 'test'"); $this->assertEquals(1, $result[0]['count']); - + $connection->delete('drop table if exists test'); $result = $connection->select("select count() as count from system.tables where name = 'test'"); $this->assertEquals(0, $result[0]['count']); @@ -352,30 +445,30 @@ public function test_connection_transaction() public function test_connection_using() { $connection = new Connection($this->getClusterConfig()); - + $connection->onCluster('test')->using('server-1')->statement('drop table if exists test1'); $connection->onCluster('test')->using('server2')->statement('drop table if exists test2'); - + $connection->onCluster('test')->using('server-1')->statement('create database if not exists cluster1'); $connection->onCluster('test')->using('server2')->statement('create database if not exists cluster2'); - + $connection->onCluster('test')->using('server-1')->statement('create table test1 (number UInt8) Engine = Memory'); $connection->onCluster('test')->using('server2')->statement('create table test2 (number UInt8) Engine = Memory'); - + $result = $connection->onCluster('test')->using('server-1')->insert('insert into test1 (number) values (?), (?), (?)', [0, 1, 2]); $this->assertTrue($result); - + $result = $connection->select('select * from test1'); - + $this->assertEquals(3, count($result)); - + $result = $connection->onCluster('test')->using('server2')->insert('insert into test2 (number) values (?), (?), (?), (?)', [0, 1, 2, 4]); $this->assertTrue($result); - + $result = $connection->select('select * from test2'); - + $this->assertEquals(4, count($result)); - + $connection->onCluster('test')->using('server-1')->statement('drop table if exists test1'); $connection->onCluster('test')->using('server2')->statement('drop table if exists test2'); } @@ -383,22 +476,22 @@ public function test_connection_using() public function test_builder_get() { $connection = new Connection($this->getSimpleConfig()); - + $result = $connection->table($connection->raw('numbers(0,10)'))->select('number')->get(); - + $this->assertEquals(10, count($result)); } public function test_builder_async_get() { $connection = new Connection($this->getSimpleConfig()); - $result = $connection->table($connection->raw('numbers(0,10)'))->select('number')->asyncWithQuery(function ($builder) use($connection) { + $result = $connection->table($connection->raw('numbers(0,10)'))->select('number')->asyncWithQuery(function ($builder) use ($connection) { $builder->table($connection->raw('numbers(10,10)'))->select('number'); })->get(); - + $this->assertEquals(2, count($result)); - $this->assertEquals(['0','1','2','3','4','5','6','7','8','9'], array_column($result[0], 'number')); - $this->assertEquals(['10','11','12','13','14','15','16','17','18','19'], array_column($result[1], 'number')); + $this->assertEquals(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], array_column($result[0], 'number')); + $this->assertEquals(['10', '11', '12', '13', '14', '15', '16', '17', '18', '19'], array_column($result[1], 'number')); } public function test_builder_insert_files() @@ -406,24 +499,24 @@ public function test_builder_insert_files() $connection = new Connection($this->getSimpleConfig()); $connection->statement('drop table if exists test'); $connection->statement('create table test (number UInt64) engine = Memory'); - + $result = $connection->table('test')->insertFiles(['number'], [ - new FileFromString('0'.PHP_EOL.'1'.PHP_EOL.'2') + new FileFromString('0'.PHP_EOL.'1'.PHP_EOL.'2'), ]); $this->assertTrue($result[0][0]); - + $result = $connection->table('test')->get(); - + $this->assertEquals(3, count($result)); - + $connection->statement('drop table if exists test'); $connection->statement('create table test (number UInt64) engine = Memory'); - + $result = $connection->table('test')->insertFile(['number'], new FileFromString('0'.PHP_EOL.'1'.PHP_EOL.'2')); $this->assertTrue($result); - + $result = $connection->table('test')->get(); - + $this->assertEquals(3, count($result)); } @@ -435,55 +528,127 @@ public function test_builder_insert() $connection->table('test')->insert(['number' => 1]); $connection->table('test')->insert([['number' => 2], ['number' => 3]]); - + $connection->table('test')->insert([4]); $connection->table('test')->insert([[5], [6]]); $result = $connection->table('test')->select('number')->get(); $this->assertEquals(6, count($result)); - + $this->assertFalse($connection->table('table')->insert([])); } - + public function test_builder_delete() { $connection = new Connection($this->getSimpleConfig()); $connection->statement('drop table if exists test'); $connection->statement('create table test (number UInt64) engine = MergeTree order by number'); - + $connection->table('test')->insertFiles(['number'], [ - new FileFromString('0'.PHP_EOL.'1'.PHP_EOL.'2') + new FileFromString('0'.PHP_EOL.'1'.PHP_EOL.'2'), ]); - + /* * We have to sleep for 3 seconds while mutation in progress */ sleep(3); - + $connection->table('test')->where('number', '=', 1)->delete(); - + $result = $connection->table('test')->select($connection->raw('count() as count'))->get(); - + $this->assertEquals(2, $result[0]['count']); } - + public function test_builder_count() { $connection = new Connection($this->getSimpleConfig()); $result = $connection->table($connection->raw('numbers(0,10)'))->count(); - + $this->assertEquals(10, $result); - + $result = $connection->table($connection->raw('numbers(0,10)'))->groupBy($connection->raw('number % 2'))->count(); - + $this->assertEquals(2, $result); } - + public function test_builder_first() { $connection = new Connection($this->getSimpleConfig()); $result = $connection->table($connection->raw('numbers(2,10)'))->first(); - + $this->assertEquals(2, $result['number']); } + + public function test_builder_connection() + { + $connection = new Connection($this->getSimpleConfig()); + $builder = $connection->table($connection->raw('numbers(2,10)')); + + $this->assertEquals($connection, $builder->getConnection()); + } + + public function test_builder_pagination() + { + $connection = new Connection($this->getSimpleConfig()); + $paginator1 = $connection->table($connection->raw('numbers(0,10)'))->paginate(1, 2); + + $this->assertEquals(2, $paginator1->count()); + $this->assertEquals(2, $paginator1->perPage()); + $this->assertEquals(1, $paginator1->currentPage()); + $this->assertEquals(5, $paginator1->lastPage()); + $this->assertEquals([['number' => 0], ['number' => 1]], $paginator1->values()->all()); + + $paginator2 = $connection->table($connection->raw('numbers(0,10)'))->paginate(3, 1); + + $this->assertEquals(1, $paginator2->count()); + $this->assertEquals(1, $paginator2->perPage()); + $this->assertEquals(3, $paginator2->currentPage()); + $this->assertEquals(10, $paginator2->lastPage()); + $this->assertEquals([['number' => 2]], $paginator2->values()->all()); + } + + public function test_connection_set_client() + { + $connection = new Connection($this->getSimpleConfig()); + + $server = new Server('127.0.0.2'); + $serverProvider = new ServerProvider(); + $serverProvider->addServer($server); + + $transport = new HttpTransport(new \GuzzleHttp\Client()); + + $client = new Client($serverProvider, $transport); + $connection->setClient($client); + + $this->assertEquals($client, $connection->getClient()); + } + + public function test_builder_last_query_statistics() + { + $connection = new Connection($this->getSimpleConfig()); + + $server = new Server('127.0.0.1'); + $serverProvider = new ServerProvider(); + $serverProvider->addServer($server); + + $transport = $this->createMock(TransportInterface::class); + $transport->method('read')->willReturn([ + new Result(new Query($server, ''), [0, 1], new QueryStatistic(10, 20, 30, 40)), + ]); + + $client = new Client($serverProvider, $transport); + + $connection->setClient($client); + + $builder = $connection->table('test'); + $builder->get(); + + $lastQueryStatistic = $builder->getLastQueryStatistics(); + + $this->assertEquals(10, $lastQueryStatistic->getRows()); + $this->assertEquals(20, $lastQueryStatistic->getBytes()); + $this->assertEquals(30, $lastQueryStatistic->getTime()); + $this->assertEquals(40, $lastQueryStatistic->getRowsBeforeLimitAtLeast()); + } } diff --git a/tests/TwoElementsLogicExpressionTest.php b/tests/TwoElementsLogicExpressionTest.php index ea73934..252eb41 100644 --- a/tests/TwoElementsLogicExpressionTest.php +++ b/tests/TwoElementsLogicExpressionTest.php @@ -14,7 +14,7 @@ class TwoElementsLogicExpressionTest extends TestCase { use MockeryPHPUnitIntegration; - public function getBuilder() : Builder + public function getBuilder(): Builder { return new Builder(m::mock(Client::class)); }