Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mabar committed Jul 16, 2024
1 parent 597affd commit 964f15f
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 83 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"authors": [],
"require": {
"php": "7.4 - 8.3",
"orisai/exceptions": "^1.0.0"
"orisai/exceptions": "^1.0.0",
"symfony/polyfill-php80": "^1.30"
},
"require-dev": {
"brianium/paratest": "^6.3.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@ public function analyse(): array
}

/**
* @return list<array<string, mixed>>
* @return list<array{
* TABLE_SCHEMA: string,
* TABLE_NAME: string,
* COLUMN_NAME: string,
* COLUMN_TYPE: string,
* AUTO_INCREMENT: int,
* }>
*/
public function getRecords(): array
{
Expand Down Expand Up @@ -87,7 +93,7 @@ public function getRecords(): array
HAVING PERCENTAGE_USED >= $threshold
ORDER BY c.TABLE_SCHEMA, c.TABLE_NAME, c.COLUMN_NAME;
SQL,
);
);
}

}
136 changes: 80 additions & 56 deletions src/Auditor/BoolLikeColumn/BoolLikeColumnMysqlAuditor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

use Orisai\DbAudit\Report\ColumnViolationSource;
use Orisai\DbAudit\Report\Violation;
use function assert;
use function PHPUnit\Framework\assertNull;
use function var_dump;
use function str_contains;

final class BoolLikeColumnMysqlAuditor extends BoolLikeColumnAuditor
{
Expand All @@ -22,7 +20,11 @@ public function analyse(): array
$this->cleanup();
}

assertNull($checks);
$groupedChecks = [];
foreach ($checks as $check) {
$groupedChecks[$check['TABLE_SCHEMA']][$check['TABLE_NAME']][$check['COLUMN_NAME']][] = $check['CHECK_CLAUSE'];
}

$violations = [];
foreach ($records as $record) {
$source = new ColumnViolationSource(
Expand All @@ -43,18 +45,32 @@ public function analyse(): array
);
}

//TODO - check může mít i když jde o int
//TODO - opravit název constraint
$violations[] = new Violation(
self::getKey(),
'Column '
. $source->toString()
. " contains only 0 and 1 but does not define constraint 'check(0, 1)'.",
$source,
);
$columnChecks = $groupedChecks[$record['TABLE_SCHEMA']][$record['TABLE_NAME']][$record['COLUMN_NAME']] ?? [];

$hasBooleanCheck = false;
foreach ($columnChecks as $columnCheck) {
if (
str_contains($columnCheck, "`{$record['COLUMN_NAME']}` in (0,1)")
|| str_contains($columnCheck, "`{$record['COLUMN_NAME']}` in (1,0)")
) {
$hasBooleanCheck = true;

break; // No need to check others
}
}

if (!$hasBooleanCheck) {
$violations[] = new Violation(
self::getKey(),
'Column '
. $source->toString()
. " contains only 0 and 1 but the table does not define CHECK ( `{$record['COLUMN_NAME']}` IN (0, 1)).",
$source,
);
}
}

return $records;
return $violations;
}

