diff --git a/src/Commands/DBAuditCommand.php b/src/Commands/DBAuditCommand.php index 8e70b43..6cd8d73 100644 --- a/src/Commands/DBAuditCommand.php +++ b/src/Commands/DBAuditCommand.php @@ -3,9 +3,9 @@ namespace Vcian\LaravelDBAuditor\Commands; use Illuminate\Console\Command; -use League\Flysystem\Config; use Vcian\LaravelDBAuditor\Constants\Constant; +use function Laravel\Prompts\search; use function Laravel\Prompts\select; class DBAuditCommand extends Command @@ -29,14 +29,13 @@ class DBAuditCommand extends Command */ public function handle(): void { - $commands = match (connection_driver()) { - Constant::SQLITE_DB => config('audit.sqlite_commands'), - Constant::MYSQL_DB => config('audit.mysql_commands'), - }; - $commandSelect = select( label: 'Please Select feature which would you like to do', - options: $commands, + options: match (connection_driver()) { + Constant::SQLITE_DB => config('audit.sqlite_commands'), + Constant::MYSQL_DB => config('audit.mysql_commands'), + Constant::POSTGRESQL_DB => config('audit.pgsql_commands'), + }, default: Constant::SUMMARY_COMMAND ); diff --git a/src/Commands/DBConstraintCommand.php b/src/Commands/DBConstraintCommand.php index 2e4fc9d..cf60b69 100644 --- a/src/Commands/DBConstraintCommand.php +++ b/src/Commands/DBConstraintCommand.php @@ -6,6 +6,7 @@ use Vcian\LaravelDBAuditor\Constants\Constant; use Vcian\LaravelDBAuditor\Traits\Audit; +use Vcian\LaravelDBAuditor\Traits\DBConstraint; use Vcian\LaravelDBAuditor\Traits\DisplayTable; use function Laravel\Prompts\confirm; use function Laravel\Prompts\select; @@ -14,7 +15,7 @@ class DBConstraintCommand extends Command { - use DisplayTable, Audit; + use Audit, DisplayTable, DBConstraint; /** * @var bool */ @@ -40,8 +41,21 @@ class DBConstraintCommand extends Command */ public function handle(): int|string { - $this->connection = connection_driver(); - $tableList = $this->getTableList(); + return match (connection_driver()) { + 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), + default => $this->mysql(), + }; + } + + /** + * MySQL Constraint + * @return int + */ + public function mysql() + { + $tableList = collect($this->getTableList()) + ->diff(config('audit.skip_tables'))->values()->toArray(); $tableName = select( label: __('Lang::messages.constraint.question.table_selection'), @@ -60,10 +74,10 @@ public function handle(): int|string if (empty($noConstraintFields)) { $continue = Constant::STATUS_FALSE; } else { - if (confirm(label: __('Lang::messages.constraint.question.continue'))) { - + if (confirm(label: __('Lang::messages.constraint.question.add_constraint'))) { $this->skip = Constant::STATUS_FALSE; $constraintList = $this->getConstraintList($tableName, $noConstraintFields); + $selectConstrain = select( label: __('Lang::messages.constraint.question.constraint_selection'), options: $constraintList, @@ -83,6 +97,35 @@ public function handle(): int|string return self::SUCCESS; } + /** + * PostgreSQL Constraint + * @return int + */ + public function pgsql() + { + $tableList = collect($this->getTableList()) + ->diff(config('audit.skip_tables'))->values()->toArray(); + $tableName = select( + label: __('Lang::messages.constraint.question.table_selection'), + options: $tableList, + default: reset($tableList) + ); + + $this->display($tableName); + + if ($tableName) { + + $continue = Constant::STATUS_TRUE; + + do { + if ($this->getNoConstraintFields($tableName)) { + $continue = Constant::STATUS_FALSE; + }; + } while ($continue === Constant::STATUS_TRUE); + } + + return self::SUCCESS; + } /** * Display error messages diff --git a/src/Commands/DBStandardCommand.php b/src/Commands/DBStandardCommand.php index 36f987f..aebd55d 100644 --- a/src/Commands/DBStandardCommand.php +++ b/src/Commands/DBStandardCommand.php @@ -91,8 +91,8 @@ public function tableReport(string $tableName, string $connection) if (!$tableStatus) { return render(view('DBAuditor::error_message', ['message' => 'No Table Found'])); - } else { - render(view('DBAuditor::'.$connection.'.table_standard', ['tableStatus' => $tableStatus])); } + + render(view('DBAuditor::'.$connection.'.table_standard', ['tableStatus' => $tableStatus])); } } diff --git a/src/Commands/DBSummaryCommand.php b/src/Commands/DBSummaryCommand.php index b3df694..ab039c6 100644 --- a/src/Commands/DBSummaryCommand.php +++ b/src/Commands/DBSummaryCommand.php @@ -33,6 +33,7 @@ public function handle() { return match (connection_driver()) { 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), default => $this->mysql(), }; } @@ -72,7 +73,7 @@ public function sqlite(): int public function mysql(): int { $this->table( - ['Database Name', 'Size', 'Table Count', 'Engin', 'Character Set'], + ['Database Name', 'Size (MB)', 'Table Count', 'DB Version', 'Character Set'], [[ database_name(), $this->getDatabaseSize(), @@ -84,4 +85,19 @@ public function mysql(): int return self::SUCCESS; } + + public function pgsql(): int + { + $this->table( + ['Database Name', 'Size (MB)', 'Table Count', 'Character Set'], + [[ + database_name(), + $this->getDatabaseSize(), + count($this->getTableList()), + $this->getCharacterSetName(), + ]] + ); + + return self::SUCCESS; + } } diff --git a/src/Config/audit.php b/src/Config/audit.php index c919d1c..b439ac0 100644 --- a/src/Config/audit.php +++ b/src/Config/audit.php @@ -8,12 +8,11 @@ 'sqlite_sequence', 'migrations', 'migrations_history', - 'sessions', 'password_resets', 'failed_jobs', 'jobs', 'queue_job', - 'queue_failed_jobs', + 'queue_failed_jobs' ], 'mysql_commands' => [ Constant::STANDARD_COMMAND, @@ -26,5 +25,11 @@ Constant::CONSTRAINT_COMMAND, Constant::SUMMARY_COMMAND, Constant::TRACK_COMMAND, + ], + 'pgsql_commands' => [ + Constant::STANDARD_COMMAND, + Constant::CONSTRAINT_COMMAND, + Constant::SUMMARY_COMMAND, + Constant::TRACK_COMMAND, ] ]; diff --git a/src/Constants/Constant.php b/src/Constants/Constant.php index 1327060..3efa562 100644 --- a/src/Constants/Constant.php +++ b/src/Constants/Constant.php @@ -36,6 +36,8 @@ class Constant public const SQLITE_DB = 'sqlite'; + public const POSTGRESQL_DB = 'pgsql'; + public const UNIQUE_RULES = 'unique'; public const INDEX_FILE_NAME = 'update_table_index.php'; diff --git a/src/Lang/en/messages.php b/src/Lang/en/messages.php index e7d84c7..c62ef85 100644 --- a/src/Lang/en/messages.php +++ b/src/Lang/en/messages.php @@ -18,7 +18,7 @@ 'constraint' => [ 'question' => [ 'table_selection' => 'Which table would you like to audit?', - 'continue' => 'Do you want add more constraint?', + 'add_constraint' => 'Do you want add more constraint?', 'constraint_selection' => 'Please select a constraint which you want to add.', 'field_selection' => 'Please select a field to add constraint', 'foreign_table' => 'Please add foreign table name.', diff --git a/src/Queries/DatabaseCharacterSetClass.php b/src/Queries/DatabaseCharacterSetClass.php index 5aa76f2..0ed9ec0 100644 --- a/src/Queries/DatabaseCharacterSetClass.php +++ b/src/Queries/DatabaseCharacterSetClass.php @@ -19,6 +19,7 @@ public function __invoke(): string { return match ($this->driver) { 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), default => $this->mysql(), }; } @@ -45,4 +46,11 @@ public function select($query): array { return DB::select($query); } + + public function pgsql(): string + { + $result = $this->select("SELECT pg_encoding_to_char(encoding) AS character_set FROM pg_database WHERE datname = current_database();"); + + return reset($result)?->character_set ?? Constant::DASH; + } } diff --git a/src/Queries/DatabaseConstraintClass.php b/src/Queries/DatabaseConstraintClass.php index 20e1007..0815124 100644 --- a/src/Queries/DatabaseConstraintClass.php +++ b/src/Queries/DatabaseConstraintClass.php @@ -5,13 +5,14 @@ use Illuminate\Support\Facades\DB; use Vcian\LaravelDBAuditor\Constants\Constant; use Vcian\LaravelDBAuditor\Traits\Audit; +use Vcian\LaravelDBAuditor\Traits\DBConstraint; class DatabaseConstraintClass { use Audit; protected string $driver, $database; - public function __construct(protected string $table) + public function __construct(protected string $table, protected string $fields = '') { $this->driver = connection_driver(); $this->database = database_name(); @@ -21,10 +22,20 @@ public function __invoke(): array { return match ($this->driver) { 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), default => $this->mysql(), }; } + /** + * @param $query + * @return array + */ + public function select($query): array + { + return DB::select($query); + } + /** * Sqlite query. * @@ -62,12 +73,75 @@ public function mysql(): array ]; } - /** - * @param $query - * @return array - */ - public function select($query): array + + public function pgsql(): array { - return DB::select($query); + return [ + 'primary' => $this->getPgsqlConstraintField($this->table, Constant::CONSTRAINT_PRIMARY_KEY), + 'unique' => $this->getPgsqlConstraintField($this->table, Constant::CONSTRAINT_UNIQUE_KEY), + 'foreign' => $this->getPgsqlConstraintField($this->table, Constant::CONSTRAINT_FOREIGN_KEY), + 'index' => $this->getPgsqlConstraintField($this->table, Constant::CONSTRAINT_INDEX_KEY), + ]; + } + + public function getPgsqlConstraintField(string $tableName, string $input): array + { + if ($input === Constant::CONSTRAINT_FOREIGN_KEY){ + return collect(DB::select("SELECT + conname AS constraint_name, + a.attname AS column_name, + confrelid::regclass AS foreign_table, + af.attname AS foreign_column + FROM + pg_constraint AS c + JOIN + pg_class AS t ON c.conrelid = t.oid + JOIN + pg_attribute AS a ON a.attnum = ANY(c.conkey) AND a.attrelid = t.oid + JOIN + pg_attribute AS af ON af.attnum = ANY(c.confkey) AND af.attrelid = c.confrelid + WHERE + t.relname = ? + AND c.contype = 'f'", [$tableName])) + ->flatten() + ->toArray(); + } + + if ($input === Constant::CONSTRAINT_INDEX_KEY) { + return collect(DB::select("SELECT + i.relname AS index_name, + a.attname AS column_name + FROM + pg_class AS t + JOIN + pg_index AS ix ON t.oid = ix.indrelid + JOIN + pg_class AS i ON i.oid = ix.indexrelid + JOIN + pg_attribute AS a ON a.attnum = ANY(ix.indkey) AND a.attrelid = t.oid + WHERE t.relname = ?",[$tableName])) + ->select('column_name') + ->flatten() + ->toArray(); + } else { + $conType = Constant::CONSTRAINT_PRIMARY_KEY ? 'p' : 'u'; + return collect(DB::select(" + SELECT + conname AS constraint_name, + a.attname AS column_name + FROM + pg_constraint AS c + JOIN + pg_class AS t ON c.conrelid = t.oid + JOIN + pg_attribute AS a ON a.attnum = ANY(c.conkey) AND a.attrelid = t.oid + WHERE + t.relname = ? + AND c.contype = ?",[$tableName,$conType])) + ->select('column_name') + ->flatten() + ->toArray(); + } + } } diff --git a/src/Queries/DatabaseConstraintListClass.php b/src/Queries/DatabaseConstraintListClass.php new file mode 100644 index 0000000..4a5fb35 --- /dev/null +++ b/src/Queries/DatabaseConstraintListClass.php @@ -0,0 +1,91 @@ +driver = connection_driver(); + $this->database = database_name(); + } + + public function __invoke(): array + { + return match ($this->driver) { + 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), + default => $this->mysql(), + }; + } + + /** + * @param $query + * @return array + */ + public function select($query): array + { + return DB::select($query); + } + + public function mysql(): array + { + $constrainList = Constant::ARRAY_DECLARATION; + + if (!empty($this->fields['integer'])) { + + if (!$this->tableHasValue($this->table)) { + $constrainList[] = Constant::CONSTRAINT_FOREIGN_KEY; + + if (empty($this->getConstraintField($this->table, Constant::CONSTRAINT_PRIMARY_KEY))) { + $constrainList[] = Constant::CONSTRAINT_PRIMARY_KEY; + } + } + } + + if (!empty($this->fields['mix'])) { + $constrainList[] = Constant::CONSTRAINT_INDEX_KEY; + + if (!empty($this->getUniqueFields($this->table, $this->fields['mix']))) { + $constrainList[] = Constant::CONSTRAINT_UNIQUE_KEY; + } + } + + + return $constrainList; + } + + public function pgsql(): array + { + $constraintList = Constant::ARRAY_DECLARATION; + + if (!empty($fields['integer'])) { + + if (!$this->tableHasValue($this->table)) { + $constraintList[] = Constant::CONSTRAINT_FOREIGN_KEY; + + if (empty($this->getConstraintField($this->table, Constant::CONSTRAINT_PRIMARY_KEY))) { + $constraintList[] = Constant::CONSTRAINT_PRIMARY_KEY; + } + } + } + + if (!empty($fields['mix'])) { + $constraintList[] = Constant::CONSTRAINT_INDEX_KEY; + + if (!empty($this->getUniqueFields($this->table, $fields['mix']))) { + $constraintList[] = Constant::CONSTRAINT_UNIQUE_KEY; + } + } + return $constraintList; + + } +} diff --git a/src/Queries/DatabaseEngineClass.php b/src/Queries/DatabaseEngineClass.php index a56d5f9..255ebbe 100644 --- a/src/Queries/DatabaseEngineClass.php +++ b/src/Queries/DatabaseEngineClass.php @@ -17,6 +17,7 @@ public function __invoke(): string { return match ($this->driver) { 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), default => $this->mysql(), }; } @@ -43,4 +44,9 @@ public function select($query): array { return DB::select($query); } + + public function pgsql(): string + { + + } } diff --git a/src/Queries/DatabaseFieldDetailsClass.php b/src/Queries/DatabaseFieldDetailsClass.php index 0ee7215..19c88ed 100644 --- a/src/Queries/DatabaseFieldDetailsClass.php +++ b/src/Queries/DatabaseFieldDetailsClass.php @@ -18,6 +18,7 @@ public function __invoke(): array { return match ($this->driver) { 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), default => $this->mysql(), }; } @@ -47,4 +48,30 @@ public function mysql(): array return $this->select("SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA`= '" . $this->database . "' AND `TABLE_NAME`= '" . $this->table . "' "); } + + public function pgsql(): array + { + return DB::select("SELECT + *, + CASE + WHEN data_type = 'character varying' THEN 'varchar' + WHEN data_type = 'character' THEN 'char' + WHEN data_type = 'timestamp without time zone' THEN 'timestamp' + ELSE data_type + END AS data_type, + character_maximum_length, + is_nullable, + CASE + WHEN data_type IN ('character varying', 'character') THEN character_maximum_length + WHEN data_type IN ('numeric', 'decimal') THEN numeric_precision + WHEN data_type IN ('integer', 'bigint') THEN numeric_precision + ELSE NULL + END AS size + FROM + information_schema.columns + WHERE + table_schema = 'public' + AND table_name = ?",[$this->table] + ); + } } diff --git a/src/Queries/DatabaseNonConstraintFieldClass.php b/src/Queries/DatabaseNonConstraintFieldClass.php new file mode 100644 index 0000000..af628ad --- /dev/null +++ b/src/Queries/DatabaseNonConstraintFieldClass.php @@ -0,0 +1,117 @@ +driver = connection_driver(); + $this->database = database_name(); + } + + public function __invoke(): array + { + return match ($this->driver) { + 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), + default => $this->mysql(), + }; + } + + /** + * @param $query + * @return array + */ + public function select($query): array + { + return DB::select($query); + } + + public function mysql(): array + { + $fields = Constant::ARRAY_DECLARATION; + try { + $fieldList = DB::select("SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` + WHERE `TABLE_SCHEMA`= '" . database_name() . "' AND `TABLE_NAME`= '" . $this->table . "' AND ( `COLUMN_KEY` = '' OR `COLUMN_KEY` = 'UNI' ) "); + + foreach ($fieldList as $field) { + if (!in_array($field->DATA_TYPE, Constant::RESTRICT_DATATYPE)) { + if (!$this->checkFieldHasIndex($this->table, $field->COLUMN_NAME)) { + if (str_contains($field->DATA_TYPE, "int")) { + $fields['integer'][] = $field->COLUMN_NAME; + } + $fieldDetails = $this->getFieldDataType($this->table, $field->COLUMN_NAME); + + if ($fieldDetails['size'] <= Constant::DATATYPE_VARCHAR_SIZE) { + $fields['mix'][] = $field->COLUMN_NAME; + } + } + } + } + } catch (Exception $exception) { + Log::error($exception->getMessage()); + } + return $fields; + } + + public function pgsql(): array + { + $fields = Constant::ARRAY_DECLARATION; + try { + $fieldList = collect(DB::select("SELECT + a.attname AS column_name, + CASE + WHEN format_type(a.atttypid, a.atttypmod) LIKE 'character varying%' THEN + REPLACE(format_type(a.atttypid, a.atttypmod), 'character varying', 'varchar') + ELSE + format_type(a.atttypid, a.atttypmod) + END AS data_type + FROM + pg_attribute a + JOIN + pg_class t ON a.attrelid = t.oid + LEFT JOIN + pg_constraint c ON t.oid = c.conrelid AND (a.attnum = ANY(c.conkey)) + WHERE + t.relname = ? + AND a.attnum > 0 + AND NOT a.attisdropped + AND c.conrelid IS NULL + ORDER BY + a.attnum;", [$this->table])); + + foreach ($fieldList as $field) { + if (!in_array($field->data_type, Constant::RESTRICT_DATATYPE)) { + if (!$this->checkFieldHasIndex($this->table, $field->column_name)) { + + if (str_contains($field->data_type, "int")) { + $fields['integer'][] = $field->column_name; + } + $fieldDetails = $this->getFieldDataType($this->table, $field->column_name); + + + if ($fieldDetails['size'] <= Constant::DATATYPE_VARCHAR_SIZE) { + $fields['mix'][] = $field->column_name; + } + } + } + } + } catch (Exception $exception) { + Log::error($exception->getMessage()); + } + + return $fields; + + } + +} diff --git a/src/Queries/DatabaseSizeClass.php b/src/Queries/DatabaseSizeClass.php index 4be721d..df92527 100644 --- a/src/Queries/DatabaseSizeClass.php +++ b/src/Queries/DatabaseSizeClass.php @@ -20,6 +20,7 @@ public function __invoke(): string { return match ($this->driver) { 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), default => $this->mysql(), }; } @@ -57,5 +58,19 @@ public function mysql(): string } + /** + * pgsql size + * + * @return string + */ + public function pgsql(): string + { + $result = collect( + $this->select("SELECT (pg_database_size('".$this->database."') / 1024 / 1024) AS size;") + )->toArray(); + + return reset($result)->size ?? Constant::DASH; + } + } diff --git a/src/Queries/DatabaseTableClass.php b/src/Queries/DatabaseTableClass.php index 81a1d16..5779349 100644 --- a/src/Queries/DatabaseTableClass.php +++ b/src/Queries/DatabaseTableClass.php @@ -17,6 +17,7 @@ public function __invoke(): array { return match ($this->driver) { 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), default => $this->mysql(), }; } @@ -49,4 +50,12 @@ public function mysql(): array 'Tables_in_'.database_name() ); } + + public function pgsql() : array + { + return array_column( + $this->select("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';"), + 'table_name' + ); + } } diff --git a/src/Queries/DatabaseTableFieldIndexClass.php b/src/Queries/DatabaseTableFieldIndexClass.php new file mode 100644 index 0000000..7e91bb9 --- /dev/null +++ b/src/Queries/DatabaseTableFieldIndexClass.php @@ -0,0 +1,79 @@ +driver = connection_driver(); + $this->database = database_name(); + } + + public function __invoke(): bool + { + return match ($this->driver) { + 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), + default => $this->mysql(), + }; + } + + + public function mysql(): bool + { + $indexList = DB::select("SHOW INDEX FROM " . database_name() . "." . $this->table . ""); + foreach ($indexList as $data) { + if ($data->Column_name === $this->fieldName && str_contains($data->Key_name, 'index')) { + return Constant::STATUS_TRUE; + } + } + + return Constant::STATUS_FALSE; + } + + public function sqlite(): bool + { + return Constant::STATUS_FALSE; + } + + public function pgsql(): bool + { + $indexList = DB::select("SELECT + i.relname AS index_name, + a.attname AS column_name, + ix.indisunique AS is_unique, + ix.indisprimary AS is_primary + FROM + pg_class t, + pg_class i, + pg_index ix, + pg_attribute a + WHERE + t.oid = ix.indrelid + AND i.oid = ix.indexrelid + AND a.attrelid = t.oid + AND a.attnum = ANY(ix.indkey) + AND t.relkind = 'r' + AND t.relname = ? + ORDER BY + t.relname, + i.relname;",[$this->table]); + + foreach ($indexList as $data) { + if ($data->column_name === $this->fieldName && str_contains($data->index_name, 'index')) { + return Constant::STATUS_TRUE; + } + } + + return Constant::STATUS_FALSE; + } + +} diff --git a/src/Queries/DatabaseTableFieldTypeClass.php b/src/Queries/DatabaseTableFieldTypeClass.php index 2064856..a20d6a3 100644 --- a/src/Queries/DatabaseTableFieldTypeClass.php +++ b/src/Queries/DatabaseTableFieldTypeClass.php @@ -20,6 +20,7 @@ public function __invoke(): array { return match ($this->driver) { 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), default => $this->mysql(), }; } @@ -57,7 +58,7 @@ public function mysql(): array $dataType = reset($data); - if(in_array($dataType->DATA_TYPE, Constant::NUMERIC_DATATYPE)) { + if (in_array($dataType->DATA_TYPE, Constant::NUMERIC_DATATYPE)) { if($dataType->DATA_TYPE === Constant::DATATYPE_DECIMAL) { $size = "(". $dataType->NUMERIC_PRECISION .",". $dataType->NUMERIC_SCALE .")"; @@ -72,4 +73,38 @@ public function mysql(): array return ['data_type' => $dataType->DATA_TYPE, 'size' => $size]; } } + + public function pgsql(): array + { + $data = DB::select("SELECT + column_name, + CASE + WHEN data_type = 'character varying' THEN 'varchar' + WHEN data_type = 'character' THEN 'char' + WHEN data_type = 'timestamp without time zone' THEN 'timestamp' + ELSE data_type + END AS data_type, + character_maximum_length, + is_nullable, + CASE + WHEN data_type IN ('character varying', 'character') THEN character_maximum_length + WHEN data_type IN ('numeric', 'decimal') THEN numeric_precision + WHEN data_type IN ('integer', 'bigint') THEN numeric_precision + ELSE NULL + END AS size + FROM + information_schema.columns + WHERE + table_schema = 'public' + AND Column_name = ? + AND table_name = ?",[$this->field, $this->table] + ); + + $dataTypeDetails = reset($data); + + return [ + 'data_type' => $dataTypeDetails->data_type, + 'size' => $dataTypeDetails->size, + ]; + } } diff --git a/src/Queries/DatabaseTableFieldsClass.php b/src/Queries/DatabaseTableFieldsClass.php index afce63a..fded7bb 100644 --- a/src/Queries/DatabaseTableFieldsClass.php +++ b/src/Queries/DatabaseTableFieldsClass.php @@ -19,6 +19,7 @@ public function __invoke(): array { return match ($this->driver) { 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), default => $this->mysql(), }; } @@ -49,4 +50,18 @@ public function mysql(): array $fields = $this->select("Describe `$this->table`"); return array_column($fields, 'Field'); } + + public function pgsql(): array + { + $fields = DB::select( + "SELECT column_name,data_type, character_maximum_length, is_nullable,column_default + FROM + information_schema.columns + WHERE + table_schema = 'public' + AND table_name = ?",[$this->table] + ); + + return array_column($fields, 'column_name'); + } } diff --git a/src/Queries/DatabaseTableSizeClass.php b/src/Queries/DatabaseTableSizeClass.php index e78b88d..7df7484 100644 --- a/src/Queries/DatabaseTableSizeClass.php +++ b/src/Queries/DatabaseTableSizeClass.php @@ -19,6 +19,7 @@ public function __invoke(): string { return match ($this->driver) { 'sqlite' => $this->sqlite(), + 'pgsql' => $this->pgsql(), default => $this->mysql(), }; } @@ -58,5 +59,11 @@ public function mysql(): string } + public function pgsql(): string + { + $result = $this->select("SELECT pg_size_pretty(pg_table_size('".$this->table."')) AS size"); + return reset($result)?->size ?? Constant::DASH; + } + } diff --git a/src/Traits/Audit.php b/src/Traits/Audit.php index 11fa2dc..10ce218 100644 --- a/src/Traits/Audit.php +++ b/src/Traits/Audit.php @@ -8,11 +8,11 @@ use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Log; use Vcian\LaravelDBAuditor\Constants\Constant; -use Vcian\LaravelDBAuditor\Queries\DatabaseConstraintClass; +use Vcian\LaravelDBAuditor\Queries\DatabaseConstraintListClass; trait Audit { - use DBFunctions; + use DBFunctions, DBConstraint; /** * Check field exist or not @@ -29,68 +29,6 @@ public function checkFieldExistOrNot(string $tableName, string $field): bool return Constant::STATUS_FALSE; } - /** - * Get fields which has no constraint - * @param string $tableName - * @return array - */ - public function getNoConstraintFields(string $tableName): array - { - $fields = Constant::ARRAY_DECLARATION; - try { - $fieldList = DB::select("SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` - WHERE `TABLE_SCHEMA`= '" . database_name() . "' AND `TABLE_NAME`= '" . $tableName . "' AND ( `COLUMN_KEY` = '' OR `COLUMN_KEY` = 'UNI' ) "); - - foreach ($fieldList as $field) { - if (!in_array($field->DATA_TYPE, Constant::RESTRICT_DATATYPE)) { - if (!$this->checkFieldHasIndex($tableName, $field->COLUMN_NAME)) { - if (str_contains($field->DATA_TYPE, "int")) { - $fields['integer'][] = $field->COLUMN_NAME; - } - $fieldDetails = $this->getFieldDataType($tableName, $field->COLUMN_NAME); - - if ($fieldDetails['size'] <= Constant::DATATYPE_VARCHAR_SIZE) { - $fields['mix'][] = $field->COLUMN_NAME; - } - } - } - } - } catch (Exception $exception) { - Log::error($exception->getMessage()); - } - return $fields; - } - - /** - * Get Constraint List - * @param string $tableName - * @param array $fields - * @return array - */ - public function getConstraintList(string $tableName, array $fields): array - { - $constrainList = Constant::ARRAY_DECLARATION; - - if (!empty($fields['integer'])) { - - if (!$this->tableHasValue($tableName)) { - $constrainList[] = Constant::CONSTRAINT_FOREIGN_KEY; - - if (empty($this->getConstraintField($tableName, Constant::CONSTRAINT_PRIMARY_KEY))) { - $constrainList[] = Constant::CONSTRAINT_PRIMARY_KEY; - } - } - } - - if (!empty($fields['mix'])) { - $constrainList[] = Constant::CONSTRAINT_INDEX_KEY; - - if (!empty($this->getUniqueFields($tableName, $fields['mix']))) { - $constrainList[] = Constant::CONSTRAINT_UNIQUE_KEY; - } - } - return $constrainList; - } /** * Check Table Has Value @@ -99,6 +37,7 @@ public function getConstraintList(string $tableName, array $fields): array */ public function tableHasValue(string $tableName): bool { + try { if (DB::select("Select * from " . $tableName)) { return Constant::STATUS_TRUE; @@ -109,71 +48,6 @@ public function tableHasValue(string $tableName): bool return Constant::STATUS_FALSE; } - /** - * Get constraint fields - * @param string $tableName - * @param string $input - * @return DatabaseConstraintClass - */ - public function getConstraintField(string $tableName, string $input): array - { - try { - $constraintFields = Constant::ARRAY_DECLARATION; - - if (!$this->checkTableExist($tableName)) { - return []; - } - - if ($input === Constant::CONSTRAINT_INDEX_KEY) { - $result = DB::select("SHOW INDEX FROM `{$tableName}` where Key_name != 'PRIMARY' and Key_name not like '%unique%'"); - } else { - $result = DB::select("SHOW KEYS FROM `{$tableName}` WHERE Key_name LIKE '%" . strtolower($input) . "%'"); - } - - - if ($input === Constant::CONSTRAINT_FOREIGN_KEY) { - return $this->getForeignKeyDetails($tableName); - } - - if ($result) { - foreach ($result as $value) { - $constraintFields[] = $value->Column_name; - } - } - } catch (Exception $exception) { - Log::error($exception->getMessage()); - } - return $constraintFields; - } - - /** - * get Foreign Key - * @param string $tableName - * @return array - */ - public function getForeignKeyDetails(string $tableName): array - { - $foreignFieldDetails = Constant::ARRAY_DECLARATION; - try { - $resultForeignKey = DB::select("SELECT i.TABLE_SCHEMA, i.TABLE_NAME, i.CONSTRAINT_TYPE,k.COLUMN_NAME, i.CONSTRAINT_NAME, - k.REFERENCED_TABLE_NAME, k.REFERENCED_COLUMN_NAME FROM information_schema.TABLE_CONSTRAINTS i - LEFT JOIN information_schema.KEY_COLUMN_USAGE k ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME - WHERE i.CONSTRAINT_TYPE = 'FOREIGN KEY' AND i.TABLE_SCHEMA = '" . database_name() . "' AND i.TABLE_NAME = '" . $tableName . "'"); - - if ($resultForeignKey) { - foreach ($resultForeignKey as $value) { - $foreignFieldDetails[] = [ - "column_name" => $value->COLUMN_NAME, - "foreign_table_name" => $value->REFERENCED_TABLE_NAME, - "foreign_column_name" => $value->REFERENCED_COLUMN_NAME - ]; - } - } - } catch (Exception $exception) { - Log::error($exception->getMessage()); - } - return $foreignFieldDetails; - } /** * Get Unique Fields diff --git a/src/Traits/DBConstraint.php b/src/Traits/DBConstraint.php new file mode 100644 index 0000000..94ca400 --- /dev/null +++ b/src/Traits/DBConstraint.php @@ -0,0 +1,99 @@ +checkTableExist($tableName)) { + return []; + } + + if ($input === Constant::CONSTRAINT_INDEX_KEY) { + $result = DB::select("SHOW INDEX FROM `{$tableName}` where Key_name != 'PRIMARY' and Key_name not like '%unique%'"); + } else { + $result = DB::select("SHOW KEYS FROM `{$tableName}` WHERE Key_name LIKE '%" . strtolower($input) . "%'"); + } + + + if ($input === Constant::CONSTRAINT_FOREIGN_KEY) { + return $this->getForeignKeyDetails($tableName); + } + + if ($result) { + foreach ($result as $value) { + $constraintFields[] = $value->Column_name; + } + } + } catch (Exception $exception) { + Log::error($exception->getMessage()); + } + return $constraintFields; + } + + /** + * get Foreign Key + * @param string $tableName + * @return array + */ + public function getForeignKeyDetails(string $tableName): array + { + $foreignFieldDetails = Constant::ARRAY_DECLARATION; + try { + $resultForeignKey = DB::select("SELECT i.TABLE_SCHEMA, i.TABLE_NAME, i.CONSTRAINT_TYPE,k.COLUMN_NAME, i.CONSTRAINT_NAME, + k.REFERENCED_TABLE_NAME, k.REFERENCED_COLUMN_NAME FROM information_schema.TABLE_CONSTRAINTS i + LEFT JOIN information_schema.KEY_COLUMN_USAGE k ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME + WHERE i.CONSTRAINT_TYPE = 'FOREIGN KEY' AND i.TABLE_SCHEMA = '" . database_name() . "' AND i.TABLE_NAME = '" . $tableName . "'"); + + if ($resultForeignKey) { + foreach ($resultForeignKey as $value) { + $foreignFieldDetails[] = [ + "column_name" => $value->COLUMN_NAME, + "foreign_table_name" => $value->REFERENCED_TABLE_NAME, + "foreign_column_name" => $value->REFERENCED_COLUMN_NAME + ]; + } + } + } catch (Exception $exception) { + Log::error($exception->getMessage()); + } + return $foreignFieldDetails; + } +} diff --git a/src/Traits/DBFunctions.php b/src/Traits/DBFunctions.php index 0c6ec0d..67d6b43 100644 --- a/src/Traits/DBFunctions.php +++ b/src/Traits/DBFunctions.php @@ -2,15 +2,14 @@ namespace Vcian\LaravelDBAuditor\Traits; -use Exception; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; + use Vcian\LaravelDBAuditor\Constants\Constant; use Vcian\LaravelDBAuditor\Queries\DatabaseCharacterSetClass; use Vcian\LaravelDBAuditor\Queries\DatabaseEngineClass; use Vcian\LaravelDBAuditor\Queries\DatabaseFieldDetailsClass; use Vcian\LaravelDBAuditor\Queries\DatabaseSizeClass; use Vcian\LaravelDBAuditor\Queries\DatabaseTableClass; +use Vcian\LaravelDBAuditor\Queries\DatabaseTableFieldIndexClass; use Vcian\LaravelDBAuditor\Queries\DatabaseTableFieldsClass; use Vcian\LaravelDBAuditor\Queries\DatabaseTableFieldTypeClass; use Vcian\LaravelDBAuditor\Queries\DatabaseTableSizeClass; @@ -50,7 +49,7 @@ public function checkTableExist(string $tableName): bool /** * Get Table List - * @return array + * @return array|string|int */ public function getTableList(): array { @@ -66,7 +65,7 @@ public function getTableList(): array public function getFieldsDetails(string $tableName): array { $fieldDetails = new DatabaseFieldDetailsClass($tableName); - return $fieldDetails() ?? Constant::ARRAY_DECLARATION; + return $fieldDetails(); } /** @@ -87,11 +86,7 @@ public function getTableSize(string $tableName): string */ public function getFieldDataType(string $tableName, string $fieldName): array|bool { - $fieldDataType = new DatabaseTableFieldTypeClass( - $tableName, - $fieldName - ); - + $fieldDataType = new DatabaseTableFieldTypeClass($tableName, $fieldName); return $fieldDataType(); } @@ -103,19 +98,8 @@ public function getFieldDataType(string $tableName, string $fieldName): array|bo */ public function checkFieldHasIndex(string $tableName, string $fieldName): bool { - try { - $query = "SHOW INDEX FROM " . database_name() . "." . $tableName . ""; - $fieldConstraints = DB::select($query); - - foreach ($fieldConstraints as $fieldConstraint) { - if ($fieldConstraint->Column_name === $fieldName && str_contains($fieldConstraint->Key_name, 'index')) { - return Constant::STATUS_TRUE; - } - } - } catch (Exception $exception) { - Log::error($exception->getMessage()); - } - return Constant::STATUS_FALSE; + $getIndex = new DatabaseTableFieldIndexClass($tableName, $fieldName); + return $getIndex(); } /** diff --git a/src/Traits/DisplayTable.php b/src/Traits/DisplayTable.php index 8fbf5df..3fd0751 100644 --- a/src/Traits/DisplayTable.php +++ b/src/Traits/DisplayTable.php @@ -3,7 +3,9 @@ namespace Vcian\LaravelDBAuditor\Traits; use Illuminate\Support\Facades\DB; +use Vcian\LaravelDBAuditor\Constants\Constant; use Vcian\LaravelDBAuditor\Queries\DatabaseConstraintClass; +use Vcian\LaravelDBAuditor\Queries\DatabaseConstraintListClass; use function Termwind\{renderUsing}; use function Termwind\{render}; @@ -37,9 +39,9 @@ public function display(string $tableName) :void 'size' => $this->getTableSize($tableName), 'fields' => $fields, 'field_count' => count($fields), - 'constraint' => $constraint(), + 'constraint' => $constraint() ]; - render(view('DBAuditor::'.$this->connection.'.constraint', ['data' => $data])); + render(view('DBAuditor::'.connection_driver().'.constraint', ['data' => $data])); } } diff --git a/src/Traits/Rules.php b/src/Traits/Rules.php index efb9f6d..9c1047f 100644 --- a/src/Traits/Rules.php +++ b/src/Traits/Rules.php @@ -24,9 +24,10 @@ public function allTablesRules(): array { $checkTableStandard = Constant::ARRAY_DECLARATION; // array of table name and status $tableList = collect($this->getTableList()) - ->diff(config('db-auditor.skip_tables')) + ->diff(config('audit.skip_tables')) ->toArray(); + foreach ($tableList as $tableName) { $status = $this->checkStatus($tableName); $size = $this->getTableSize($tableName); @@ -113,18 +114,19 @@ public function fieldRules(string $tableName): array $checkFields = Constant::ARRAY_DECLARATION; try { $fields = $this->getFields($tableName); - + $fieldDT = []; foreach ($fields as $field) { $checkFields[$field] = $this->checkRules($field, Constant::FIELD_RULES); $dataTypeDetails = $this->getFieldDataType($tableName, $field); $checkFields[$field]['datatype'] = $dataTypeDetails; - if (connection_driver() === Constant::MYSQL_DB && $dataTypeDetails['data_type'] === Constant::DATATYPE_VARCHAR + if (connection_driver() === Constant::POSTGRESQL_DB && $dataTypeDetails['data_type'] === Constant::DATATYPE_VARCHAR && $dataTypeDetails['size'] <= Constant::DATATYPE_VARCHAR_SIZE ) { $checkFields[$field]['suggestion'] = __('Lang::messages.standard.error_message.datatype_change'); } } + } catch (Exception $exception) { Log::error($exception->getMessage()); } diff --git a/src/views/pgsql/constraint.blade.php b/src/views/pgsql/constraint.blade.php new file mode 100644 index 0000000..c8ad51b --- /dev/null +++ b/src/views/pgsql/constraint.blade.php @@ -0,0 +1,63 @@ +
+
+ TABLE NAME : {{ $data['table'] }} +
+ +
+ Columns + + {{ $data['field_count'] }} +
+ +
+ Table Size + + {{ $data['size'] }} +
+ +
+
+ Fields + + Data Type +
+ + + @foreach ($data['fields'] as $field) +
+ {{ $field->column_name }} + {{ $field->data_type }} {{ $field->size ? '(' . $field->size . ')' : '' }} + + {{ $field->data_type }} +
+ @endforeach +
+ +
+ @if(($data['constraint'])) + @foreach ($data['constraint'] as $key => $value) + @if ($value) +
+ {{ strtoupper($key) }} +
+ @foreach ($value as $constraintField) + @if ($constraintField && $key === 'foreign') +
+ {{ $constraintField->column_name }} + + {{ $constraintField->foreign_table }} + {{ $constraintField->foreign_column }} +
+ @else +
+ {{ $constraintField }} + +
+ @endif + @endforeach + @endif + @endforeach + @endif +
+
diff --git a/src/views/pgsql/standard.blade.php b/src/views/pgsql/standard.blade.php new file mode 100644 index 0000000..7782d07 --- /dev/null +++ b/src/views/pgsql/standard.blade.php @@ -0,0 +1,35 @@ +
+ @php + $success = 0; + $error = 0; + @endphp +
+
+ TABLE NAME + + Standardization +
+ @foreach ($tableStatus as $table) +
+ {{ $table['name'] }} + ({{ $table['size'] }} MB) + + @if ($table['status']) + @php $success++; @endphp + + @else + @php $error++; @endphp + + @endif +
+ @endforeach +
+ {{ $success }} TABLE + PASSED ✓ +
+
+ {{ $error }} TABLE + FAILED ✗ +
+
+
diff --git a/src/views/pgsql/table_standard.blade.php b/src/views/pgsql/table_standard.blade.php new file mode 100644 index 0000000..419042f --- /dev/null +++ b/src/views/pgsql/table_standard.blade.php @@ -0,0 +1,74 @@ +
+ TABLE NAME : {{ str_replace('_', ' ', $tableStatus['table']) }} + + @if ($tableStatus['table_comment']) +
+ suggestion(s) +
+
    + @foreach ($tableStatus['table_comment'] as $commentKey => $comment) +
  1. + {{ $comment }} +
  2. + @endforeach +
+ @endif +
+ + + + + + + + + + + + @foreach ($tableStatus['fields'] as $key => $field) + + @if (!empty($field)) + @if ((isset($field['suggestion']) && isset($field['datatype']) && count($field) === 2) || count($field) === 1) + + + @else + + + @endif + + + + @php + if(isset($field['datatype'])) { + unset($field['datatype']); + } + @endphp + @foreach ($field as $key => $fieldComment) + + + + + + @if ($key === 'suggestion') + + @else + + @endif + + @endforeach + @else + + + + + + @endif + + @php + unset($field['datatype']); + @endphp + @endforeach + +
field name standard check datatype size suggestion(s)
{{ $key }}{{ $key }} {{ $field['datatype']['data_type'] ?? "-" }} {{ $field['datatype']['size'] ?? "-" }}
{{ $fieldComment }} {{ $fieldComment }}
{{ $key }} {{ $field['datatype']['data_type'] ?? "-" }} {{ $field['datatype']['size'] ?? "-" }} -
+
+