diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index f2b9a10..a63634b 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -18,12 +18,95 @@ on: - 'phpunit*' - '.github/workflows/phpunit.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +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 }} Unit Tests + name: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} runs-on: ubuntu-22.04 + 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:2022-latest + env: + MSSQL_SA_PASSWORD: 1Secure*Password1 + ACCEPT_EULA: Y + MSSQL_PID: Developer + ports: + - 1433:1433 + 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:21 + 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 +117,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 @@ -48,7 +146,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 }} @@ -74,6 +172,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 +185,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/Database/Migrations/2024-12-27-110712_ChangePayloadFieldTypeInSqlsrv.php b/src/Database/Migrations/2024-12-27-110712_ChangePayloadFieldTypeInSqlsrv.php new file mode 100644 index 0000000..c77c4e0 --- /dev/null +++ b/src/Database/Migrations/2024-12-27-110712_ChangePayloadFieldTypeInSqlsrv.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Queue\Database\Migrations; + +use CodeIgniter\Database\BaseConnection; +use CodeIgniter\Database\Migration; + +/** + * @property BaseConnection $db + */ +class ChangePayloadFieldTypeInSqlsrv extends Migration +{ + public function up(): void + { + if ($this->db->DBDriver === 'SQLSRV') { + $fields = [ + 'payload' => [ + 'name' => 'payload', + 'type' => 'NVARCHAR', + 'constraint' => 'MAX', + 'null' => false, + ], + ]; + $this->forge->modifyColumn('queue_jobs', $fields); + } + } + + public function down(): void + { + if ($this->db->DBDriver === 'SQLSRV') { + $fields = [ + 'payload' => [ + 'name' => 'payload', + 'type' => 'TEXT', // already deprecated + 'null' => false, + ], + ]; + $this->forge->modifyColumn('queue_jobs', $fields); + } + } +} diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index 121c8fb..ed7d26f 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -100,7 +100,13 @@ private function skipLocked(string $sql): string return str_replace('WHERE', $replace, $sql); } - return $sql .= ' FOR UPDATE SKIP LOCKED'; + if ($this->db->DBDriver === 'OCI8') { + $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'; } /** @@ -111,9 +117,9 @@ 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 ' + 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/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index 33fd4af..d62d015 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -89,7 +89,7 @@ public function testPush(): void $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), - 'available_at' => '1703859316', + 'available_at' => 1703859316, ]); } @@ -108,7 +108,7 @@ public function testPushWithPriority(): void 'queue' => 'queue', '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', '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', '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, ]); } @@ -344,7 +344,7 @@ public function testRetry(): void $handler = new DatabaseHandler($this->config); $count = $handler->retry(1, 'queue1'); - $this->assertSame($count, 1); + $this->assertSame(1, $count); $this->seeInDatabase('queue_jobs', [ 'id' => 3, 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); } } diff --git a/tests/PredisHandlerTest.php b/tests/PredisHandlerTest.php index 4470295..51f1492 100644 --- a/tests/PredisHandlerTest.php +++ b/tests/PredisHandlerTest.php @@ -288,7 +288,7 @@ public function testRetry(): void $handler = new PredisHandler($this->config); $count = $handler->retry(1, 'queue1'); - $this->assertSame($count, 1); + $this->assertSame(1, $count); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(2, $predis->zcard('queues:queue1:default')); diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index e691197..96ae0d6 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -254,7 +254,7 @@ public function testRetry(): void $handler = new RedisHandler($this->config); $count = $handler->retry(1, 'queue1'); - $this->assertSame($count, 1); + $this->assertSame(1, $count); $redis = self::getPrivateProperty($handler, 'redis'); $this->assertSame(2, $redis->zCard('queues:queue1:default')); 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; + } +}