From ec1465af8667206aebd0ae65f84a6a2c164308d2 Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 19 Sep 2024 12:37:13 +0200 Subject: [PATCH 01/11] add tests for every database handler --- .github/workflows/phpunit.yml | 90 +++++++++++++++-- src/Models/QueueJobModel.php | 2 +- tests/_support/Config/Registrar.php | 143 ++++++++++++++++++++++++++++ 3 files changed, 226 insertions(+), 9 deletions(-) create mode 100644 tests/_support/Config/Registrar.php diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index f2b9a10..bc7f3a6 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -20,10 +20,68 @@ on: jobs: main: - name: PHP ${{ matrix.php-versions }} Unit Tests - runs-on: ubuntu-22.04 + name: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + strategy: + matrix: + php-versions: ['8.1', '8.2', '8.3'] + db-platforms: ['MySQLi', 'SQLite3'] + include: + # Postgre + - php-versions: '8.1' + db-platforms: Postgre + # SQLSRV + - php-versions: '8.1' + db-platforms: SQLSRV + # OCI8 + - php-versions: '8.1' + db-platforms: OCI8 services: + mysql: + image: mysql:8.0 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: test + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + postgres: + image: postgres + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: test + ports: + - 5432:5432 + options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 + + mssql: + image: mcr.microsoft.com/mssql/server:2019-CU10-ubuntu-20.04 + env: + SA_PASSWORD: 1Secure*Password1 + ACCEPT_EULA: Y + MSSQL_PID: Developer + ports: + - 1433:1433 + options: --health-cmd="/opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" --health-interval=10s --health-timeout=5s --health-retries=3 + + oracle: + image: gvenzl/oracle-xe:18 + env: + ORACLE_RANDOM_PASSWORD: true + APP_USER: ORACLE + APP_USER_PASSWORD: ORACLE + ports: + - 1521:1521 + options: >- + --health-cmd healthcheck.sh + --health-interval 20s + --health-timeout 10s + --health-retries 10 + redis: image: redis ports: @@ -34,12 +92,27 @@ jobs: --health-timeout=5s --health-retries=3 - if: "!contains(github.event.head_commit.message, '[ci skip]')" - strategy: - matrix: - php-versions: ['8.1', '8.2', '8.3'] - steps: + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + with: + # this might remove tools that are actually needed, + # if set to "true" but frees about 6 GB + tool-cache: false + + # all of these default to true, but feel free to set to + # "false" if necessary for your workflow + android: true + dotnet: true + haskell: true + large-packages: false + docker-images: true + swap-storage: true + + - name: Create database for MSSQL Server + if: matrix.db-platforms == 'SQLSRV' + run: sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q "CREATE DATABASE test" + - name: Checkout uses: actions/checkout@v4 @@ -74,6 +147,7 @@ jobs: - name: Test with PHPUnit run: vendor/bin/phpunit --coverage-text env: + DB: ${{ matrix.db-platforms }} TERM: xterm-256color TACHYCARDIA_MONITOR_GA: enabled @@ -86,7 +160,7 @@ jobs: env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_PARALLEL: true - COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} + COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} coveralls: needs: [main] diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index 121c8fb..b6df36c 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -111,7 +111,7 @@ private function setPriority(BaseBuilder $builder, array $priority): BaseBuilder $builder->whereIn('priority', $priority); if ($priority !== ['default']) { - if ($this->db->DBDriver === 'SQLite3') { + if ($this->db->DBDriver !== 'MySQLi') { $builder->orderBy( 'CASE priority ' . implode( diff --git a/tests/_support/Config/Registrar.php b/tests/_support/Config/Registrar.php new file mode 100644 index 0000000..86a303e --- /dev/null +++ b/tests/_support/Config/Registrar.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\Config; + +/** + * Class Registrar + * + * Provides a basic registrar class for testing BaseConfig registration functions. + */ +class Registrar +{ + /** + * DB config array for testing purposes. + * + * @var array|bool|int|string>> + */ + protected static array $dbConfig = [ + 'MySQLi' => [ + 'DSN' => '', + 'hostname' => '127.0.0.1', + 'username' => 'root', + 'password' => '', + 'database' => 'test', + 'DBDriver' => 'MySQLi', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => true, + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + 'port' => 3306, + ], + 'Postgre' => [ + 'DSN' => '', + 'hostname' => 'localhost', + 'username' => 'postgres', + 'password' => 'postgres', + 'database' => 'test', + 'DBDriver' => 'Postgre', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => true, + 'charset' => 'utf8', + 'DBCollat' => '', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + 'port' => 5432, + ], + 'SQLite3' => [ + 'DSN' => '', + 'hostname' => 'localhost', + 'username' => '', + 'password' => '', + 'database' => 'database.db', + 'DBDriver' => 'SQLite3', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => true, + 'charset' => 'utf8', + 'DBCollat' => '', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + 'port' => 3306, + 'foreignKeys' => true, + ], + 'SQLSRV' => [ + 'DSN' => '', + 'hostname' => 'localhost', + 'username' => 'sa', + 'password' => '1Secure*Password1', + 'database' => 'test', + 'DBDriver' => 'SQLSRV', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => true, + 'charset' => 'utf8', + 'DBCollat' => '', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + 'port' => 1433, + ], + 'OCI8' => [ + 'DSN' => 'localhost:1521/XEPDB1', + 'hostname' => '', + 'username' => 'ORACLE', + 'password' => 'ORACLE', + 'database' => '', + 'DBDriver' => 'OCI8', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => true, + 'charset' => 'AL32UTF8', + 'DBCollat' => '', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + ], + ]; + + /** + * Override database config + * + * @return array|bool|int|string> + */ + public static function Database(): array + { + $config = []; + + // Under GitHub Actions, we can set an ENV var named 'DB' + // so that we can test against multiple databases. + if (($group = getenv('DB')) && isset(self::$dbConfig[$group])) { + $config['tests'] = self::$dbConfig[$group]; + } + + return $config; + } +} From d0e4dfaaa3f755efafb248de119772aeb21ace57 Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 19 Sep 2024 12:49:57 +0200 Subject: [PATCH 02/11] add permissions to the phpunit workflow --- .github/workflows/phpunit.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index bc7f3a6..d0d9906 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -18,6 +18,13 @@ on: - 'phpunit*' - '.github/workflows/phpunit.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + jobs: main: name: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} From c1481bb14e3f04d77e1f5b626c30a8b3654e119b Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 19 Sep 2024 12:59:53 +0200 Subject: [PATCH 03/11] update workflow --- .github/workflows/phpunit.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index d0d9906..a5ce956 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -53,7 +53,11 @@ jobs: MYSQL_DATABASE: test ports: - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 postgres: image: postgres @@ -63,20 +67,28 @@ jobs: POSTGRES_DB: test ports: - 5432:5432 - options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 + options: >- + --health-cmd=pg_isready + --health-interval=10s + --health-timeout=5s + --health-retries=3 mssql: - image: mcr.microsoft.com/mssql/server:2019-CU10-ubuntu-20.04 + image: mcr.microsoft.com/mssql/server:2022-latest env: - SA_PASSWORD: 1Secure*Password1 + MSSQL_SA_PASSWORD: 1Secure*Password1 ACCEPT_EULA: Y MSSQL_PID: Developer ports: - 1433:1433 - options: --health-cmd="/opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" --health-interval=10s --health-timeout=5s --health-retries=3 + options: >- + --health-cmd="/opt/mssql-tools18/bin/sqlcmd -C -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" + --health-interval=10s + --health-timeout=5s + --health-retries=3 oracle: - image: gvenzl/oracle-xe:18 + image: gvenzl/oracle-xe:21 env: ORACLE_RANDOM_PASSWORD: true APP_USER: ORACLE From eacc271ceacd60927c978bad5d0d5ecf35e0b683 Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 19 Sep 2024 13:51:18 +0200 Subject: [PATCH 04/11] update tests and workflow --- .github/workflows/phpunit.yml | 2 +- tests/DatabaseHandlerTest.php | 47 +++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index a5ce956..b969c5a 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -140,7 +140,7 @@ jobs: with: php-version: ${{ matrix.php-versions }} tools: composer, phive, phpunit - extensions: intl, json, mbstring, gd, xdebug, xml, sqlite3, redis + extensions: intl, json, mbstring, gd, xdebug, xml, sqlite3, sqlsrv, oci8, pgsql coverage: xdebug env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index 33fd4af..51cf4ba 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -13,6 +13,7 @@ namespace Tests; +use Closure; use CodeIgniter\I18n\Time; use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Enums\Status; @@ -35,12 +36,22 @@ final class DatabaseHandlerTest extends TestCase protected $seed = TestDatabaseQueueSeeder::class; private QueueConfig $config; + private Closure $field; protected function setUp(): void { parent::setUp(); $this->config = config(QueueConfig::class); + + // handle filed custom type conversion for SQLSRV + $this->field = function ($field) { + if ($this->db->DBDriver === 'SQLSRV') { + return "CONVERT(VARCHAR, {$field})"; + } + + return $field; + }; } public function testDatabaseHandler(): void @@ -87,9 +98,9 @@ public function testPush(): void $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), - 'available_at' => '1703859316', + 'queue' => 'queue', + ($this->field)('payload') => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'available_at' => '1703859316', ]); } @@ -105,10 +116,10 @@ public function testPushWithPriority(): void $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), - 'priority' => 'high', - 'available_at' => '1703859316', + 'queue' => 'queue', + ($this->field)('payload') => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'priority' => 'high', + 'available_at' => '1703859316', ]); } @@ -121,20 +132,20 @@ public function testPushAndPopWithPriority(): void $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), - 'priority' => 'low', - 'available_at' => '1703859316', + 'queue' => 'queue', + ($this->field)('payload') => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), + 'priority' => 'low', + 'available_at' => '1703859316', ]); $result = $handler->setPriority('high')->push('queue', 'success', ['key2' => 'value2']); $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), - 'priority' => 'high', - 'available_at' => '1703859316', + 'queue' => 'queue', + ($this->field)('payload') => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), + 'priority' => 'high', + 'available_at' => '1703859316', ]); $result = $handler->pop('queue', ['high', 'low']); @@ -347,9 +358,9 @@ public function testRetry(): void $this->assertSame($count, 1); $this->seeInDatabase('queue_jobs', [ - 'id' => 3, - 'queue' => 'queue1', - 'payload' => json_encode(['job' => 'failure', 'data' => []]), + 'id' => 3, + 'queue' => 'queue1', + ($this->field)('payload') => json_encode(['job' => 'failure', 'data' => []]), ]); $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 1, From cbc7d71443232e077a325cba5753ef58fa141570 Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 19 Sep 2024 14:50:58 +0200 Subject: [PATCH 05/11] update tests for SQLSRV --- tests/DatabaseHandlerTest.php | 69 ++++++++----------- .../Constraints/SeeInDatabaseExtended.php | 48 +++++++++++++ tests/_support/TestCase.php | 19 +++++ 3 files changed, 96 insertions(+), 40 deletions(-) create mode 100644 tests/_support/Constraints/SeeInDatabaseExtended.php diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index 51cf4ba..40cb4c6 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -13,7 +13,6 @@ namespace Tests; -use Closure; use CodeIgniter\I18n\Time; use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Enums\Status; @@ -36,22 +35,12 @@ final class DatabaseHandlerTest extends TestCase protected $seed = TestDatabaseQueueSeeder::class; private QueueConfig $config; - private Closure $field; protected function setUp(): void { parent::setUp(); $this->config = config(QueueConfig::class); - - // handle filed custom type conversion for SQLSRV - $this->field = function ($field) { - if ($this->db->DBDriver === 'SQLSRV') { - return "CONVERT(VARCHAR, {$field})"; - } - - return $field; - }; } public function testDatabaseHandler(): void @@ -97,10 +86,10 @@ public function testPush(): void $result = $handler->push('queue', 'success', ['key' => 'value']); $this->assertTrue($result); - $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - ($this->field)('payload') => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), - 'available_at' => '1703859316', + $this->seeInDatabaseExtended('queue_jobs', [ + 'queue' => 'queue', + $this->field('payload') => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'available_at' => '1703859316', ]); } @@ -115,11 +104,11 @@ public function testPushWithPriority(): void $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); $this->assertTrue($result); - $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - ($this->field)('payload') => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), - 'priority' => 'high', - 'available_at' => '1703859316', + $this->seeInDatabaseExtended('queue_jobs', [ + 'queue' => 'queue', + $this->field('payload') => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'priority' => 'high', + 'available_at' => '1703859316', ]); } @@ -131,21 +120,21 @@ public function testPushAndPopWithPriority(): void $result = $handler->push('queue', 'success', ['key1' => 'value1']); $this->assertTrue($result); - $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - ($this->field)('payload') => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), - 'priority' => 'low', - 'available_at' => '1703859316', + $this->seeInDatabaseExtended('queue_jobs', [ + 'queue' => 'queue', + $this->field('payload') => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), + 'priority' => 'low', + 'available_at' => '1703859316', ]); $result = $handler->setPriority('high')->push('queue', 'success', ['key2' => 'value2']); $this->assertTrue($result); - $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - ($this->field)('payload') => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), - 'priority' => 'high', - 'available_at' => '1703859316', + $this->seeInDatabaseExtended('queue_jobs', [ + 'queue' => 'queue', + $this->field('payload') => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), + 'priority' => 'high', + 'available_at' => '1703859316', ]); $result = $handler->pop('queue', ['high', 'low']); @@ -216,7 +205,7 @@ public function testPop(): void $result = $handler->pop('queue1', ['default']); $this->assertInstanceOf(QueueJob::class, $result); - $this->seeInDatabase('queue_jobs', [ + $this->seeInDatabaseExtended('queue_jobs', [ 'status' => Status::RESERVED->value, 'available_at' => 1_697_269_860, ]); @@ -243,7 +232,7 @@ public function testLater(): void $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); - $this->seeInDatabase('queue_jobs', [ + $this->seeInDatabaseExtended('queue_jobs', [ 'id' => 2, 'status' => Status::RESERVED->value, ]); @@ -251,7 +240,7 @@ public function testLater(): void $result = $handler->later($queueJob, 60); $this->assertTrue($result); - $this->seeInDatabase('queue_jobs', [ + $this->seeInDatabaseExtended('queue_jobs', [ 'id' => 2, 'status' => Status::PENDING->value, 'available_at' => Time::now()->addSeconds(60)->timestamp, @@ -275,7 +264,7 @@ public function testFailedAndKeepJob(): void $this->dontSeeInDatabase('queue_jobs', [ 'id' => 2, ]); - $this->seeInDatabase('queue_jobs_failed', [ + $this->seeInDatabaseExtended('queue_jobs_failed', [ 'id' => 2, 'connection' => 'database', 'queue' => 'queue1', @@ -313,7 +302,7 @@ public function testDoneAndKeepJob(): void $result = $handler->done($queueJob, true); $this->assertTrue($result); - $this->seeInDatabase('queue_jobs', [ + $this->seeInDatabaseExtended('queue_jobs', [ 'id' => 2, 'status' => Status::DONE->value, ]); @@ -357,10 +346,10 @@ public function testRetry(): void $this->assertSame($count, 1); - $this->seeInDatabase('queue_jobs', [ - 'id' => 3, - 'queue' => 'queue1', - ($this->field)('payload') => json_encode(['job' => 'failure', 'data' => []]), + $this->seeInDatabaseExtended('queue_jobs', [ + 'id' => 3, + 'queue' => 'queue1', + $this->field('payload') => json_encode(['job' => 'failure', 'data' => []]), ]); $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 1, @@ -407,7 +396,7 @@ public function testFlush(): void $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 1, ]); - $this->seeInDatabase('queue_jobs_failed', [ + $this->seeInDatabaseExtended('queue_jobs_failed', [ 'id' => 2, ]); } diff --git a/tests/_support/Constraints/SeeInDatabaseExtended.php b/tests/_support/Constraints/SeeInDatabaseExtended.php new file mode 100644 index 0000000..96397da --- /dev/null +++ b/tests/_support/Constraints/SeeInDatabaseExtended.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\Constraints; + +use CodeIgniter\Test\Constraints\SeeInDatabase; + +class SeeInDatabaseExtended extends SeeInDatabase +{ + /** + * Gets a string representation of the constraint + * + * @param int $options + */ + public function toString(bool $exportObjects = false, $options = 0): string + { + $this->data = array_combine( + array_map(fn ($key) => $this->extractFieldName($key), array_keys($this->data)), + $this->data + ); + + return parent::toString($exportObjects, $options); + } + + /** + * Extract field name from complex key + */ + protected function extractFieldName(string $input): string + { + $pattern = '/CONVERT\(\s*\w+,\s*(\w+)\s*\)/'; + + if (preg_match($pattern, $input, $matches)) { + return $matches[1]; + } + + return $input; + } +} diff --git a/tests/_support/TestCase.php b/tests/_support/TestCase.php index 0b956db..2718d9d 100644 --- a/tests/_support/TestCase.php +++ b/tests/_support/TestCase.php @@ -17,6 +17,7 @@ use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; use Exception; +use Tests\Support\Constraints\SeeInDatabaseExtended; abstract class TestCase extends CIUnitTestCase { @@ -41,4 +42,22 @@ protected function tearDown(): void // Reset the current time. Time::setTestNow(); } + + public function seeInDatabaseExtended(string $table, array $where): void + { + $constraint = new SeeInDatabaseExtended($this->db, $where); + $this->assertThat($table, $constraint); + } + + /** + * Handle custom field type conversion for SQLSRV + */ + public function field(string $name): string + { + if ($this->db->DBDriver === 'SQLSRV') { + return "CONVERT(VARCHAR, {$name})"; + } + + return $name; + } } From 94ba245eeb3b9651d28ec5e6e883f6ae8d7bb715 Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 19 Sep 2024 15:31:49 +0200 Subject: [PATCH 06/11] update tests --- tests/DatabaseHandlerTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index 40cb4c6..6a52338 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -89,7 +89,7 @@ public function testPush(): void $this->seeInDatabaseExtended('queue_jobs', [ 'queue' => 'queue', $this->field('payload') => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), - 'available_at' => '1703859316', + 'available_at' => 1703859316, ]); } @@ -108,7 +108,7 @@ public function testPushWithPriority(): void 'queue' => 'queue', $this->field('payload') => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), 'priority' => 'high', - 'available_at' => '1703859316', + 'available_at' => 1703859316, ]); } @@ -124,7 +124,7 @@ public function testPushAndPopWithPriority(): void 'queue' => 'queue', $this->field('payload') => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), 'priority' => 'low', - 'available_at' => '1703859316', + 'available_at' => 1703859316, ]); $result = $handler->setPriority('high')->push('queue', 'success', ['key2' => 'value2']); @@ -134,7 +134,7 @@ public function testPushAndPopWithPriority(): void 'queue' => 'queue', $this->field('payload') => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), 'priority' => 'high', - 'available_at' => '1703859316', + 'available_at' => 1703859316, ]); $result = $handler->pop('queue', ['high', 'low']); @@ -268,7 +268,7 @@ public function testFailedAndKeepJob(): void 'id' => 2, 'connection' => 'database', 'queue' => 'queue1', - 'failed_at' => '1703859316', + 'failed_at' => 1703859316, ]); } From bf855c72980d3f29f36a675fd5c7627b04bfa9f2 Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 19 Sep 2024 18:19:24 +0200 Subject: [PATCH 07/11] fix sqlsrv tests --- tests/_support/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/_support/TestCase.php b/tests/_support/TestCase.php index 2718d9d..e6d96c1 100644 --- a/tests/_support/TestCase.php +++ b/tests/_support/TestCase.php @@ -55,7 +55,7 @@ public function seeInDatabaseExtended(string $table, array $where): void public function field(string $name): string { if ($this->db->DBDriver === 'SQLSRV') { - return "CONVERT(VARCHAR, {$name})"; + return "CONVERT(VARCHAR(MAX), {$name})"; } return $name; From 259837446d343db7b8b954e4bbd57f1a56f317a4 Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 19 Sep 2024 18:19:35 +0200 Subject: [PATCH 08/11] update workflow --- .github/workflows/phpunit.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index b969c5a..afbbbd2 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -25,6 +25,12 @@ concurrency: permissions: contents: read +env: + NLS_LANG: 'AMERICAN_AMERICA.UTF8' + NLS_DATE_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + NLS_TIMESTAMP_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + NLS_TIMESTAMP_TZ_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + jobs: main: name: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} From 1952bcb52d953c367f5b49866e848ecf3493ed7a Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 20 Sep 2024 07:28:13 +0200 Subject: [PATCH 09/11] update skipLocked() method for OCI8 --- src/Models/QueueJobModel.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index b6df36c..9523684 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -100,6 +100,10 @@ private function skipLocked(string $sql): string return str_replace('WHERE', $replace, $sql); } + if ($this->db->DBDriver === 'OCI8') { + $sql = "SELECT * FROM ({$sql}) subquery"; + } + return $sql .= ' FOR UPDATE SKIP LOCKED'; } From ca8e0b9aba70bea507a6b3ef29a951cffce63bae Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 20 Sep 2024 08:12:44 +0200 Subject: [PATCH 10/11] update skipLocked() method for OCI8 --- src/Models/QueueJobModel.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index 9523684..adf6742 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -101,7 +101,8 @@ private function skipLocked(string $sql): string } if ($this->db->DBDriver === 'OCI8') { - $sql = "SELECT * FROM ({$sql}) subquery"; + $sql = preg_replace('/ OFFSET .*/', '', $sql); + $sql = "SELECT * FROM ({$sql}) WHERE ROWNUM = 1"; } return $sql .= ' FOR UPDATE SKIP LOCKED'; From 5e2f5f7a71436d110b4b446585b33fbd807f2c1a Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 20 Sep 2024 20:12:25 +0200 Subject: [PATCH 11/11] update tests --- src/Models/QueueJobModel.php | 9 +++++---- tests/Models/QueueJobModelTest.php | 6 ++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index adf6742..ed7d26f 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -101,11 +101,12 @@ private function skipLocked(string $sql): string } if ($this->db->DBDriver === 'OCI8') { - $sql = preg_replace('/ OFFSET .*/', '', $sql); - $sql = "SELECT * FROM ({$sql}) WHERE ROWNUM = 1"; + $sql = str_replace('SELECT *', 'SELECT "id"', $sql); + // prepare final query + $sql = sprintf('SELECT * FROM "%s" WHERE "id" = (%s)', $this->db->prefixTable($this->table), $sql); } - return $sql .= ' FOR UPDATE SKIP LOCKED'; + return $sql . ' FOR UPDATE SKIP LOCKED'; } /** @@ -118,7 +119,7 @@ private function setPriority(BaseBuilder $builder, array $priority): BaseBuilder if ($priority !== ['default']) { if ($this->db->DBDriver !== 'MySQLi') { $builder->orderBy( - 'CASE priority ' + sprintf('CASE %s ', $this->db->protectIdentifiers('priority')) . implode( ' ', array_map(static fn ($value, $key) => "WHEN '{$value}' THEN {$key}", $priority, array_keys($priority)) diff --git a/tests/Models/QueueJobModelTest.php b/tests/Models/QueueJobModelTest.php index 01c8532..cf829d3 100644 --- a/tests/Models/QueueJobModelTest.php +++ b/tests/Models/QueueJobModelTest.php @@ -41,11 +41,9 @@ public function testSkipLocked(): void if ($model->db->DBDriver === 'SQLite3') { $this->assertSame($sql, $result); } elseif ($model->db->DBDriver === 'SQLSRV') { - $expected = 'SELECT * FROM queue_jobs WITH (ROWLOCK,UPDLOCK,READPAST) WHERE queue = "test" AND status = 0 AND available_at < 123456 LIMIT 1'; - $this->assertSame($expected, $result); + $this->assertStringContainsString('WITH (ROWLOCK,UPDLOCK,READPAST) WHERE', $result); } else { - $expected = 'SELECT * FROM queue_jobs WHERE queue = "test" AND status = 0 AND available_at < 123456 LIMIT 1 FOR UPDATE SKIP LOCKED'; - $this->assertSame($expected, $result); + $this->assertStringContainsString('FOR UPDATE SKIP LOCKED', $result); } }