private function createProcedure(): void
Expand All @@ -69,8 +85,10 @@ private function createProcedure(): void
DECLARE fetched_column_name VARCHAR(64) CHARACTER SET utf8mb4;
DECLARE fetched_column_type VARCHAR(64) CHARACTER SET utf8mb4;
DECLARE fetched_data_type VARCHAR(64) CHARACTER SET utf8mb4;
DECLARE done INT DEFAULT FALSE;
DECLARE non_bool_count INT DEFAULT 0;
DECLARE non_bool_count TINYINT;
DECLARE done TINYINT DEFAULT 0;
-- Declare cursor to iterate over integer columns
DECLARE cur CURSOR FOR
Expand All @@ -84,12 +102,12 @@ private function createProcedure(): void
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- Create temporary table for results
CREATE TEMPORARY TABLE IF NOT EXISTS OrisaiDbAudit_bool_like_columns (
TABLE_SCHEMA VARCHAR(64) CHARACTER SET utf8mb4,
TABLE_NAME VARCHAR(64) CHARACTER SET utf8mb4,
COLUMN_NAME VARCHAR(64) CHARACTER SET utf8mb4,
COLUMN_TYPE VARCHAR(64) CHARACTER SET utf8mb4,
DATA_TYPE VARCHAR(64) CHARACTER SET utf8mb4
CREATE TEMPORARY TABLE OrisaiDbAudit_bool_like_columns (
TABLE_SCHEMA VARCHAR(64) CHARACTER SET utf8mb4 NOT NULL,
TABLE_NAME VARCHAR(64) CHARACTER SET utf8mb4 NOT NULL,
COLUMN_NAME VARCHAR(64) CHARACTER SET utf8mb4 NOT NULL,
COLUMN_TYPE VARCHAR(64) CHARACTER SET utf8mb4 NOT NULL,
DATA_TYPE VARCHAR(64) CHARACTER SET utf8mb4 NOT NULL
);
OPEN cur;
Expand Down Expand Up @@ -148,7 +166,13 @@ private function cleanup(): void
}

/**
* @return list<array<mixed>>
* @return list<array{
* TABLE_SCHEMA: string,
* TABLE_NAME: string,
* COLUMN_NAME: string,
* COLUMN_TYPE: string,
* DATA_TYPE: string,
* }>
*/
private function getRecords(): array
{
Expand All @@ -163,49 +187,49 @@ private function getRecords(): array
);
}

/**
* @return list<array{
* TABLE_SCHEMA: string,
* TABLE_NAME: string,
* COLUMN_NAME: string,
* CHECK_CLAUSE: string,
* }>
*/
private function getChecks(): array
{
//TODO - drop
//return $this->dbal->query('SELECT * FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS');
//return $this->dbal->query("SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = 'bool_like_column'");

return $this->dbal->query(
/** @lang MySQL */
<<<'SQL'
SELECT
c.TABLE_SCHEMA,
c.TABLE_NAME,
c.COLUMN_NAME,
c.COLUMN_TYPE,
c.DATA_TYPE,
cc.CHECK_CLAUSE
c.TABLE_SCHEMA,
c.TABLE_NAME,
c.COLUMN_NAME,
cc.CHECK_CLAUSE
FROM
OrisaiDbAudit_bool_like_columns c
OrisaiDbAudit_bool_like_columns c
LEFT JOIN (
SELECT
tc.TABLE_SCHEMA,
tc.TABLE_NAME,
kcu.COLUMN_NAME,
cc.CHECK_CLAUSE
FROM
INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc
ON tc.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
AND tc.CONSTRAINT_SCHEMA = cc.CONSTRAINT_SCHEMA
JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
ON kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
AND kcu.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA
WHERE
tc.CONSTRAINT_TYPE = 'CHECK'
SELECT
tc.TABLE_SCHEMA,
tc.TABLE_NAME,
cc.CHECK_CLAUSE
FROM
INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS cc
ON tc.CONSTRAINT_NAME = cc.CONSTRAINT_NAME
AND tc.CONSTRAINT_SCHEMA = cc.CONSTRAINT_SCHEMA
WHERE
tc.CONSTRAINT_TYPE = 'CHECK'
) cc
ON c.TABLE_SCHEMA = cc.TABLE_SCHEMA
AND c.TABLE_NAME = cc.TABLE_NAME
AND c.COLUMN_NAME = cc.COLUMN_NAME
ON c.TABLE_SCHEMA = cc.TABLE_SCHEMA
AND c.TABLE_NAME = cc.TABLE_NAME
AND cc.CHECK_CLAUSE LIKE CONCAT('%(`', c.COLUMN_NAME, '` in%')
WHERE
cc.CHECK_CLAUSE IS NOT NULL
ORDER BY
c.TABLE_SCHEMA,
c.TABLE_NAME,
c.COLUMN_NAME;
SQL
c.TABLE_SCHEMA,
c.TABLE_NAME,
c.COLUMN_NAME;
SQL,
);
}

