Skip to content

Commit

Permalink
Merge pull request #89 from nextras/reflection
Browse files Browse the repository at this point in the history
Reflection updates for schemas
  • Loading branch information
hrach authored Apr 11, 2020
2 parents 1562af6 + d702461 commit b193ac5
Show file tree
Hide file tree
Showing 18 changed files with 245 additions and 140 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/composer.lock
/vendor
/.idea
4 changes: 4 additions & 0 deletions .phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ includes:
- phar://%rootDir%/phpstan.phar/conf/bleedingEdge.neon

parameters:
level: 7
paths:
- src

# never type is not supported: https://github.com/phpstan/phpstan/issues/2297
earlyTerminatingMethodCalls:
Nextras\Dbal\SqlProcessor:
Expand Down
86 changes: 42 additions & 44 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,60 +1,58 @@
language: php

php:
- 7.1
- 7.2
- 7.3
- 7.4
- 7.1
- 7.2
- 7.3
- 7.4

services:
- mysql
- postgresql
- mysql
- postgresql

env:
matrix:
- dependencies=lowest
- dependencies=highest
cache:
directories:
- $HOME/.composer/cache

matrix:
fast_finish: true
before_install:
- phpenv config-rm xdebug.ini || true

cache:
directories:
- $HOME/.composer/cache
# Create php.ini & databases.ini
- cp ./tests/php.unix-sample.ini ./tests/php.ini
- cp ./tests/databases.sample.ini ./tests/databases.ini

# Create MySQL & PostgreSQL database
- psql -c 'CREATE DATABASE nextras_dbal_test' -U postgres
- sleep 2
- mysql -e 'CREATE DATABASE nextras_dbal_test;'
- mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
- mysql -u root -e "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('')"

install:
- travis_retry composer update --no-interaction --prefer-dist --no-progress

jobs:
fast_finish: true
include:
- name: Lowest Dependencies
install:
- travis_retry composer update --prefer-lowest --prefer-stable --no-interaction --prefer-dist --no-progress

before_script:
# Create php.ini & databases.ini
- cp ./tests/php.unix-sample.ini ./tests/php.ini

- if [ "$TRAVIS_PHP_VERSION" == "7.2" ]; then cat ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini >> ./tests/php.ini; fi
- if [ "$TRAVIS_PHP_VERSION" == "7.2" ]; then NTESTER_FLAGS="--coverage ./coverage.xml --coverage-src ./src"; else TESTER_FLAGS=""; fi
- cp ./tests/databases.sample.ini ./tests/databases.ini

# Create MySQL & Postgre database
- psql -c 'CREATE DATABASE nextras_dbal_test' -U postgres
- sleep 2
- mysql -e 'CREATE DATABASE nextras_dbal_test;'
- mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
# https://github.com/travis-ci/travis-ci/issues/6961#issuecomment-264283390
- mysql -u root -e "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('')"

# Install dependencies
- phpenv config-rm xdebug.ini || true
- if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --no-interaction; fi
- if [ "$dependencies" = "highest" ]; then composer update --no-interaction; fi
- if [ "$TRAVIS_PHP_VERSION" == "7.4" ]; then NTESTER_FLAGS="-p phpdbg --coverage ./coverage.xml --coverage-src ./src"; fi

script:
- ./tests/run.sh -s $NTESTER_FLAGS ./tests/cases
- if [ "$TRAVIS_PHP_VERSION" == "7.2" ]; then composer phpstan; fi
- ./tests/run.sh -s $NTESTER_FLAGS ./tests/cases
- if [ "$TRAVIS_PHP_VERSION" == "7.4" ]; then composer phpstan; fi

after_script:
- if [ "$TRAVIS_PHP_VERSION" == "7.2" ]; then
wget https://github.com/satooshi/php-coveralls/releases/download/v2.0.0/php-coveralls.phar
&& php php-coveralls.phar --verbose --config tests/.coveralls.yml
|| true;
fi
- if [ "$TRAVIS_PHP_VERSION" == "7.4" ]; then
wget https://github.com/satooshi/php-coveralls/releases/download/v2.0.0/php-coveralls.phar
&& php php-coveralls.phar --verbose --config tests/.coveralls.yml
|| true;
fi

