├── .gitignore ├── README.md ├── bin └── database-to-plantuml ├── composer.json ├── resource ├── plantuml.jar └── samples │ ├── mysql-employees.sql │ └── pgsql-employees.sql └── src ├── Backend ├── CommonMark.php └── PlantUML.php └── Frontend ├── Column.php ├── Database.php ├── MySQL ├── Column.php ├── Database.php ├── Reader.php └── Table.php ├── PgSQL ├── Column.php ├── Database.php ├── Reader.php └── Table.php ├── Reader.php └── Table.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /composer.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Database to PlantUML 2 | 3 | This utility renders a graphical 2D visualisation of a database. 4 | 5 | Currently, the only supported frontends are **PostgreSQL** and 6 | **MySQL**. There are 2 backends: `commonmark` and `plantuml`. The 7 | `plantuml` backend allows to generate visualisations into the 8 | following formats: 9 | 10 | * PNG, 11 | * SVG, 12 | * EPS, 13 | * PDF, 14 | * VDX, 15 | * XMI, 16 | * HTML, 17 | * TXT, 18 | * UTXT, 19 | * LaTeX. 20 | 21 | # Installation 22 | 23 | With [Composer](https://getcomposer.org/), simply run the following command: 24 | 25 | ```sh 26 | $ composer install 27 | ``` 28 | 29 | If you would like to use it as a dependency of your project, then: 30 | 31 | ```sh 32 | $ composer require hywan/database-to-plantuml 33 | ``` 34 | 35 | To use the `plantuml` backend, you can use the JAR in `resource/plantuml.jar`. 36 | 37 | # Examples with… 38 | 39 | ## … PostgreSQL 40 | 41 | Taking as an example the famous `employees` use case: 42 | 43 | ```sh 44 | # Import the schema. 45 | $ psql -f resource/samples/pgsql-employees.sql postgres 46 | 47 | # Generate the visualisation. 48 | $ bin/database-to-plantuml -d 'pgsql:dbname=employees' -u hywan -s employees | \ 49 | java -jar resource/plantuml.jar -verbose -pipe > output.png 50 | ``` 51 | 52 | ![Output with PostgreSQL](https://cldup.com/UMsPg3WKh0.png) 53 | 54 | ## … MySQL 55 | 56 | With the same `employees` use case: 57 | 58 | ```sh 59 | # Import the schema. 60 | $ mysql -u root < resource/samples/mysql-employees.sql 61 | 62 | # Generate the visualisation. 63 | $ bin/database-to-plantuml -d 'mysql:dbname=employees' -u root -s employees | \ 64 | java -jar resource/plantuml.jar -verbose -pipe > output.png 65 | ``` 66 | 67 | ![Output with MySQL](https://cldup.com/Cgn7bqdEz5.png) 68 | 69 | Note: Outputs differ because the `employees` examples are not exactly 70 | the same. They are here to illustrate the tool only. 71 | 72 | # License 73 | 74 | BSD-3-License, but seriously, do what ever you want! 75 | -------------------------------------------------------------------------------- /bin/database-to-plantuml: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | parse(Router\Cli::getURI()); 45 | 46 | // Declare the options. 47 | $options = new Console\GetOption( 48 | [ 49 | ['database-dsn', Console\GetOption::REQUIRED_ARGUMENT, 'd'], 50 | ['database-user', Console\GetOption::REQUIRED_ARGUMENT, 'u'], 51 | ['database-password', Console\GetOption::REQUIRED_ARGUMENT, 'p'], 52 | ['database-name', Console\GetOption::REQUIRED_ARGUMENT, 's'], 53 | ['backend', Console\GetOption::REQUIRED_ARGUMENT, 'b'], 54 | ['help', Console\GetOption::NO_ARGUMENT, 'h'], 55 | ['help', Console\GetOption::NO_ARGUMENT, '?'], 56 | ], 57 | $parser 58 | ); 59 | 60 | // Compute options. 61 | $databaseDsn = 'mysql:host=localhost'; 62 | $databaseUser = 'root'; 63 | $databasePassword = ''; 64 | $databaseName = null; 65 | $frontendName = Frontend\MySQL\Reader::class; 66 | $backendName = Backend\PlantUML::class; 67 | 68 | while (false !== $c = $options->getOption($v)) { 69 | switch($c) { 70 | case 'd': 71 | $databaseDsn = $v; 72 | 73 | break; 74 | 75 | case 'u': 76 | $databaseUser = $v; 77 | 78 | break; 79 | 80 | case 'p': 81 | $databasePassword = $v; 82 | 83 | break; 84 | 85 | case 's': 86 | $databaseName = $v; 87 | 88 | break; 89 | 90 | case 'b': 91 | $v = strtolower($v); 92 | 93 | switch ($v) { 94 | case 'commonmark': 95 | $backendName = Backend\CommonMark::class; 96 | 97 | break; 98 | 99 | case 'plantuml': 100 | $backendName = Backend\PlantUML::class; 101 | 102 | break; 103 | 104 | default: 105 | throw new RuntimeException( 106 | 'Backend ' . $v . ' is invalid. Choose between `commonmark` or `plantuml`.' 107 | ); 108 | } 109 | 110 | break; 111 | 112 | case 'h': 113 | case '?': 114 | usage(); 115 | exit(1); 116 | } 117 | } 118 | 119 | if (empty($databaseName)) { 120 | echo 'The database name is required.', "\n\n"; 121 | 122 | usage(); 123 | 124 | exit(2); 125 | } 126 | 127 | if (0 !== preg_match('/^(?[^:]+):/', $databaseDsn, $dsnType)) { 128 | switch ($dsnType['type']) { 129 | case 'mysql': 130 | $frontendName = Frontend\MySQL\Reader::class; 131 | 132 | break; 133 | 134 | case 'pgsql': 135 | $frontendName = Frontend\PgSQL\Reader::class; 136 | 137 | break; 138 | 139 | default: 140 | throw new RuntimeException( 141 | 'Unfortunately, the DSN type `' . $dsnType['type'] . 142 | '` is not implemented :-(!' 143 | ); 144 | } 145 | } else { 146 | throw new RuntimeException( 147 | 'The DSN `' . $dsn . '` seems to be invalid.' 148 | ); 149 | } 150 | 151 | try { 152 | $reader = new $frontendName($databaseDsn, $databaseUser, $databasePassword); 153 | $backend = new $backendName(); 154 | 155 | echo $backend->visit($reader->read($databaseName)); 156 | } catch (Exception $e) { 157 | ob_start(); 158 | 159 | Console\Cursor::colorize('foreground(white) background(red)'); 160 | echo $e->getMessage(), "\n"; 161 | Console\Cursor::colorize('normal'); 162 | $content = ob_get_contents(); 163 | 164 | ob_end_clean(); 165 | 166 | file_put_contents('php://stderr', $content); 167 | exit(2); 168 | } 169 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "hywan/database-to-plantuml", 3 | "description": "Extract database table information into a PlantUML description.", 4 | "type" : "library", 5 | "homepage" : "https://github.com/Hywan/Database-to-PlantUML", 6 | "license" : "BSD-3-Clause", 7 | "authors" : [ 8 | { 9 | "name" : "Ivan Enderlin", 10 | "email": "ivan.enderlin@hoa-project.net" 11 | } 12 | ], 13 | "support": { 14 | "source": "https://github.com/Hywan/Database-to-PlantUML" 15 | }, 16 | "require": { 17 | "php" : ">=7.1", 18 | "hoa/console" : "~3.0", 19 | "hoa/database": "~0.17", 20 | "hoa/router" : "~3.0", 21 | "hoa/visitor" : "~2.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Hywan\\DatabaseToPlantUML\\": "src/" 26 | } 27 | }, 28 | "bin": ["bin/database-to-plantuml"] 29 | } 30 | -------------------------------------------------------------------------------- /resource/plantuml.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hywan/Database-to-PlantUML/062eb69f489b5dfc0a7bc12346cb7544eb9d5ce9/resource/plantuml.jar -------------------------------------------------------------------------------- /resource/samples/mysql-employees.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS employees; 2 | CREATE DATABASE IF NOT EXISTS employees; 3 | USE employees; 4 | 5 | SELECT 'CREATING DATABASE STRUCTURE' as 'INFO'; 6 | 7 | DROP TABLE IF EXISTS dept_emp, 8 | dept_manager, 9 | titles, 10 | salaries, 11 | employees, 12 | departments; 13 | 14 | /*!50503 set default_storage_engine = InnoDB */; 15 | /*!50503 select CONCAT('storage engine: ', @@default_storage_engine) as INFO */; 16 | 17 | CREATE TABLE employees ( 18 | emp_no INT NOT NULL, 19 | birth_date DATE NOT NULL, 20 | first_name VARCHAR(14) NOT NULL, 21 | last_name VARCHAR(16) NOT NULL, 22 | gender ENUM ('M','F') NOT NULL, 23 | hire_date DATE NOT NULL, 24 | PRIMARY KEY (emp_no) 25 | ); 26 | 27 | CREATE TABLE departments ( 28 | dept_no CHAR(4) NOT NULL, 29 | dept_name VARCHAR(40) NOT NULL, 30 | PRIMARY KEY (dept_no), 31 | UNIQUE KEY (dept_name) 32 | ); 33 | 34 | CREATE TABLE dept_manager ( 35 | emp_no INT NOT NULL, 36 | dept_no CHAR(4) NOT NULL, 37 | from_date DATE NOT NULL, 38 | to_date DATE NOT NULL, 39 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 40 | FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, 41 | PRIMARY KEY (emp_no,dept_no) 42 | ); 43 | 44 | CREATE TABLE dept_emp ( 45 | emp_no INT NOT NULL, 46 | dept_no CHAR(4) NOT NULL, 47 | from_date DATE NOT NULL, 48 | to_date DATE NOT NULL, 49 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 50 | FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, 51 | PRIMARY KEY (emp_no,dept_no) 52 | ); 53 | 54 | CREATE TABLE titles ( 55 | emp_no INT NOT NULL, 56 | title VARCHAR(50) NOT NULL, 57 | from_date DATE NOT NULL, 58 | to_date DATE, 59 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 60 | PRIMARY KEY (emp_no,title, from_date) 61 | ); 62 | 63 | CREATE TABLE salaries ( 64 | emp_no INT NOT NULL, 65 | salary INT NOT NULL, 66 | from_date DATE NOT NULL, 67 | to_date DATE NOT NULL, 68 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 69 | PRIMARY KEY (emp_no, from_date) 70 | ); 71 | 72 | CREATE OR REPLACE VIEW dept_emp_latest_date AS 73 | SELECT emp_no, MAX(from_date) AS from_date, MAX(to_date) AS to_date 74 | FROM dept_emp 75 | GROUP BY emp_no; 76 | 77 | # shows only the current department for each employee 78 | CREATE OR REPLACE VIEW current_dept_emp AS 79 | SELECT l.emp_no, dept_no, l.from_date, l.to_date 80 | FROM dept_emp d 81 | INNER JOIN dept_emp_latest_date l 82 | ON d.emp_no=l.emp_no AND d.from_date=l.from_date AND l.to_date = d.to_date; 83 | -------------------------------------------------------------------------------- /resource/samples/pgsql-employees.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS employees; 2 | CREATE DATABASE employees; 3 | \connect employees; 4 | 5 | CREATE TYPE gender AS ENUM('M', 'F'); 6 | 7 | CREATE TABLE employees ( 8 | emp_no INT NOT NULL, 9 | birth_date DATE NOT NULL, 10 | first_name VARCHAR(14) NOT NULL, 11 | last_name VARCHAR(16) NOT NULL, 12 | gender gender NULL, 13 | hire_date DATE NOT NULL, 14 | PRIMARY KEY (emp_no) 15 | ); 16 | 17 | CREATE TABLE departments ( 18 | dept_no CHAR(4) NOT NULL, 19 | dept_name VARCHAR(40) NOT NULL, 20 | PRIMARY KEY (dept_no), 21 | UNIQUE (dept_name) 22 | ); 23 | 24 | CREATE TABLE dept_manager ( 25 | dept_no CHAR(4) NOT NULL, 26 | emp_no INT NOT NULL, 27 | from_date DATE NOT NULL, 28 | to_date DATE NOT NULL, 29 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 30 | FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, 31 | PRIMARY KEY (emp_no,dept_no) 32 | ); 33 | 34 | CREATE INDEX dept_manager_dept_no_idx ON dept_manager(dept_no); 35 | 36 | CREATE TABLE dept_emp ( 37 | emp_no INT NOT NULL, 38 | dept_no CHAR(4) NOT NULL, 39 | from_date DATE NOT NULL, 40 | to_date DATE NOT NULL, 41 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 42 | FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, 43 | PRIMARY KEY (emp_no,dept_no) 44 | ); 45 | 46 | CREATE INDEX dept_emp_dept_no_idx ON dept_emp(dept_no); 47 | 48 | CREATE TABLE titles ( 49 | emp_no INT NOT NULL, 50 | title VARCHAR(50) NOT NULL, 51 | from_date DATE NOT NULL, 52 | to_date DATE, 53 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 54 | PRIMARY KEY (emp_no,title, from_date) 55 | ); 56 | 57 | CREATE TABLE salaries ( 58 | emp_no INT NOT NULL, 59 | salary INT NOT NULL, 60 | from_date DATE NOT NULL, 61 | to_date DATE NOT NULL, 62 | FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, 63 | PRIMARY KEY (emp_no, from_date) 64 | ); 65 | -------------------------------------------------------------------------------- /src/Backend/CommonMark.php: -------------------------------------------------------------------------------- 1 | visitDatabase($element, $handle, $eldnah); 16 | } elseif ($element instanceof Frontend\Table) { 17 | return $this->visitTAble($element, $handle, $eldnah); 18 | } 19 | 20 | throw new \RuntimeException('Unknown element to visit ' . get_class($element) . '.'); 21 | } 22 | 23 | public function visitDatabase(Frontend\Database $database, &$handle = null, &$eldnah = null): string 24 | { 25 | $out = '# `' . $database->name . '`' . "\n\n"; 26 | 27 | foreach ($database->tables() as $table) { 28 | $out .= $table->accept($this, $handle, $eldnah) . "\n"; 29 | } 30 | 31 | return $out; 32 | } 33 | 34 | public function visitTable(Frontend\Table $table, &$handle = null, &$eldnah = null): string 35 | { 36 | $out = '## `' . $table->name . '`' . "\n\n"; 37 | 38 | if (!empty($table->comment)) { 39 | $out .= $table->comment; 40 | } 41 | 42 | $columnNames = array_keys(get_class_vars(Frontend\Column::class)); 43 | $separators = []; 44 | $maximumColumnWidths = []; 45 | 46 | foreach ($columnNames as $columnName) { 47 | $maximumColumnWidths[$columnName] = strlen($columnName); 48 | } 49 | 50 | $columns = []; 51 | 52 | foreach ($table->columns() as $column) { 53 | $columns[] = $column; 54 | 55 | foreach ($columnNames as $columnName) { 56 | $maximumColumnWidths[$columnName] = max($maximumColumnWidths[$columnName], strlen($column->$columnName ?: '')); 57 | } 58 | } 59 | 60 | $pattern = ''; 61 | 62 | foreach ($maximumColumnWidths as $maximumColumnWidth) { 63 | $length = $maximumColumnWidth + 2; 64 | 65 | $pattern .= ' %-' . $length . 's |'; 66 | $separators[] = str_repeat('-', $length); 67 | } 68 | 69 | $out .= 70 | '|' . sprintf($pattern, ...$columnNames) . "\n" . 71 | '|' . sprintf($pattern, ...$separators) . "\n"; 72 | 73 | foreach ($columns as $column) { 74 | $columnValues = array_map( 75 | function ($value) { 76 | if ('' === $value || null === $value) { 77 | return ''; 78 | } 79 | 80 | return '`' . $value . '`'; 81 | }, 82 | array_values(get_object_vars($column)) 83 | ); 84 | 85 | $out .= '|' . sprintf($pattern, ...$columnValues) . "\n"; 86 | } 87 | 88 | return $out; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Backend/PlantUML.php: -------------------------------------------------------------------------------- 1 | visitDatabase($element, $handle, $eldnah); 16 | } elseif ($element instanceof Frontend\Table) { 17 | return $this->visitTAble($element, $handle, $eldnah); 18 | } 19 | 20 | throw new \RuntimeException('Unknown element to visit ' . get_class($element) . '.'); 21 | } 22 | 23 | public function visitDatabase(Frontend\Database $database, &$handle = null, &$eldnah = null): string 24 | { 25 | $out = 26 | '@startuml' . "\n\n" . 27 | '!define table(x) class x << (T,#ffebf3) >>' . "\n" . 28 | 'hide methods' . "\n" . 29 | 'hide stereotypes' . "\n" . 30 | 'skinparam classFontColor #3b0018' . "\n" . 31 | 'skinparam classArrowColor #ff0066' . "\n" . 32 | 'skinparam classBorderColor #ff0066' . "\n" . 33 | 'skinparam classBackgroundColor ##f6f4ee' . "\n" . 34 | 'skinparam shadowing false' . "\n" . 35 | "\n"; 36 | 37 | foreach ($database->tables() as $table) { 38 | $out .= $table->accept($this, $handle, $eldnah) . "\n"; 39 | } 40 | 41 | $out .= '@enduml'; 42 | 43 | return $out; 44 | } 45 | 46 | public function visitTable(Frontend\Table $table, &$handle = null, &$eldnah = null): string 47 | { 48 | $out = 'table(' . $table->name . ') {' . "\n"; 49 | $connections = ''; 50 | 51 | $columns = []; 52 | $maximumNameLength = 0; 53 | 54 | foreach ($table->columns() as $column) { 55 | $columns[] = $column; 56 | 57 | $maximumNameLength = max($maximumNameLength, strlen($column->name)); 58 | } 59 | 60 | $maximumTabulation = 1 + (int) floor($maximumNameLength / 4); 61 | 62 | $listedColumns = []; 63 | 64 | foreach ($columns as $column) { 65 | $isPrimary = 0 !== preg_match($column::PRIMARY, $column->constraintName ?? ''); 66 | 67 | if (false === in_array($column->name, $listedColumns)) { 68 | $out .= sprintf( 69 | ' {field} %s%s%s%s%s' . "\n", 70 | $isPrimary ? '+' : '', 71 | $column->name, 72 | str_repeat("\t", max(1, $maximumTabulation - (int) (floor(strlen($column->name) / 4)))), 73 | $column->isNullable ? '?' : '', 74 | $column->type 75 | ); 76 | 77 | $listedColumns[] = $column->name; 78 | } 79 | 80 | if (false === $isPrimary && 81 | null !== $column->referencedTableName && 82 | null !== $column->referencedColumnName) { 83 | $connections .= 84 | $column->referencedTableName . ' <-- ' . $table->name . 85 | ' : on ' . $column->name . ' = ' . $column->referencedColumnName . "\n"; 86 | } 87 | } 88 | 89 | $out .= 90 | '}' . "\n\n" . 91 | $connections; 92 | 93 | return $out; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Frontend/Column.php: -------------------------------------------------------------------------------- 1 | visit($this, $handle, $eldnah); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Frontend/Database.php: -------------------------------------------------------------------------------- 1 | _databaseConnection = $databaseConnection; 19 | $this->name = $name; 20 | } 21 | 22 | abstract public function tables(): iterable; 23 | 24 | public function getDatabaseConnection(): Dal 25 | { 26 | return $this->_databaseConnection; 27 | } 28 | 29 | public function accept(Visitor\Visit $visitor, &$handle = null, $eldnah = null) 30 | { 31 | return $visitor->visit($this, $handle, $eldnah); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Frontend/MySQL/Column.php: -------------------------------------------------------------------------------- 1 | getDatabaseConnection() 17 | ->prepare( 18 | 'SELECT table_schema AS databaseName, ' . 19 | ' table_name AS name, ' . 20 | ' engine, ' . 21 | ' table_comment AS comment ' . 22 | 'FROM information_schema.tables ' . 23 | 'WHERE table_schema = :database_name', 24 | [ 25 | PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL 26 | ] 27 | ) 28 | ->execute([ 29 | 'database_name' => $this->name 30 | ]); 31 | 32 | $tables->setFetchingStyle( 33 | DalStatement::FROM_START, 34 | DalStatement::FORWARD, 35 | DalStatement::AS_CLASS, 36 | Table::class, 37 | [ 38 | $this->getDatabaseConnection() 39 | ] 40 | ); 41 | 42 | yield from $tables; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Frontend/MySQL/Reader.php: -------------------------------------------------------------------------------- 1 | getDatabaseConnection(), $databaseName); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Frontend/MySQL/Table.php: -------------------------------------------------------------------------------- 1 | getDatabaseConnection() 17 | ->prepare( 18 | 'SELECT c.column_name AS name, ' . 19 | ' c.column_default AS defaultValue, ' . 20 | ' CASE c.is_nullable WHEN "YES" THEN 1 ELSE 0 END AS isNullable, ' . 21 | ' c.column_type AS type, ' . 22 | ' k.constraint_name AS constraintName, ' . 23 | ' k.referenced_table_name AS referencedTableName, ' . 24 | ' k.referenced_column_name AS referencedColumnName ' . 25 | 'FROM information_schema.columns AS c ' . 26 | 'LEFT JOIN information_schema.key_column_usage AS k ' . 27 | 'ON k.table_schema = c.table_schema ' . 28 | 'AND k.table_name = c.table_name ' . 29 | 'AND k.column_name = c.column_name ' . 30 | 'WHERE c.table_schema = :database_name ' . 31 | 'AND c.table_name = :table_name ' . 32 | 'ORDER BY c.ordinal_position ASC', 33 | [ 34 | PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL 35 | ] 36 | ) 37 | ->execute([ 38 | 'database_name' => $this->databaseName, 39 | 'table_name' => $this->name 40 | ]); 41 | 42 | $columns->setFetchingStyle( 43 | DalStatement::FROM_START, 44 | DalStatement::FORWARD, 45 | DalStatement::AS_CLASS, 46 | Column::class 47 | ); 48 | 49 | yield from $columns; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Frontend/PgSQL/Column.php: -------------------------------------------------------------------------------- 1 | name), [1 => 'public']); 16 | 17 | $tables = 18 | $this->getDatabaseConnection() 19 | ->prepare( 20 | 'SELECT table_catalog AS "databaseName", ' . 21 | ' table_name AS name, ' . 22 | ' table_schema AS "tableSchema" ' . 23 | 'FROM information_schema.tables ' . 24 | 'WHERE table_catalog = :database_name ' . 25 | 'AND table_schema = :table_schema', 26 | [ 27 | PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY 28 | ] 29 | ) 30 | ->execute([ 31 | 'database_name' => $databaseName, 32 | 'table_schema' => $tableSchema 33 | ]); 34 | 35 | $tables->setFetchingStyle( 36 | DalStatement::FROM_START, 37 | DalStatement::FORWARD, 38 | DalStatement::AS_CLASS, 39 | Table::class, 40 | [ 41 | $this->getDatabaseConnection() 42 | ] 43 | ); 44 | 45 | yield from $tables; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Frontend/PgSQL/Reader.php: -------------------------------------------------------------------------------- 1 | getDatabaseConnection(), $databaseName); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Frontend/PgSQL/Table.php: -------------------------------------------------------------------------------- 1 | getDatabaseConnection() 17 | ->prepare( 18 | 'SELECT c.column_name AS name, ' . 19 | ' c.column_default AS "defaultValue", ' . 20 | ' CASE c.is_nullable WHEN \'YES\' THEN 1 ELSE 0 END AS "isNullable", ' . 21 | ' c.data_type AS type, ' . 22 | ' k.constraint_name AS "constraintName", ' . 23 | ' cc.table_name AS "referencedTableName", ' . 24 | ' cc.column_name AS "referencedColumnName" ' . 25 | 'FROM information_schema.columns AS c ' . 26 | 'LEFT JOIN information_schema.key_column_usage AS k ' . 27 | 'ON k.table_catalog = c.table_catalog ' . 28 | 'AND k.table_name = c.table_name ' . 29 | 'AND k.table_schema = c.table_schema ' . 30 | 'AND k.column_name = c.column_name ' . 31 | 'LEFT JOIN information_schema.constraint_column_usage AS cc ' . 32 | 'ON cc.constraint_name = k.constraint_name ' . 33 | 'WHERE c.table_catalog = :database_name ' . 34 | 'AND c.table_name = :table_name ' . 35 | 'AND c.table_schema = :table_schema', 36 | [ 37 | PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY 38 | ] 39 | ) 40 | ->execute([ 41 | 'database_name' => $this->databaseName, 42 | 'table_name' => $this->name, 43 | 'table_schema' => $this->tableSchema 44 | ]); 45 | 46 | $columns->setFetchingStyle( 47 | DalStatement::FROM_START, 48 | DalStatement::FORWARD, 49 | DalStatement::AS_CLASS, 50 | Column::class 51 | ); 52 | 53 | yield from $columns; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Frontend/Reader.php: -------------------------------------------------------------------------------- 1 | _databaseConnection = Dal::getInstance( 18 | 'main', 19 | Dal::PDO, 20 | $dsn, 21 | $user, 22 | $password 23 | ); 24 | } 25 | 26 | abstract public function read(string $databaseName): Database; 27 | 28 | public function getDatabaseConnection(): Dal 29 | { 30 | return $this->_databaseConnection; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Frontend/Table.php: -------------------------------------------------------------------------------- 1 | _databaseConnection = $databaseConnection; 23 | } 24 | 25 | abstract public function columns(): iterable; 26 | 27 | public function getDatabaseConnection(): Dal 28 | { 29 | return $this->_databaseConnection; 30 | } 31 | 32 | public function accept(Visitor\Visit $visitor, &$handle = null, $eldnah = null) 33 | { 34 | return $visitor->visit($this, $handle, $eldnah); 35 | } 36 | } 37 | --------------------------------------------------------------------------------