Expand Down
25 changes: 15 additions & 10 deletions src/Auditor/EmptyColumn/EmptyColumnMysqlAuditor.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,15 @@ private function createProcedure(): void
<<<'SQL'
CREATE PROCEDURE OrisaiDbAudit_FindEmptyColumns()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE fetched_table_schema VARCHAR(64) CHARACTER SET utf8mb4;
DECLARE fetched_table_name VARCHAR(64) CHARACTER SET utf8mb4;
DECLARE fetched_column_name VARCHAR(64) CHARACTER SET utf8mb4;
DECLARE col_is_empty INT DEFAULT 1;
DECLARE table_is_empty INT DEFAULT 1;
DECLARE table_is_empty TINYINT;
DECLARE col_is_empty TINYINT;
DECLARE done TINYINT DEFAULT 0;
DECLARE cur CURSOR FOR
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
FROM information_schema.columns
Expand All @@ -61,10 +64,10 @@ private function createProcedure(): void
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
CREATE TEMPORARY TABLE IF NOT EXISTS OrisaiDbAudit_empty_columns (
TABLE_SCHEMA VARCHAR(64) NOT NULL,
TABLE_NAME VARCHAR(64) NOT NULL,
COLUMN_NAME VARCHAR(64) NOT NULL
CREATE TEMPORARY TABLE OrisaiDbAudit_empty_columns (
TABLE_SCHEMA VARCHAR(64) CHARACTER SET utf8mb4 NOT NULL,
TABLE_NAME VARCHAR(64) CHARACTER SET utf8mb4 NOT NULL,
COLUMN_NAME VARCHAR(64) CHARACTER SET utf8mb4 NOT NULL
);
OPEN cur;
Expand All @@ -86,8 +89,6 @@ private function createProcedure(): void
ITERATE read_loop;
END IF;
SET @col_is_empty = 1;
-- Construct the query to check if the column contains only NULL or empty strings
SET @sql_query = CONCAT('SELECT IF(COUNT(*) = 0, 1, 0) INTO @col_is_empty FROM `', fetched_table_schema, '`.`', fetched_table_name,
'` WHERE `', fetched_column_name, '` IS NOT NULL AND `', fetched_column_name, '` != "" LIMIT 1');
Expand Down Expand Up @@ -124,7 +125,11 @@ private function cleanup(): void
}

/**
* @return list<array<string, mixed>>
* @return list<array{
* TABLE_SCHEMA: string,
* TABLE_NAME: string,
* COLUMN_NAME: string,
* }>
*/
private function getRecords(): array
{
Expand Down
7 changes: 5 additions & 2 deletions src/Auditor/EmptyTable/EmptyTableMysqlAuditor.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ public function analyse(): array
}

/**
* @return list<array<string, mixed>>
* @return list<array{
* TABLE_SCHEMA: string,
* TABLE_NAME: string,
* }>
*/
private function getRecords(): array
{
Expand All @@ -52,7 +55,7 @@ private function getRecords(): array
AND TABLE_ROWS = 0
AND TABLE_TYPE = 'BASE TABLE';
SQL,
);
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ public function analyse(): array
}

/**
* @return list<array<string, mixed>>
* @return list<array{
* TABLE_SCHEMA: string,
* TABLE_NAME: string,
* COLUMN_NAME: string,
* COLUMN_DEFAULT: string,
* COLUMN_TYPE: string,
* }>
*/
private function getRecords(): array
{
Expand All @@ -53,7 +59,7 @@ private function getRecords(): array
)
ORDER BY c.TABLE_SCHEMA, c.TABLE_NAME, c.COLUMN_NAME;
SQL,
);
);
}

}
10 changes: 4 additions & 6 deletions tests/Integration/Auditor/BoolLikeColumnMysqlAuditorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ public function test(DbalAdapter $dbal): void
SQL,
);

