Skip to content

Commit

Permalink
Merge branch 'refs/heads/master' into 19855-file-validator-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
bizley committed May 30, 2024
2 parents 06fa521 + e1268d1 commit 7c5b213
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 33 deletions.
67 changes: 67 additions & 0 deletions .github/workflows/ci-mariadb.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
on:
- pull_request
- push

name: ci-mariadb

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
tests:
name: PHP ${{ matrix.php }}-mariadb-${{ matrix.mariadb }}
env:
extensions: curl, intl, pdo, pdo_mysql
XDEBUG_MODE: coverage, develop

runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
php: [7.4, 8.0, 8.1, 8.2, 8.3, 8.4]
mariadb:
- mariadb:10.4
- mariadb:latest

services:
mysql:
image: ${{ matrix.mariadb }}
env:
MARIADB_ROOT_PASSWORD: root
MARIADB_DATABASE: yiitest
ports:
- 3306:3306
options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=3

steps:
- name: Checkout.
uses: actions/checkout@v4

- name: Install PHP with extensions.
uses: shivammathur/setup-php@v2
with:
coverage: xdebug
extensions: ${{ env.EXTENSIONS }}
ini-values: date.timezone='UTC'
php-version: ${{ matrix.php }}
tools: composer:v2, pecl

- name: Install dependencies with composer
if: matrix.php != '8.4'
run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi

- name: Install dependencies with PHP 8.4.
if: matrix.php == '8.4'
run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ignore-platform-reqs --ansi

- name: Run MariaDB tests with PHPUnit and generate coverage.
run: vendor/bin/phpunit --group mysql --coverage-clover=coverage.xml --colors=always

- name: Upload coverage to Codecov.
if: matrix.php == '7.4'
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Yii Framework 2 Change Log
- Bug #19817: Add MySQL Query `addCheck()` and `dropCheck()` (@bobonov)
- Bug #20165: Adjust pretty name of closures for PHP 8.4 compatibility (@staabm)
- Bug #19855: Fixed `yii\validators\FileValidator` to not limit some of its rules only to array attribute (bizley)
- Enh: #20171: Support JSON columns for MariaDB 10.4 or higher (@terabytesoftw)

2.0.49.2 October 12, 2023
-------------------------
Expand Down
2 changes: 1 addition & 1 deletion framework/db/mysql/JsonExpressionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ public function build(ExpressionInterface $expression, array &$params = [])
$placeholder = static::PARAM_PREFIX . count($params);
$params[$placeholder] = Json::encode($value);

return "CAST($placeholder AS JSON)";
return $placeholder;
}
}
25 changes: 25 additions & 0 deletions framework/db/mysql/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,19 @@ protected function findColumns($table)
}
throw $e;
}


$jsonColumns = $this->getJsonColumns($table);

foreach ($columns as $info) {
if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_LOWER) {
$info = array_change_key_case($info, CASE_LOWER);
}

if (\in_array($info['field'], $jsonColumns, true)) {
$info['type'] = static::TYPE_JSON;
}

$column = $this->loadColumnSchema($info);
$table->columns[$column->name] = $column;
if ($column->isPrimaryKey) {
Expand Down Expand Up @@ -641,4 +650,20 @@ private function loadTableConstraints($tableName, $returnType)

return $result[$returnType];
}

private function getJsonColumns(TableSchema $table): array
{
$sql = $this->getCreateTableSql($table);
$result = [];

$regexp = '/json_valid\([\`"](.+)[\`"]\s*\)/mi';

if (\preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$result[] = $match[1];
}
}

