From 9bf1f6489c4495c2f98289e3e3a5384f32c05b36 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 5 Dec 2023 16:03:43 +0100 Subject: [PATCH 1/5] fixed tests --- composer.json | 4 +-- .../Connection.getInsertId().mysql.phpt | 3 --- .../ResultSet.normalizeRow.mysql.phpt | 11 ++++++-- .../ResultSet.normalizeRow.postgre.phpt | 9 +++++++ .../ResultSet.normalizeRow.sqlite.phpt | 9 +++++++ .../Table/bugs/ActiveRow.__isset().phpt | 2 +- tests/Database/Table/bugs/ZeroPrimaryKey.phpt | 2 -- tests/Database/Table/bugs/bug216.phpt | 27 +++---------------- tests/Database/Table/bugs/bug49.phpt | 3 +-- tests/Database/connect.inc.php | 10 +++---- 10 files changed, 39 insertions(+), 41 deletions(-) diff --git a/composer.json b/composer.json index 89fa3f29c..562d6ab75 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ "nette/utils": "^3.1" }, "require-dev": { - "nette/tester": "^2.0", + "nette/tester": "^2.3", "nette/di": "^v3.0", - "mockery/mockery": "^1.0.0", + "mockery/mockery": "^1.3.6", "tracy/tracy": "^2.4", "phpstan/phpstan-nette": "^0.12" }, diff --git a/tests/Database/Connection.getInsertId().mysql.phpt b/tests/Database/Connection.getInsertId().mysql.phpt index e80b17746..a95bdfb59 100644 --- a/tests/Database/Connection.getInsertId().mysql.phpt +++ b/tests/Database/Connection.getInsertId().mysql.phpt @@ -11,9 +11,6 @@ use Tester\Assert; require __DIR__ . '/connect.inc.php'; // create $connection -$connection->query('CREATE DATABASE IF NOT EXISTS nette_test'); -$connection->query('USE nette_test'); - $connection->query(' CREATE TEMPORARY TABLE noprimarykey ( diff --git a/tests/Database/ResultSet.normalizeRow.mysql.phpt b/tests/Database/ResultSet.normalizeRow.mysql.phpt index 910cd393a..0b57aa868 100644 --- a/tests/Database/ResultSet.normalizeRow.mysql.phpt +++ b/tests/Database/ResultSet.normalizeRow.mysql.phpt @@ -134,10 +134,17 @@ Assert::equal([ ], (array) $res->fetch()); -$connection->getPdo()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); +$res = $connection->query('SELECT SUM(`int`) AS int_sum, AVG(`int`) AS int_avg, SUM(`double`) AS float_sum, AVG(`double`) AS float_avg FROM types WHERE `int` = 1 GROUP BY `int`'); +Assert::equal([ + 'int_sum' => 1.0, + 'int_avg' => 1.0, + 'float_sum' => 1.1, + 'float_avg' => 1.1, +], (array) $res->fetch()); -$res = $connection->query('SELECT `int`, `decimal`, `decimal2`, `float`, `double` FROM types'); +$connection->getPdo()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); +$res = $connection->query('SELECT `int`, `decimal`, `decimal2`, `float`, `double` FROM types'); Assert::equal([ 'int' => 1, 'decimal' => 1.0, diff --git a/tests/Database/ResultSet.normalizeRow.postgre.phpt b/tests/Database/ResultSet.normalizeRow.postgre.phpt index 6e78caa53..1eb955d62 100644 --- a/tests/Database/ResultSet.normalizeRow.postgre.phpt +++ b/tests/Database/ResultSet.normalizeRow.postgre.phpt @@ -133,3 +133,12 @@ $res = $connection->query('SELECT "integer" AS a, "text" AS a FROM types'); Assert::same([ 'a' => 'a', ], (array) @$res->fetch()); + + +$res = $connection->query('SELECT SUM("integer") AS int_sum, AVG("integer") AS int_avg, SUM("double") AS float_sum, AVG("double") AS float_avg FROM types WHERE "integer" = 1 GROUP BY "integer"'); +Assert::equal([ + 'int_sum' => 1, + 'int_avg' => 1.0, + 'float_sum' => 1.11, + 'float_avg' => 1.11, +], (array) $res->fetch()); diff --git a/tests/Database/ResultSet.normalizeRow.sqlite.phpt b/tests/Database/ResultSet.normalizeRow.sqlite.phpt index 58a897812..4b5b7f4c6 100644 --- a/tests/Database/ResultSet.normalizeRow.sqlite.phpt +++ b/tests/Database/ResultSet.normalizeRow.sqlite.phpt @@ -113,3 +113,12 @@ $res = $connection->query('SELECT [int] AS a, [text] AS a FROM types'); Assert::same([ 'a' => 'a', ], (array) @$res->fetch()); + + +$res = $connection->query('SELECT SUM([int]) AS int_sum, AVG([int]) AS int_avg, SUM([double]) AS float_sum, AVG([double]) AS float_avg FROM types WHERE [int] = 1 GROUP BY [int]'); +Assert::equal([ + 'int_sum' => 1, + 'int_avg' => 1.0, + 'float_sum' => 1.1, + 'float_avg' => 1.1, +], (array) $res->fetch()); diff --git a/tests/Database/Table/bugs/ActiveRow.__isset().phpt b/tests/Database/Table/bugs/ActiveRow.__isset().phpt index 0f35d7199..f02307314 100644 --- a/tests/Database/Table/bugs/ActiveRow.__isset().phpt +++ b/tests/Database/Table/bugs/ActiveRow.__isset().phpt @@ -1,7 +1,7 @@ query('CREATE DATABASE IF NOT EXISTS nette_test'); -$context->query('USE nette_test'); $context->query(' CREATE TABLE ships ( diff --git a/tests/Database/Table/bugs/bug216.phpt b/tests/Database/Table/bugs/bug216.phpt index 8617eebc5..c56e6e6f4 100644 --- a/tests/Database/Table/bugs/bug216.phpt +++ b/tests/Database/Table/bugs/bug216.phpt @@ -2,37 +2,16 @@ /** * Test: bug #216 - * @dataProvider? ../databases.ini + * @dataProvider? ../../databases.ini */ declare(strict_types=1); use Tester\Assert; -require __DIR__ . '/../../../bootstrap.php'; +require __DIR__ . '/../../connect.inc.php'; -//Prepare connection -$options = Tester\Environment::loadData() + ['user' => null, 'password' => null]; - -try { - $connection = new Nette\Database\Connection($options['dsn'], $options['user'], $options['password']); -} catch (PDOException $e) { - Tester\Environment::skip("Connection to '$options[dsn]' failed. Reason: " . $e->getMessage()); -} - -if (strpos($options['dsn'], 'sqlite::memory:') === false) { - Tester\Environment::lock($options['dsn'], TEMP_DIR); -} - -$driverName = $connection->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME); -$cacheMemoryStorage = new Nette\Caching\Storages\MemoryStorage; - -$structure = new Nette\Database\Structure($connection, $cacheMemoryStorage); -$conventions = new Nette\Database\Conventions\StaticConventions; -$context = new Nette\Database\Context($connection, $structure, $conventions, $cacheMemoryStorage); - -//Testing -Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverName}-nette_test1.sql"); +Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driverName}-nette_test1.sql"); $book = $context->table('author')->insert([ 'name' => $context->literal('LOWER(?)', 'Eddard Stark'), diff --git a/tests/Database/Table/bugs/bug49.phpt b/tests/Database/Table/bugs/bug49.phpt index d095f7ee2..af0dc7fce 100644 --- a/tests/Database/Table/bugs/bug49.phpt +++ b/tests/Database/Table/bugs/bug49.phpt @@ -11,8 +11,7 @@ use Tester\Assert; require __DIR__ . '/../../connect.inc.php'; -$context->query('CREATE DATABASE IF NOT EXISTS nette_test'); -$context->query('USE nette_test'); + $context->query('CREATE TABLE `TABLE 30` (id int)'); Assert::same( diff --git a/tests/Database/connect.inc.php b/tests/Database/connect.inc.php index 3a591b69c..758bfb7f6 100644 --- a/tests/Database/connect.inc.php +++ b/tests/Database/connect.inc.php @@ -11,16 +11,16 @@ $options = Tester\Environment::loadData() + ['user' => null, 'password' => null]; +if (strpos($options['dsn'], 'sqlite::memory:') === false) { + Tester\Environment::lock($options['dsn'], TEMP_DIR); +} + try { $connection = new Nette\Database\Connection($options['dsn'], $options['user'], $options['password']); } catch (PDOException $e) { Tester\Environment::skip("Connection to '$options[dsn]' failed. Reason: " . $e->getMessage()); } -if (strpos($options['dsn'], 'sqlite::memory:') === false) { - Tester\Environment::lock($options['dsn'], TEMP_DIR); -} - $driverName = $connection->getPdo()->getAttribute(PDO::ATTR_DRIVER_NAME); $cacheMemoryStorage = new Nette\Caching\Storages\MemoryStorage; @@ -32,7 +32,7 @@ /** Replaces [] with driver-specific quotes */ function reformat($s): string { - global $driverName; + $driverName = $GLOBALS['driverName']; if (is_array($s)) { if (isset($s[$driverName])) { return $s[$driverName]; From 499045d68c29de4fd5495d7239a7747d77f8b157 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 25 Jan 2021 00:54:46 +0100 Subject: [PATCH 2/5] tested using GitHub Actions --- .gitattributes | 3 +- .github/workflows/coding-style.yml | 31 +++++++ .github/workflows/static-analysis.yml | 21 +++++ .github/workflows/tests.yml | 129 ++++++++++++++++++++++++++ .travis.yml | 83 ----------------- appveyor.yml | 47 ---------- readme.md | 3 +- tests/.coveralls.yml | 2 +- tests/databases.appveyor.ini | 23 ----- tests/databases.github.ini | 27 ++++++ tests/databases.sqlite.ini | 2 + tests/databases.travis.ini | 12 --- 12 files changed, 213 insertions(+), 170 deletions(-) create mode 100644 .github/workflows/coding-style.yml create mode 100644 .github/workflows/static-analysis.yml create mode 100644 .github/workflows/tests.yml delete mode 100644 .travis.yml delete mode 100644 appveyor.yml delete mode 100644 tests/databases.appveyor.ini create mode 100644 tests/databases.github.ini create mode 100644 tests/databases.sqlite.ini delete mode 100644 tests/databases.travis.ini diff --git a/.gitattributes b/.gitattributes index 3aa6270af..9670e954e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,7 @@ .gitattributes export-ignore .gitignore export-ignore .github export-ignore -.travis.yml export-ignore -ecs.php export-ignore +ncs.* export-ignore phpstan.neon export-ignore tests/ export-ignore diff --git a/.github/workflows/coding-style.yml b/.github/workflows/coding-style.yml new file mode 100644 index 000000000..79c9babf1 --- /dev/null +++ b/.github/workflows/coding-style.yml @@ -0,0 +1,31 @@ +name: Coding Style + +on: [push, pull_request] + +jobs: + nette_cc: + name: Nette Code Checker + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: shivammathur/setup-php@v2 + with: + php-version: 7.2 + coverage: none + + - run: composer create-project nette/code-checker temp/code-checker ^3 --no-progress + - run: php temp/code-checker/code-checker --strict-types --no-progress --ignore "tests/*/fixtures" + + + nette_cs: + name: Nette Coding Standard + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + coverage: none + + - run: composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress + - run: php temp/coding-standard/ecs check diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 000000000..18e8ae5be --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,21 @@ +name: Static Analysis (only informative) + +on: + push: + branches: + - master + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: shivammathur/setup-php@v2 + with: + php-version: 7.4 + coverage: none + + - run: composer install --no-progress --prefer-dist + - run: composer phpstan -- --no-progress + continue-on-error: true # is only informative diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..670686bd9 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,129 @@ +name: Tests + +on: [push, pull_request] + +env: + php-extensions: mbstring, intl, pdo_sqlsrv-5.10.0beta2 + php-tools: "composer:v2, pecl" + +jobs: + tests: + runs-on: ubuntu-latest + strategy: + matrix: + php: ['7.1', '7.2', '7.3', '7.4', '8.0'] + + fail-fast: false + + name: PHP ${{ matrix.php }} tests + + services: + mysql57: + image: mysql:5.7 + env: + MYSQL_DATABASE: nette_test + MYSQL_ROOT_PASSWORD: root + ports: + - 3306:3306 + options: >- + --health-cmd "mysqladmin ping -ppass" + --health-interval 10s + --health-start-period 10s + --health-timeout 5s + --health-retries 10 + + mysql80: + image: mysql:8.0 + ports: + - 3307:3306 + options: >- + --health-cmd="mysqladmin ping -ppass" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + -e MYSQL_ROOT_PASSWORD=root + -e MYSQL_DATABASE=nette_test + --entrypoint sh mysql:8 -c "exec docker-entrypoint.sh mysqld --default-authentication-plugin=mysql_native_password" + + postgres96: + image: postgres:9.6 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: nette_test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + postgres13: + image: postgres:13 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: nette_test + ports: + - 5433:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + mssql: + image: mcr.microsoft.com/mssql/server:latest + env: + ACCEPT_EULA: Y + SA_PASSWORD: YourStrong!Passw0rd + MSSQL_PID: Developer + ports: + - 1433:1433 + options: >- + --name=mssql + --health-cmd "/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'SELECT 1'" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v3 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.php-extensions }} + tools: ${{ env.php-tools }} + coverage: none + + - name: Create databases.ini + run: cp ./tests/databases.github.ini ./tests/Database/databases.ini + + - name: Create MS SQL Database + run: docker exec -i mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'YourStrong!Passw0rd' -Q 'CREATE DATABASE nette_test' + + - run: composer install --no-progress --prefer-dist + - run: vendor/bin/tester tests -s -C + - if: failure() + uses: actions/upload-artifact@v3 + with: + name: output + path: tests/**/output + + + lowest_dependencies: + name: Lowest Dependencies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.0 + coverage: none + + - name: Create databases.ini + run: cp ./tests/databases.sqlite.ini ./tests/Database/databases.ini + + - run: composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable + - run: vendor/bin/tester tests -s -C diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index eb5ab5686..000000000 --- a/.travis.yml +++ /dev/null @@ -1,83 +0,0 @@ -language: php -php: - - 7.1 - - 7.2 - - 7.3 - - 7.4 - - 8.0snapshot - -services: - - mysql - - postgresql - -before_install: - # turn off XDebug - - phpenv config-rm xdebug.ini || return 0 - - # Create databases.ini - - cp ./tests/databases.travis.ini ./tests/Database/databases.ini - - # Create Postgre database - - psql -c "CREATE DATABASE nette_test WITH ENCODING 'UTF8' LC_COLLATE='POSIX' TEMPLATE=template0" -U postgres - -install: - - travis_retry composer install --no-progress --prefer-dist - -script: - - vendor/bin/tester tests -s - -after_failure: - # Print *.actual content - - for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done - -jobs: - include: - - name: Lowest Dependencies - install: - - travis_retry composer update --no-progress --prefer-dist --prefer-lowest --prefer-stable - - - - name: Nette Code Checker - php: 7.4 - install: - - travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress - script: - - php temp/code-checker/code-checker --strict-types - - - - name: Nette Coding Standard - php: 7.4 - install: - - travis_retry composer create-project nette/coding-standard temp/coding-standard ^3 --no-progress - script: - - php temp/coding-standard/ecs check - - - - stage: Static Analysis (informative) - php: 7.4 - script: - - composer run-script phpstan - - - - stage: Code Coverage - php: 7.4 - script: - - vendor/bin/tester -p phpdbg tests -s --coverage ./coverage.xml --coverage-src ./src - after_script: - - wget https://github.com/satooshi/php-coveralls/releases/download/v1.0.1/coveralls.phar - - php coveralls.phar --verbose --config tests/.coveralls.yml - - - allow_failures: - - stage: Static Analysis (informative) - - stage: Code Coverage - - -dist: xenial - -cache: - directories: - - $HOME/.composer/cache - -notifications: - email: false diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 91ad14250..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,47 +0,0 @@ -build: off -cache: - - c:\php -> appveyor.yml - - '%LOCALAPPDATA%\Composer\files -> appveyor.yml' - -clone_folder: c:\projects\database - -services: - - mssql2008r2sp2 -# - mssql2012sp1 -# - mssql2014 -# - mssql2016 - - mysql - -init: - - SET PATH=c:\php;%PATH% - - SET PHP=1 - - SET ANSICON=121x90 (121x90) - -install: - # Install PHP - - IF EXIST c:\php (SET PHP=0) ELSE (SET PHP=1) - - IF %PHP%==1 mkdir c:\php - - IF %PHP%==1 cd c:\php - - IF %PHP%==1 curl https://windows.php.net/downloads/releases/archives/php-7.1.5-Win32-VC14-x64.zip --output php.zip - - IF %PHP%==1 7z x php.zip >nul - - IF %PHP%==1 echo extension_dir=ext >> php.ini - - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini - - IF %PHP%==1 curl https://github.com/Microsoft/msphpsql/releases/download/v4.3.0/Windows-7.1.zip -L --output sqlsrv.zip - - IF %PHP%==1 7z x sqlsrv.zip >nul - - IF %PHP%==1 copy Windows-7.1\x64\php_pdo_sqlsrv_71_ts.dll ext\php_pdo_sqlsrv_ts.dll - - IF %PHP%==1 del /Q *.zip - - cd c:\projects\database - - # Install Nette Tester - - appveyor DownloadFile https://getcomposer.org/composer.phar - - php composer.phar install --prefer-dist --no-interaction --no-progress - - # Create databases.ini - - copy tests\databases.appveyor.ini tests\Database\databases.ini - -test_script: - - vendor\bin\tester tests -s -c tests\php-win.ini - -on_failure: - # Print *.actual content - - for /r %%x in (*.actual) do ( type "%%x" ) diff --git a/readme.md b/readme.md index cc5a955bc..395122f61 100644 --- a/readme.md +++ b/readme.md @@ -2,8 +2,7 @@ Nette Database ============== [![Downloads this Month](https://img.shields.io/packagist/dm/nette/database.svg)](https://packagist.org/packages/nette/database) -[![Build Status](https://travis-ci.org/nette/database.svg?branch=master)](https://travis-ci.org/nette/database) -[![Build Status Windows](https://ci.appveyor.com/api/projects/status/github/nette/database?branch=master&svg=true)](https://ci.appveyor.com/project/dg/database/branch/master) +[![Tests](https://github.com/nette/database/workflows/Tests/badge.svg?branch=v3.0)](https://github.com/nette/database/actions) [![Latest Stable Version](https://poser.pugx.org/nette/database/v/stable)](https://github.com/nette/database/releases) [![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/database/blob/master/license.md) diff --git a/tests/.coveralls.yml b/tests/.coveralls.yml index 82764a3f5..845038250 100644 --- a/tests/.coveralls.yml +++ b/tests/.coveralls.yml @@ -1,4 +1,4 @@ # for php-coveralls -service_name: travis-ci +service_name: github-actions coverage_clover: coverage.xml json_path: coverage.json diff --git a/tests/databases.appveyor.ini b/tests/databases.appveyor.ini deleted file mode 100644 index 2dededfeb..000000000 --- a/tests/databases.appveyor.ini +++ /dev/null @@ -1,23 +0,0 @@ -[mysql] -dsn = "mysql:host=127.0.0.1" -user = root -password = "Password12!" - -[sqlite] -dsn = "sqlite::memory:" - -[sqlsrv 2008] -dsn = "sqlsrv:Server=(local)\SQL2008R2SP2;Database=master" -user = sa -password = "Password12!" - -;[sqlsrv 2012] -;dsn = "sqlsrv:Server=(local)\SQL2012SP1;Database=master" -;user = sa -;password = "Password12!" - -; NDB does not differentiate between 2012 and 2014 -;[sqlsrv 2014] -;dsn = "sqlsrv:Server=(local)\SQL2014;Database=master" -;user = sa -;password = "Password12!" diff --git a/tests/databases.github.ini b/tests/databases.github.ini new file mode 100644 index 000000000..289a8dcfa --- /dev/null +++ b/tests/databases.github.ini @@ -0,0 +1,27 @@ +[mysql 5.7] +dsn = "mysql:host=127.0.0.1;port=3306;dbname=nette_test" +user = root +password = root + +[mysql 8.0] +dsn = "mysql:host=127.0.0.1;port=3307;dbname=nette_test" +user = root +password = root + +[postgresql 9.6] +dsn = "pgsql:host=127.0.0.1;port=5432;dbname=nette_test" +user = postgres +password = postgres + +[postgresql 13] +dsn = "pgsql:host=127.0.0.1;port=5433;dbname=nette_test" +user = postgres +password = postgres + +[sqlsrv] +dsn = "sqlsrv:Server=localhost,1433;Database=nette_test" +user = SA +password = "YourStrong!Passw0rd" + +[sqlite] +dsn = "sqlite::memory:" diff --git a/tests/databases.sqlite.ini b/tests/databases.sqlite.ini new file mode 100644 index 000000000..2d89c58ce --- /dev/null +++ b/tests/databases.sqlite.ini @@ -0,0 +1,2 @@ +[sqlite] +dsn = "sqlite::memory:" diff --git a/tests/databases.travis.ini b/tests/databases.travis.ini deleted file mode 100644 index e253dc27a..000000000 --- a/tests/databases.travis.ini +++ /dev/null @@ -1,12 +0,0 @@ -[mysql] -dsn = "mysql:host=127.0.0.1" -user = root -password = - -[postgresql] -dsn = "pgsql:host=127.0.0.1;dbname=nette_test" -user = postgres -password = - -[sqlite] -dsn = "sqlite::memory:" From 2d2188571035badb90e7385c5b4b407e2c8fd844 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 31 May 2021 19:02:42 +0200 Subject: [PATCH 3/5] type fix [Closes #280] --- src/Database/Table/Selection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Database/Table/Selection.php b/src/Database/Table/Selection.php index 88df90814..32084ff79 100644 --- a/src/Database/Table/Selection.php +++ b/src/Database/Table/Selection.php @@ -488,7 +488,7 @@ public function count(string $column = null): int $this->execute(); return count($this->data); } - return $this->aggregation("COUNT($column)"); + return (int) $this->aggregation("COUNT($column)"); } From 43b27588f077449b5a1d9488ef5e44e600270fed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Mystik=20Jon=C3=A1=C5=A1?= Date: Mon, 8 Nov 2021 16:21:36 +0100 Subject: [PATCH 4/5] Fixed aggregations when group by and having conditions are used (#284) --- src/Database/Table/GroupedSelection.php | 21 ++++++--- src/Database/Table/Selection.php | 22 +++++---- src/Database/Table/SqlBuilder.php | 13 ++++++ tests/Database/Table/Table.aggregation.phpt | 51 +++++++++++++++++++++ 4 files changed, 93 insertions(+), 14 deletions(-) diff --git a/src/Database/Table/GroupedSelection.php b/src/Database/Table/GroupedSelection.php index 4e6f57c57..05ff6294e 100644 --- a/src/Database/Table/GroupedSelection.php +++ b/src/Database/Table/GroupedSelection.php @@ -96,7 +96,7 @@ public function order(string $columns, ...$params) /** * @return mixed */ - public function aggregation(string $function) + public function aggregation(string $function, string $groupFunction = null) { $aggregation = &$this->getRefTable($refPath)->aggregation[$refPath . $function . $this->sqlBuilder->getSelectQueryHash($this->getPreviousAccessedColumns())]; @@ -105,12 +105,21 @@ public function aggregation(string $function) $selection = $this->createSelectionInstance(); $selection->getSqlBuilder()->importConditions($this->getSqlBuilder()); - $selection->select($function); - $selection->select("$this->name.$this->column"); - $selection->group("$this->name.$this->column"); - foreach ($selection as $row) { - $aggregation[$row[$this->column]] = $row; + if ($groupFunction && $selection->getSqlBuilder()->importGroupConditions($this->getSqlBuilder())) { + $selection->select("$function AS aggregate, $this->name.$this->column AS groupname"); + $selection->group($selection->getSqlBuilder()->getGroup() . ", $this->name.$this->column"); + $query = "SELECT $groupFunction(aggregate) AS groupaggregate, groupname FROM (" . $selection->getSql() . ') AS aggregates GROUP BY groupname'; + foreach ($this->context->query($query, ...$selection->getSqlBuilder()->getParameters()) as $row) { + $aggregation[$row->groupname] = $row; + } + } else { + $selection->select($function); + $selection->select("$this->name.$this->column"); + $selection->group("$this->name.$this->column"); + foreach ($selection as $row) { + $aggregation[$row[$this->column]] = $row; + } } } diff --git a/src/Database/Table/Selection.php b/src/Database/Table/Selection.php index 32084ff79..f8b1b3a21 100644 --- a/src/Database/Table/Selection.php +++ b/src/Database/Table/Selection.php @@ -467,13 +467,19 @@ public function alias(string $tableChain, string $alias) * @param string $function select call in "FUNCTION(column)" format * @return mixed */ - public function aggregation(string $function) + public function aggregation(string $function, string $groupFunction = null) { $selection = $this->createSelectionInstance(); $selection->getSqlBuilder()->importConditions($this->getSqlBuilder()); - $selection->select($function); - foreach ($selection->fetch() as $val) { - return $val; + if ($groupFunction && $selection->getSqlBuilder()->importGroupConditions($this->getSqlBuilder())) { + $selection->select("$function AS aggregate"); + $query = "SELECT $groupFunction(aggregate) AS groupaggregate FROM (" . $selection->getSql() . ') AS aggregates'; + return $this->context->query($query, ...$selection->getSqlBuilder()->getParameters())->fetch()->groupaggregate; + } else { + $selection->select($function); + foreach ($selection->fetch() as $val) { + return $val; + } } } @@ -488,7 +494,7 @@ public function count(string $column = null): int $this->execute(); return count($this->data); } - return (int) $this->aggregation("COUNT($column)"); + return (int) $this->aggregation("COUNT($column)", 'SUM'); } @@ -498,7 +504,7 @@ public function count(string $column = null): int */ public function min(string $column) { - return $this->aggregation("MIN($column)"); + return $this->aggregation("MIN($column)", 'MIN'); } @@ -508,7 +514,7 @@ public function min(string $column) */ public function max(string $column) { - return $this->aggregation("MAX($column)"); + return $this->aggregation("MAX($column)", 'MAX'); } @@ -518,7 +524,7 @@ public function max(string $column) */ public function sum(string $column) { - return $this->aggregation("SUM($column)"); + return $this->aggregation("SUM($column)", 'SUM'); } diff --git a/src/Database/Table/SqlBuilder.php b/src/Database/Table/SqlBuilder.php index aa4cbce7f..518b8ff03 100644 --- a/src/Database/Table/SqlBuilder.php +++ b/src/Database/Table/SqlBuilder.php @@ -256,6 +256,19 @@ public function importConditions(self $builder): void } + public function importGroupConditions(self $builder): bool + { + if ($builder->having) { + $this->group = $builder->group; + $this->having = $builder->having; + $this->parameters['group'] = $builder->parameters['group']; + $this->parameters['having'] = $builder->parameters['having']; + return true; + } + return false; + } + + /********************* SQL selectors ****************d*g**/ diff --git a/tests/Database/Table/Table.aggregation.phpt b/tests/Database/Table/Table.aggregation.phpt index 87f15901f..98b83e2c9 100644 --- a/tests/Database/Table/Table.aggregation.phpt +++ b/tests/Database/Table/Table.aggregation.phpt @@ -41,3 +41,54 @@ test('', function () use ($context) { Assert::count(2, $authors); Assert::same(2, $authors->count('DISTINCT author.id')); // SELECT COUNT(DISTINCT author.id) FROM `author` INNER JOIN `book` ON `author`.`id` = `book`.`author_id` WHERE (`book`.`translator_id` IS NOT NULL) }); + + +test('', function () use ($context) { + $authors = $context->table('book')->group('book.id')->having('COUNT(DISTINCT :book_tag.tag_id) < 2'); // SELECT `author`.* FROM `author` INNER JOIN `book` ON `author`.`id` = `book`.`author_id` WHERE (`book`.`translator_id` IS NOT NULL) GROUP BY `author`.`id` + Assert::count(2, $authors); + Assert::same(2, $authors->count('DISTINCT author.id')); // SELECT COUNT(DISTINCT author.id) FROM `author` INNER JOIN `book` ON `author`.`id` = `book`.`author_id` WHERE (`book`.`translator_id` IS NOT NULL) +}); + + +test('', function () use ($context) { + $bookTags = []; + foreach ($context->table('book') as $book) { + $book_tags = $book->related('book_tag'); + $bookTags[$book->title] = $book_tags->count('DISTINCT tag.id'); + } + + Assert::same([ + '1001 tipu a triku pro PHP' => 2, + 'JUSH' => 1, + 'Nette' => 1, + 'Dibi' => 2, + ], $bookTags); +}); + + +test('', function () use ($context) { + $bookTags = []; + foreach ($context->table('book')->group('book.id, book.title')->having('COUNT(DISTINCT :book_tag.tag_id) < 2') as $book) { + $book_tags = $book->related('book_tag'); + $bookTags[$book->title] = $book_tags->count('DISTINCT tag.id'); + } + + Assert::same([ + 'JUSH' => 1, + 'Nette' => 1, + ], $bookTags); +}); + +test('', function () use ($context) { + $bookTags = []; + foreach ($context->table('author') as $author) { + $books = $author->related('book'); + $bookTags[$author->name] = $books->group('book.id')->having('COUNT(DISTINCT :book_tag.tag_id) < 2')->count('DISTINCT book.id'); + } + + Assert::same([ + 'Jakub Vrana' => 1, + 'David Grudl' => 1, + 'Geek' => 0, + ], $bookTags); +}); From 26a93f271143e32cac2d49a7532a0744e05db44d Mon Sep 17 00:00:00 2001 From: David Grudl Date: Wed, 13 Dec 2023 15:20:16 +0100 Subject: [PATCH 5/5] cs --- src/Bridges/DatabaseDI/DatabaseExtension.php | 3 ++- src/Database/Helpers.php | 3 ++- src/Database/Table/Selection.php | 4 ++-- src/Database/Table/SqlBuilder.php | 3 ++- tests/Database/Reflection.phpt | 2 +- tests/Database/Table/Selection.insert().phpt | 2 +- tests/Database/Table/bugs/bug216.phpt | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Bridges/DatabaseDI/DatabaseExtension.php b/src/Bridges/DatabaseDI/DatabaseExtension.php index 844a65de3..c2fdff055 100644 --- a/src/Bridges/DatabaseDI/DatabaseExtension.php +++ b/src/Bridges/DatabaseDI/DatabaseExtension.php @@ -11,6 +11,7 @@ use Nette; use Nette\Schema\Expect; +use Tracy\BlueScreen; /** @@ -66,7 +67,7 @@ public function beforeCompile() $builder = $this->getContainerBuilder(); foreach ($this->config as $name => $config) { - if ($config->debugger ?? $builder->getByType(\Tracy\BlueScreen::class)) { + if ($config->debugger ?? $builder->getByType(BlueScreen::class)) { $connection = $builder->getDefinition($this->prefix("$name.connection")); $connection->addSetup('@Tracy\BlueScreen::addPanel', [ [Nette\Bridges\DatabaseTracy\ConnectionPanel::class, 'renderException'], diff --git a/src/Database/Helpers.php b/src/Database/Helpers.php index e87f00995..1bfd0aac5 100644 --- a/src/Database/Helpers.php +++ b/src/Database/Helpers.php @@ -244,7 +244,8 @@ public static function createDebugPanel( $connection, bool $explain = true, string $name = null - ): Nette\Bridges\DatabaseTracy\ConnectionPanel { + ): Nette\Bridges\DatabaseTracy\ConnectionPanel + { $panel = new Nette\Bridges\DatabaseTracy\ConnectionPanel($connection); $panel->explain = $explain; $panel->name = $name; diff --git a/src/Database/Table/Selection.php b/src/Database/Table/Selection.php index f8b1b3a21..1d4cce6f6 100644 --- a/src/Database/Table/Selection.php +++ b/src/Database/Table/Selection.php @@ -354,7 +354,7 @@ protected function condition($condition, array $params, $tableChain = null): voi * More calls appends with AND. * @param array $parameters ['column1' => 1, 'column2 > ?' => 2, 'full condition'] * @return static - * @throws \Nette\InvalidArgumentException + * @throws Nette\InvalidArgumentException */ public function whereOr(array $parameters) { @@ -841,7 +841,7 @@ public function insert(iterable $data) } } - // Primary without autoincrement, try get primary from inserting data + // Primary without autoincrement, try get primary from inserting data } elseif ($this->primary && isset($data[$this->primary])) { $primaryKey = $data[$this->primary]; diff --git a/src/Database/Table/SqlBuilder.php b/src/Database/Table/SqlBuilder.php index 518b8ff03..579649947 100644 --- a/src/Database/Table/SqlBuilder.php +++ b/src/Database/Table/SqlBuilder.php @@ -799,7 +799,8 @@ protected function addConditionComposition( array $parameters, array &$conditions, array &$conditionsParameters - ): bool { + ): bool + { if ($this->driver->isSupported(ISupplementalDriver::SUPPORT_MULTI_COLUMN_AS_OR_COND)) { $conditionFragment = '(' . implode(' = ? AND ', $columns) . ' = ?) OR '; $condition = substr(str_repeat($conditionFragment, count($parameters)), 0, -4); diff --git a/tests/Database/Reflection.phpt b/tests/Database/Reflection.phpt index e34107ad8..881f56cc4 100644 --- a/tests/Database/Reflection.phpt +++ b/tests/Database/Reflection.phpt @@ -91,7 +91,7 @@ $expectedColumns = [ switch ($driverName) { case 'mysql': - $version = $connection->getPdo()->getAttribute(\PDO::ATTR_SERVER_VERSION); + $version = $connection->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION); if (version_compare($version, '8.0', '>=')) { $expectedColumns[0]['size'] = null; } diff --git a/tests/Database/Table/Selection.insert().phpt b/tests/Database/Table/Selection.insert().phpt index 6bba685ff..4fdb7ae46 100644 --- a/tests/Database/Table/Selection.insert().phpt +++ b/tests/Database/Table/Selection.insert().phpt @@ -17,7 +17,7 @@ Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../files/{$driverN $book = $context->table('author')->insert([ 'name' => $context->literal('LOWER(?)', 'Eddard Stark'), 'web' => 'http://example.com', - 'born' => new \DateTime('2011-11-11'), + 'born' => new DateTime('2011-11-11'), ]); // INSERT INTO `author` (`name`, `web`) VALUES (LOWER('Eddard Stark'), 'http://example.com', '2011-11-11 00:00:00') // id = 14 diff --git a/tests/Database/Table/bugs/bug216.phpt b/tests/Database/Table/bugs/bug216.phpt index c56e6e6f4..9575284be 100644 --- a/tests/Database/Table/bugs/bug216.phpt +++ b/tests/Database/Table/bugs/bug216.phpt @@ -16,7 +16,7 @@ Nette\Database\Helpers::loadFromFile($connection, __DIR__ . "/../../files/{$driv $book = $context->table('author')->insert([ 'name' => $context->literal('LOWER(?)', 'Eddard Stark'), 'web' => 'http://example.com', - 'born' => new \DateTime('2011-11-11'), + 'born' => new DateTime('2011-11-11'), ]); // INSERT INTO `author` (`name`, `web`) VALUES (LOWER('Eddard Stark'), 'http://example.com', '2011-11-11 00:00:00') // id = 14