//TODO - kombinovaná constraint
$dbal->exec(
/** @lang MySQL */
<<<'SQL'
Expand All @@ -131,17 +132,14 @@ public function test(DbalAdapter $dbal): void
(0, 0, 0, 0)
SQL,
);
// TODO - drop
//self::assertNull($dbal->query('SELECT * FROM INFORMATION_SCHEMA.CHECK_CONSTRAINTS'));

$report = $auditor->analyse();
self::assertNull($report);
self::assertEquals($report, $auditor->analyse());
self::assertEquals([
new Violation(
$key,
"Column [bool_like_column][checks][int] (Column type: 'int') contains only 0 and 1 but is not defined as tinyint.",
(new ColumnViolationSource($db, null, 'no_checks', 'int'))
(new ColumnViolationSource($db, null, 'checks', 'int'))
->setColumnType('int'),
),
new Violation(
Expand All @@ -152,13 +150,13 @@ public function test(DbalAdapter $dbal): void
),
new Violation(
$key,
"Column [bool_like_column][no_checks][int] (Column type: 'int') contains only 0 and 1 but does not define constraint 'check(0, 1)'.",
"Column [bool_like_column][no_checks][int] (Column type: 'int') contains only 0 and 1 but the table does not define CHECK ( `int` IN (0, 1)).",

Check failure on line 153 in tests/Integration/Auditor/BoolLikeColumnMysqlAuditorTest.php

View workflow job for this annotation

GitHub Actions / Coding standard (ubuntu-latest, 8.3)

Line exceeds maximum limit of 150 characters, contains 159 characters.
(new ColumnViolationSource($db, null, 'no_checks', 'int'))
->setColumnType('int'),
),
new Violation(
$key,
"Column [bool_like_column][no_checks][tinyint] (Column type: 'tinyint unsigned') contains only 0 and 1 but does not define constraint 'check(0, 1)'.",
"Column [bool_like_column][no_checks][tinyint] (Column type: 'tinyint unsigned') contains only 0 and 1 but the table does not define CHECK ( `tinyint` IN (0, 1)).",

Check failure on line 159 in tests/Integration/Auditor/BoolLikeColumnMysqlAuditorTest.php

View workflow job for this annotation

GitHub Actions / Coding standard (ubuntu-latest, 8.3)

Line exceeds maximum limit of 150 characters, contains 180 characters.
(new ColumnViolationSource($db, null, 'no_checks', 'tinyint'))
->setColumnType('tinyint unsigned'),
),
Expand Down
5 changes: 1 addition & 4 deletions tests/Integration/Auditor/EmptyColumnMysqlAuditorTest.php
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
<?php declare(strict_types = 1);

namespace Integration\Auditor;
namespace Tests\Orisai\DbAudit\Integration\Auditor;

use Dibi\Connection as DibiConnection;
use Generator;
use Nextras\Dbal\Connection as NextrasConnection;
use Orisai\DbAudit\Auditor\EmptyColumn\EmptyColumnMysqlAuditor;
use Orisai\DbAudit\Auditor\EmptyTable\EmptyTableMysqlAuditor;
use Orisai\DbAudit\Dbal\DbalAdapter;
use Orisai\DbAudit\Dbal\DibiAdapter;
use Orisai\DbAudit\Dbal\NextrasAdapter;
use Orisai\DbAudit\Report\ColumnViolationSource;
use Orisai\DbAudit\Report\TableViolationSource;
use Orisai\DbAudit\Report\Violation;
use PHPUnit\Framework\TestCase;
use Tests\Orisai\DbAudit\Helper\MysqlConnectionConfig;
use Tests\Orisai\DbAudit\Helper\MysqlShortcuts;
use function PHPUnit\Framework\assertEquals;

final class EmptyColumnMysqlAuditorTest extends TestCase
{
Expand Down
Loading

0 comments on commit 964f15f

Please sign in to comment.