return $result;
}
}
3 changes: 1 addition & 2 deletions framework/web/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,8 @@ public function bindActionParams($action, $params)
foreach ($method->getParameters() as $param) {
$name = $param->getName();
if (array_key_exists($name, $params)) {
$isValid = true;
$isValid = true;
$isArray = ($type = $param->getType()) instanceof \ReflectionNamedType && $type->getName() === 'array';

if ($isArray) {
$params[$name] = (array)$params[$name];
} elseif (is_array($params[$name])) {
Expand Down
20 changes: 10 additions & 10 deletions tests/framework/db/mysql/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -267,35 +267,35 @@ public function conditionProvider()
// json conditions
[
['=', 'jsoncol', new JsonExpression(['lang' => 'uk', 'country' => 'UA'])],
'[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => '{"lang":"uk","country":"UA"}'],
'[[jsoncol]] = :qp0', [':qp0' => '{"lang":"uk","country":"UA"}'],
],
[
['=', 'jsoncol', new JsonExpression([false])],
'[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => '[false]']
'[[jsoncol]] = :qp0', [':qp0' => '[false]']
],
'object with type. Type is ignored for MySQL' => [
['=', 'prices', new JsonExpression(['seeds' => 15, 'apples' => 25], 'jsonb')],
'[[prices]] = CAST(:qp0 AS JSON)', [':qp0' => '{"seeds":15,"apples":25}'],
'[[prices]] = :qp0', [':qp0' => '{"seeds":15,"apples":25}'],
],
'nested json' => [
['=', 'data', new JsonExpression(['user' => ['login' => 'silverfire', 'password' => 'c4ny0ur34d17?'], 'props' => ['mood' => 'good']])],
'[[data]] = CAST(:qp0 AS JSON)', [':qp0' => '{"user":{"login":"silverfire","password":"c4ny0ur34d17?"},"props":{"mood":"good"}}']
'[[data]] = :qp0', [':qp0' => '{"user":{"login":"silverfire","password":"c4ny0ur34d17?"},"props":{"mood":"good"}}']
],
'null value' => [
['=', 'jsoncol', new JsonExpression(null)],
'[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => 'null']
'[[jsoncol]] = :qp0', [':qp0' => 'null']
],
'null as array value' => [
['=', 'jsoncol', new JsonExpression([null])],
'[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => '[null]']
'[[jsoncol]] = :qp0', [':qp0' => '[null]']
],
'null as object value' => [
['=', 'jsoncol', new JsonExpression(['nil' => null])],
'[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => '{"nil":null}']
'[[jsoncol]] = :qp0', [':qp0' => '{"nil":null}']
],
'with object as value' => [
['=', 'jsoncol', new JsonExpression(new DynamicModel(['a' => 1, 'b' => 2]))],
'[[jsoncol]] = CAST(:qp0 AS JSON)', [':qp0' => '{"a":1,"b":2}']
'[[jsoncol]] = :qp0', [':qp0' => '{"a":1,"b":2}']
],
'query' => [
['=', 'jsoncol', new JsonExpression((new Query())->select('params')->from('user')->where(['id' => 1]))],
Expand All @@ -307,7 +307,7 @@ public function conditionProvider()
],
'nested and combined json expression' => [
['=', 'jsoncol', new JsonExpression(new JsonExpression(['a' => 1, 'b' => 2, 'd' => new JsonExpression(['e' => 3])]))],
"[[jsoncol]] = CAST(:qp0 AS JSON)", [':qp0' => '{"a":1,"b":2,"d":{"e":3}}']
"[[jsoncol]] = :qp0", [':qp0' => '{"a":1,"b":2,"d":{"e":3}}']
],
'search by property in JSON column (issue #15838)' => [
['=', new Expression("(jsoncol->>'$.someKey')"), '42'],
Expand All @@ -328,7 +328,7 @@ public function updateProvider()
[
'id' => 1,
],
$this->replaceQuotes('UPDATE [[profile]] SET [[description]]=CAST(:qp0 AS JSON) WHERE [[id]]=:qp1'),
$this->replaceQuotes('UPDATE [[profile]] SET [[description]]=:qp0 WHERE [[id]]=:qp1'),
[
':qp0' => '{"abc":"def","0":123,"1":null}',
':qp1' => 1,
Expand Down
61 changes: 41 additions & 20 deletions tests/framework/db/mysql/SchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,87 +146,108 @@ public function testAlternativeDisplayOfDefaultCurrentTimestampAsNullInMariaDB()

public function getExpectedColumns()
{
$version = $this->getConnection()->getSchema()->getServerVersion();
$version = $this->getConnection(false)->getServerVersion();

$columns = array_merge(
parent::getExpectedColumns(),
[
'int_col' => [
'type' => 'integer',
'dbType' => \version_compare($version, '8.0.17', '>') ? 'int' : 'int(11)',
'dbType' => 'int(11)',
'phpType' => 'integer',
'allowNull' => false,
'autoIncrement' => false,
'enumValues' => null,
'size' => \version_compare($version, '8.0.17', '>') ? null : 11,
'precision' => \version_compare($version, '8.0.17', '>') ? null : 11,
'size' => 11,
'precision' => 11,
'scale' => null,
'defaultValue' => null,
],
'int_col2' => [
'type' => 'integer',
'dbType' => \version_compare($version, '8.0.17', '>') ? 'int' : 'int(11)',
'dbType' => 'int(11)',
'phpType' => 'integer',
'allowNull' => true,
'autoIncrement' => false,
'enumValues' => null,
'size' => \version_compare($version, '8.0.17', '>') ? null : 11,
'precision' => \version_compare($version, '8.0.17', '>') ? null : 11,
'size' => 11,
'precision' => 11,
'scale' => null,
'defaultValue' => 1,
],
'int_col3' => [
'type' => 'integer',
'dbType' => \version_compare($version, '8.0.17', '>') ? 'int unsigned' : 'int(11) unsigned',
'dbType' => 'int(11) unsigned',
'phpType' => 'integer',
'allowNull' => true,
'autoIncrement' => false,
'enumValues' => null,
'size' => \version_compare($version, '8.0.17', '>') ? null : 11,
'precision' => \version_compare($version, '8.0.17', '>') ? null : 11,
'size' => 11,
'precision' => 11,
'scale' => null,
'defaultValue' => 1,
],
'tinyint_col' => [
'type' => 'tinyint',
'dbType' => \version_compare($version, '8.0.17', '>') ? 'tinyint' : 'tinyint(3)',
'dbType' => 'tinyint(3)',
'phpType' => 'integer',
'allowNull' => true,
'autoIncrement' => false,
'enumValues' => null,
'size' => \version_compare($version, '8.0.17', '>') ? null : 3,
'precision' => \version_compare($version, '8.0.17', '>') ? null : 3,
'size' => 3,
'precision' => 3,
'scale' => null,
'defaultValue' => 1,
],
'smallint_col' => [
'type' => 'smallint',
'dbType' => \version_compare($version, '8.0.17', '>') ? 'smallint' : 'smallint(1)',
'dbType' => 'smallint(1)',
'phpType' => 'integer',
'allowNull' => true,
'autoIncrement' => false,
'enumValues' => null,
'size' => \version_compare($version, '8.0.17', '>') ? null : 1,
'precision' => \version_compare($version, '8.0.17', '>') ? null : 1,
'size' => 1,
'precision' => 1,
'scale' => null,
'defaultValue' => 1,
],
'bigint_col' => [
'type' => 'bigint',
'dbType' => \version_compare($version, '8.0.17', '>') ? 'bigint unsigned' : 'bigint(20) unsigned',
'dbType' => 'bigint(20) unsigned',
'phpType' => 'string',
'allowNull' => true,
'autoIncrement' => false,
'enumValues' => null,
'size' => \version_compare($version, '8.0.17', '>') ? null : 20,
'precision' => \version_compare($version, '8.0.17', '>') ? null : 20,
'size' => 20,
'precision' => 20,
'scale' => null,
'defaultValue' => null,
],
]
);

if (version_compare($version, '5.7', '<')) {
if (\version_compare($version, '8.0.17', '>') && \stripos($version, 'MariaDb') === false) {
$columns['int_col']['dbType'] = 'int';
$columns['int_col']['size'] = null;
$columns['int_col']['precision'] = null;
$columns['int_col2']['dbType'] = 'int';
$columns['int_col2']['size'] = null;
$columns['int_col2']['precision'] = null;
$columns['int_col3']['dbType'] = 'int unsigned';
$columns['int_col3']['size'] = null;
$columns['int_col3']['precision'] = null;
$columns['tinyint_col']['dbType'] = 'tinyint';
$columns['tinyint_col']['size'] = null;
$columns['tinyint_col']['precision'] = null;
$columns['smallint_col']['dbType'] = 'smallint';
$columns['smallint_col']['size'] = null;
$columns['smallint_col']['precision'] = null;
$columns['bigint_col']['dbType'] = 'bigint unsigned';
$columns['bigint_col']['size'] = null;
$columns['bigint_col']['precision'] = null;
}

if (version_compare($version, '5.7', '<') && \stripos($version, 'MariaDb') === false) {
$columns['int_col3']['phpType'] = 'string';
$columns['json_col']['type'] = 'text';
$columns['json_col']['dbType'] = 'longtext';
Expand Down
Loading

0 comments on commit 7c5b213

Please sign in to comment.