after_failure:
# Print *.actual content & log content
- for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done
- for i in $(find tests -name \*.log); do echo "--- $i"; cat $i; echo; echo; done
# Print *.actual content & log content
- for i in $(find tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done
- for i in $(find tests -name \*.log); do echo "--- $i"; cat $i; echo; echo; done
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@
"nette/utils": "~3.0",
"nette/finder": "~2.5",
"nette/neon": "~3.0",
"phpstan/phpstan": "0.12.17",
"phpstan/phpstan": "0.12.18",
"phpstan/phpstan-deprecation-rules": "0.12.2",
"tracy/tracy": "~2.7"
},
"autoload": {
"psr-4": { "Nextras\\Dbal\\": "src/" },
"classmap": ["src/exceptions.php"]
},
"scripts": {
"phpstan": "phpstan analyse -l 7 -c .phpstan.neon src"
"phpstan": "phpstan analyze -c .phpstan.neon"
},
"extra": {
"branch-alias": {
Expand Down
5 changes: 3 additions & 2 deletions doc/param-modifiers.texy
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ Other available modifiers:
|* `%multiOr` | OR condition with multiple conditions in pairs
|* `%values`, `%values[]` | expands array for INSERT clause, multi insert
|* `%set` | expands array for SET clause
|* `%table`, `%table[]` | escapes string as table name, surrounding parentheses are not added to `%table[]` modifier; may contain a database or schema name separated by a dot, will be correctly escaped;
|* `%column`, `%column[]` | escapes string as column name, surrounding parentheses are not added to `%column[]` modifier may contain a database name, schema name or asterisk (`*`) separated by a dot, will be correctly escaped;
|* `%table`, `%table[]` | escapes string as table name, may contain a database or schema name separated by a dot; surrounding parentheses are not added to `%table[]` modifier;
|* `%column`, `%column[]` | escapes string as column name, may contain a database name, schema name or asterisk (`*`) separated by a dot; surrounding parentheses are not added to `%column[]` modifier;
|* `%ex` | expands array as processor arguments
|* `%raw` | inserts string argument as is
|* `%%` | escapes to single `%` (useful in `date_format()`, etc.)
|* `[[`, `]]` | escapes to single `[` or `]` (useful when working with array, etc.)

Let's examine `%and` and `%or` behavior. If array key is numeric and its value is an array, value is expanded with `%ex` modifier. (See below.)

Expand Down
6 changes: 3 additions & 3 deletions src/Platforms/CachedPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ public function getName(): string
}


public function getTables(): array
public function getTables(?string $schema = null): array
{
return $this->cache->load(self::CACHE_VERSION . '.tables', function () {
return $this->platform->getTables();
return $this->cache->load(self::CACHE_VERSION . '.tables.' . $schema , function () use ($schema) {
return $this->platform->getTables($schema);
});
}

Expand Down
3 changes: 2 additions & 1 deletion src/Platforms/IPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ public function getName(): string;

/**
* Returns list of tables names indexed by FQN table name.
* If no schema is provided, uses current schema name (search path).
* @return Table[]
* @phpstan-return array<string, Table>
*/
public function getTables(): array;
public function getTables(?string $schema = null): array;


/**
Expand Down
6 changes: 3 additions & 3 deletions src/Platforms/MySqlPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ public function getName(): string


/** @inheritDoc */
public function getTables(): array
public function getTables(?string $schema = null): array
{
$result = $this->connection->query('
SELECT
TABLE_SCHEMA,
TABLE_NAME,
TABLE_TYPE
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
');
WHERE TABLE_SCHEMA = COALESCE(%?s, DATABASE())
', $schema);

$tables = [];
foreach ($result as $row) {
Expand Down
26 changes: 14 additions & 12 deletions src/Platforms/PostgreSqlPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public function getName(): string


/** @inheritDoc */
public function getTables(): array
public function getTables(?string $schema = null): array
{
$result = $this->connection->query("
SELECT
Expand All @@ -46,10 +46,12 @@ public function getTables(): array
JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace
WHERE
c.relkind IN ('r', 'v')
AND n.nspname = ANY (pg_catalog.current_schemas(FALSE))
AND n.nspname = ANY (
CASE %?s IS NULL WHEN true THEN pg_catalog.current_schemas(FALSE) ELSE ARRAY[[%?s]] END
)
ORDER BY
c.relname
");
", $schema, $schema);

$tables = [];
foreach ($result as $row) {
Expand All @@ -70,13 +72,13 @@ public function getColumns(string $table): array
$result = $this->connection->query("
SELECT
a.attname::varchar AS name,
upper(t.typname) AS type,
UPPER(t.typname) AS type,
CASE WHEN a.atttypmod = -1 THEN NULL ELSE a.atttypmod -4 END AS size,
pg_catalog.pg_get_expr(ad.adbin, 'pg_catalog.pg_attrdef'::regclass)::varchar AS default,
coalesce(co.contype = 'p', FALSE) AS is_primary,
coalesce(co.contype = 'p' AND strpos(pg_get_expr(ad.adbin, ad.adrelid), 'nextval') = 1, FALSE) AS is_autoincrement,
COALESCE(co.contype = 'p', FALSE) AS is_primary,
COALESCE(co.contype = 'p' AND strpos(pg_get_expr(ad.adbin, ad.adrelid), 'nextval') = 1, FALSE) AS is_autoincrement,
NOT (a.attnotnull OR t.typtype = 'd' AND t.typnotnull) AS is_nullable,
substring(pg_catalog.pg_get_expr(ad.adbin, 'pg_catalog.pg_attrdef'::regclass) from %s) AS sequence
SUBSTRING(pg_catalog.pg_get_expr(ad.adbin, 'pg_catalog.pg_attrdef'::regclass) FROM %s) AS sequence
FROM
pg_catalog.pg_attribute AS a
JOIN pg_catalog.pg_class AS c ON a.attrelid = c.oid
Expand All @@ -85,7 +87,7 @@ public function getColumns(string $table): array
LEFT JOIN pg_catalog.pg_constraint AS co ON co.connamespace = c.relnamespace AND contype = 'p' AND co.conrelid = c.oid AND a.attnum = ANY(co.conkey)
WHERE
c.relkind IN ('r', 'v')
AND c.oid = '%column'::regclass
AND c.oid = '%table'::regclass
AND a.attnum > 0
AND NOT a.attisdropped
ORDER BY
Expand All @@ -96,7 +98,7 @@ public function getColumns(string $table): array
foreach ($result as $row) {
$column = new Column();
$column->name = (string) $row->name;
$column->type = (string) $row->type;
$column->type = (string) $row->type;
$column->size = $row->size !== null ? (int) $row->size : null;
$column->default = $row->default !== null ? (string) $row->default : null;
$column->isPrimary = (bool) $row->is_primary;
Expand Down Expand Up @@ -128,12 +130,12 @@ public function getForeignKeys(string $table): array
JOIN pg_catalog.pg_class AS clf ON co.confrelid = clf.oid
JOIN pg_catalog.pg_namespace AS ns ON ns.oid = cl.relnamespace
JOIN pg_catalog.pg_namespace AS nsf ON nsf.oid = clf.relnamespace
JOIN pg_catalog.pg_attribute AS at ON at.attrelid = cl.oid AND at.attnum = %raw
JOIN pg_catalog.pg_attribute AS atf ON atf.attrelid = clf.oid AND atf.attnum = %raw
JOIN pg_catalog.pg_attribute AS at ON at.attrelid = cl.oid AND at.attnum = co.conkey[[1]]
JOIN pg_catalog.pg_attribute AS atf ON atf.attrelid = clf.oid AND atf.attnum = co.confkey[[1]]
WHERE
co.contype = 'f'
AND cl.oid = '%column'::regclass
", 'co.conkey[1]', 'co.confkey[1]', $table);
", $table);

$keys = [];
foreach ($result as $row) {
Expand Down
24 changes: 18 additions & 6 deletions src/Platforms/SqlServerPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use Nextras\Dbal\Platforms\Data\Column;
use Nextras\Dbal\Platforms\Data\ForeignKey;
use Nextras\Dbal\Platforms\Data\Table;
use function count;
use function explode;


class SqlServerPlatform implements IPlatform
Expand All @@ -33,13 +35,14 @@ public function getName(): string


/** @inheritDoc */
public function getTables(): array
public function getTables(?string $schema = null): array
{
$result = $this->connection->query("
SELECT TABLE_NAME, TABLE_TYPE, TABLE_SCHEMA
FROM information_schema.tables
WHERE TABLE_SCHEMA = COALESCE(%?s, SCHEMA_NAME())
ORDER BY TABLE_NAME
");
", $schema);

$tables = [];
foreach ($result as $row) {
Expand All @@ -57,6 +60,14 @@ public function getTables(): array
/** @inheritDoc */
public function getColumns(string $table): array
{
$parts = explode('.', $table);
if (count($parts) === 2) {
$schema = $parts[0];
$table = $parts[1];
} else {
$schema = null;
}

$result = $this->connection->query("
SELECT
[a].[COLUMN_NAME] AS [name],
Expand All @@ -73,7 +84,7 @@ public function getColumns(string $table): array
ELSE CONVERT(BIT, 0)
END AS [is_primary],
CONVERT(
BIT, COLUMNPROPERTY(object_id([a].[TABLE_NAME]), [a].[COLUMN_NAME], 'IsIdentity')
BIT, COLUMNPROPERTY(object_id(CONCAT([a].[TABLE_SCHEMA], '.', [a].[TABLE_NAME])), [a].[COLUMN_NAME], 'IsIdentity')
) AS [is_autoincrement],
CASE
WHEN [a].[IS_NULLABLE] = 'YES'
Expand All @@ -93,8 +104,9 @@ public function getColumns(string $table): array
[b].[COLUMN_NAME] = [a].[COLUMN_NAME]
)
WHERE [a].[TABLE_NAME] = %s
AND [a].[TABLE_SCHEMA] = COALESCE(%?s, SCHEMA_NAME())
ORDER BY [a].[ORDINAL_POSITION]
", $table);
", $table, $schema);

$columns = [];
foreach ($result as $row) {
Expand All @@ -119,8 +131,8 @@ public function getColumns(string $table): array
/** @inheritDoc */
public function getForeignKeys(string $table): array
{
$parts = \explode('.', $table);
if (\count($parts) === 2) {
$parts = explode('.', $table);
if (count($parts) === 2) {
$schema = $parts[0];
$table = $parts[1];
} else {
Expand Down
14 changes: 10 additions & 4 deletions src/SqlProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public function process(array $args): string

$i = $j;
$fragments[] = preg_replace_callback(
'#%(\??+\w++(?:\[\]){0,2}+)|(%%)|\[(.+?)\]#S', // %modifier | %% | [identifier]
'#%(\??+\w++(?:\[\]){0,2}+)|(%%)|(\[\[)|(\]\])|\[(.+?)\]#S', // %modifier | %% | %[ | %] | [identifier]
function ($matches) use ($args, &$j, $last) {
if ($matches[1] !== '') {
if ($j === $last) {
Expand All @@ -116,11 +116,17 @@ function ($matches) use ($args, &$j, $last) {
} elseif ($matches[2] !== '') {
return '%';

} elseif (!ctype_digit($matches[3])) {
return $this->identifierToSql($matches[3]);
} elseif ($matches[3] !== '') {
return '[';

} elseif ($matches[4] !== '') {
return ']';

} elseif (!ctype_digit($matches[5])) {
return $this->identifierToSql($matches[5]);

} else {
return "[$matches[3]]";
return "[$matches[5]]";
}
},
$args[$i]
Expand Down
Loading

0 comments on commit b193ac5

Please sign in to comment.