Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@
- Chg #1002: Remove specific condition interfaces (@vjik)
- Chg #1003, #1006: Refactor namespace of condition objects and use promoted properties instead of getters (@vjik)
- Enh #1010: Improve `Quoter::getTableNameParts()` method (@Tigrov)
- Enh #1011: Refactor `TableSchemaInterface` and `AbstractSchema` (@Tigrov)
- Enh #1011: Remove `AbstractTableSchema` and add `TableSchema` instead (@Tigrov)

## 1.3.0 March 21, 2024

Expand Down
4 changes: 4 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace
- `AbstractSchema::getResultColumnCacheKey()` - returns the cache key for the column metadata received from the query;
- `AbstractSchema::loadResultColumn()` - creates a new column instance according to the column metadata from the query;
- `DataReaderInterface::typecastColumns()` - sets columns for type casting the query results;
- `AbstractSchema::resolveFullName()` - resolves the full name of the table, view, index, etc.;
- `AbstractSchema::clarifyFullName()` - clarifies the full name of the table, view, index, etc.;

### Remove methods

Expand All @@ -175,6 +177,7 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace
- `TableSchemaInterface::compositeForeignKey()`;
- `SchemaInterface::createColumn()` - use `ColumnBuilder` instead;
- `SchemaInterface::isReadQuery()` - use `DbStringHelper::isReadQuery()` instead;
- `AbstractSchema::resolveTableName()` - use `QuoterInterface::getTableNameParts()` instead;
- `SchemaInterface::getRawTableName()` - use `QuoterInterface::getRawTableName()` instead;
- `AbstractSchema::isReadQuery()` - use `DbStringHelper::isReadQuery()` instead;
- `AbstractSchema::getRawTableName()` - use `QuoterInterface::getRawTableName()` instead;
Expand Down Expand Up @@ -262,3 +265,4 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace
- Change namespace of condition and condition builder classes;
- Remove `AbstractDsn` and `AbstractDsnSocket` classes and `DsnInterface` interface;
- Remove `Hash` condition;
- Remove `AbstractTableSchema` and add `TableSchema` instead;
2 changes: 1 addition & 1 deletion src/Driver/Pdo/AbstractPdoSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ protected function generateCacheKey(): array

protected function getCacheKey(string $name): array
{
return [static::class, ...$this->generateCacheKey(), $this->db->getQuoter()->getRawTableName($name)];
return [static::class, ...$this->generateCacheKey(), $name];
}

protected function getCacheTag(): string
Expand Down
2 changes: 1 addition & 1 deletion src/Expression/Expression.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
* ```php
* $expression = new Expression('NOW()');
* $now = (new \Yiisoft\Db\Query\Query)->select($expression)->scalar(); // SELECT NOW();
* $now = $db->select($expression)->scalar(); // SELECT NOW();
* echo $now; // prints the current date
* ```
*
Expand Down
165 changes: 107 additions & 58 deletions src/Schema/AbstractSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Yiisoft\Db\Constant\GettypeResult;
use Yiisoft\Db\Schema\Column\ColumnInterface;

use function array_filter;
use function array_key_exists;
use function gettype;
use function is_array;
Expand All @@ -34,9 +35,9 @@ abstract class AbstractSchema implements SchemaInterface
protected const SCHEMA_CACHE_VERSION = 1;
protected const CACHE_VERSION = 'cacheVersion';
/**
* @var string|null $defaultSchema The default schema name used for the current session.
* @var string $defaultSchema The default schema name used for the current session.
*/
protected string|null $defaultSchema = null;
protected string $defaultSchema = '';
/**
* @var (ColumnInterface|null)[] Saved columns from query results.
* @psalm-var array<string, ColumnInterface|null>
Expand All @@ -48,7 +49,7 @@ abstract class AbstractSchema implements SchemaInterface
private array $schemaNames = [];
/** @var string[][] */
private array $tableNames = [];
/** @var ((Check|DefaultValue|ForeignKey|Index)[]|Index|TableSchemaInterface|null)[][] */
/** @var (Check[]|DefaultValue[]|ForeignKey[]|Index|Index[]|TableSchemaInterface|null)[][] */
private array $tableMetadata = [];

public function __construct(protected ConnectionInterface $db, private SchemaCache $schemaCache)
Expand Down Expand Up @@ -106,7 +107,7 @@ abstract protected function loadTableDefaultValues(string $tableName): array;
*
* @param string $tableName The table name.
*
* @return ForeignKey[] The foreign keys for the given table.
* @return ForeignKey[] The foreign keys for the given table, indexed by constraint name.
*/
abstract protected function loadTableForeignKeys(string $tableName): array;

Expand All @@ -119,24 +120,6 @@ abstract protected function loadTableForeignKeys(string $tableName): array;
*/
abstract protected function loadTableIndexes(string $tableName): array;

/**
* Loads a primary key for the given table.
*
* @param string $tableName The table name.
*
* @return Index|null The primary key for the given table. `null` if the table has no primary key.
*/
abstract protected function loadTablePrimaryKey(string $tableName): Index|null;

/**
* Loads all unique constraints for the given table.
*
* @param string $tableName The table name.
*
* @return Index[] The unique constraints for the given table.
*/
abstract protected function loadTableUniques(string $tableName): array;

/**
* Loads the metadata for the specified table.
*
Expand All @@ -146,7 +129,7 @@ abstract protected function loadTableUniques(string $tableName): array;
*/
abstract protected function loadTableSchema(string $name): TableSchemaInterface|null;

public function getDefaultSchema(): string|null
public function getDefaultSchema(): string
{
return $this->defaultSchema;
}
Expand Down Expand Up @@ -244,25 +227,25 @@ public function getSchemaUniques(string $schema = '', bool $refresh = false): ar
public function getTableChecks(string $name, bool $refresh = false): array
{
/** @var Check[] */
return $this->getTableMetadata($name, SchemaInterface::CHECKS, $refresh);
return $this->getTableMetadata($this->clearFullName($name), SchemaInterface::CHECKS, $refresh);
}

public function getTableDefaultValues(string $name, bool $refresh = false): array
{
/** @var DefaultValue[] */
return $this->getTableMetadata($name, SchemaInterface::DEFAULT_VALUES, $refresh);
return $this->getTableMetadata($this->clearFullName($name), SchemaInterface::DEFAULT_VALUES, $refresh);
}

public function getTableForeignKeys(string $name, bool $refresh = false): array
{
/** @var ForeignKey[] */
return $this->getTableMetadata($name, SchemaInterface::FOREIGN_KEYS, $refresh);
return $this->getTableMetadata($this->clearFullName($name), SchemaInterface::FOREIGN_KEYS, $refresh);
}

public function getTableIndexes(string $name, bool $refresh = false): array
{
/** @var Index[] */
return $this->getTableMetadata($name, SchemaInterface::INDEXES, $refresh);
return $this->getTableMetadata($this->clearFullName($name), SchemaInterface::INDEXES, $refresh);
}

public function getTableNames(string $schema = '', bool $refresh = false): array
Expand All @@ -276,14 +259,31 @@ public function getTableNames(string $schema = '', bool $refresh = false): array

public function getTablePrimaryKey(string $name, bool $refresh = false): Index|null
{
/** @var Index|null */
return $this->getTableMetadata($name, SchemaInterface::PRIMARY_KEY, $refresh);
foreach ($this->getTableIndexes($name, $refresh) as $index) {
if ($index->isPrimaryKey) {
return $index;
}
}

return null;
}

public function getTableSchema(string $name, bool $refresh = false): TableSchemaInterface|null
{
$rawName = $this->clearFullName($name);

if ($refresh) {
// Some constraints are loading and caching together.
// Reset the table constraint cache to load them without refreshing.
$this->tableMetadata[$rawName] = [];

if ($this->schemaCache->isEnabled()) {
$this->schemaCache->remove($this->getCacheKey($rawName));
}
}

/** @var TableSchemaInterface|null */
return $this->getTableMetadata($name, SchemaInterface::SCHEMA, $refresh);
return $this->getTableMetadata($rawName, SchemaInterface::SCHEMA, $refresh);
}

public function getTableSchemas(string $schema = '', bool $refresh = false): array
Expand All @@ -294,8 +294,10 @@ public function getTableSchemas(string $schema = '', bool $refresh = false): arr

public function getTableUniques(string $name, bool $refresh = false): array
{
/** @var Index[] */
return $this->getTableMetadata($name, SchemaInterface::UNIQUES, $refresh);
return array_filter(
$this->getTableIndexes($name, $refresh),
static fn (Index $index): bool => $index->isUnique
);
}

public function refresh(): void
Expand All @@ -310,7 +312,7 @@ public function refresh(): void

public function refreshTableSchema(string $name): void
{
$rawName = $this->db->getQuoter()->getRawTableName($name);
$rawName = $this->clearFullName($name);

unset($this->tableMetadata[$rawName]);

Expand All @@ -326,6 +328,23 @@ public function enableCache(bool $value): void
$this->schemaCache->setEnabled($value);
}

/**
* @deprecated Use {@see getTableUniques()}. Will be removed in version 3.0
*/
public function findUniqueIndexes(TableSchemaInterface $table): array
{
$uniques = [];
$indexes = $this->getTableIndexes($table->getFullName());

foreach ($indexes as $index) {
if ($index->isUnique) {
$uniques[$index->name] = $index->columnNames;
}
}

return $uniques;
}

/**
* Returns all schema names in the database, including the default one but not system schemas.
*
Expand Down Expand Up @@ -367,7 +386,7 @@ protected function findTableNames(string $schema): array
* @param bool $refresh Whether to fetch the latest available table metadata. If this is `false`, cached data may be
* returned if available.
*
* @return (Check|DefaultValue|ForeignKey|Index)[][]|Index[]|TableSchemaInterface[] The metadata of the given type for all
* @return Check[][]|DefaultValue[][]|ForeignKey[][]|Index[]|Index[][]|TableSchemaInterface[] The metadata of the given type for all
* tables in the given schema.
*/
protected function getSchemaMetadata(string $schema, string $type, bool $refresh): array
Expand Down Expand Up @@ -401,39 +420,44 @@ protected function getSchemaMetadata(string $schema, string $type, bool $refresh
* @param string $type The metadata type.
* @param bool $refresh whether to reload the table metadata even if it's found in the cache.
*
* @return (Check|DefaultValue|ForeignKey|Index)[]|Index|TableSchemaInterface|null The metadata of the given type
* @return Check[]|DefaultValue[]|ForeignKey[]|Index|Index[]|TableSchemaInterface|null The metadata of the given type
* for the given table.
*
* @psalm-return (
* $type is SchemaInterface::CHECKS ? Check[] :
* $type is SchemaInterface::DEFAULT_VALUES ? DefaultValue[] :
* $type is SchemaInterface::FOREIGN_KEYS ? ForeignKey[] :
* $type is SchemaInterface::INDEXES ? Index[] :
* $type is SchemaInterface::PRIMARY_KEY ? Index|null :
* TableSchemaInterface
* )
*/
protected function getTableMetadata(
string $name,
string $type,
bool $refresh = false,
): array|Index|TableSchemaInterface|null {
$rawName = $this->db->getQuoter()->getRawTableName($name);

if (!isset($this->tableMetadata[$rawName])) {
$this->loadTableMetadataFromCache($rawName);
if (!isset($this->tableMetadata[$name])) {
$this->loadTableMetadataFromCache($name);
}

if ($refresh || !isset($this->tableMetadata[$rawName][$type])) {
$this->tableMetadata[$rawName][$type] = $this->loadTableTypeMetadata($type, $rawName);
$this->saveTableMetadataToCache($rawName);
if ($refresh || !isset($this->tableMetadata[$name][$type])) {
$this->tableMetadata[$name][$type] = $this->loadTableTypeMetadata($type, $name);
$this->saveTableMetadataToCache($name);
}

return $this->tableMetadata[$rawName][$type];
return $this->tableMetadata[$name][$type];
}

/**
* This method returns the desired metadata type for the table name.
*
* @return (Check|DefaultValue|ForeignKey|Index)[]|Index|TableSchemaInterface|null
* @return Check[]|DefaultValue[]|ForeignKey[]|Index[]|TableSchemaInterface|null
*/
protected function loadTableTypeMetadata(string $type, string $name): array|Index|TableSchemaInterface|null
protected function loadTableTypeMetadata(string $type, string $name): array|TableSchemaInterface|null
{
return match ($type) {
SchemaInterface::SCHEMA => $this->loadTableSchema($name),
SchemaInterface::PRIMARY_KEY => $this->loadTablePrimaryKey($name),
SchemaInterface::UNIQUES => $this->loadTableUniques($name),
SchemaInterface::FOREIGN_KEYS => $this->loadTableForeignKeys($name),
SchemaInterface::INDEXES => $this->loadTableIndexes($name),
SchemaInterface::DEFAULT_VALUES => $this->loadTableDefaultValues($name),
Expand All @@ -445,7 +469,7 @@ protected function loadTableTypeMetadata(string $type, string $name): array|Inde
/**
* This method returns the desired metadata type for table name (with refresh if needed).
*
* @return (Check|DefaultValue|ForeignKey|Index)[]|Index|TableSchemaInterface|null
* @return Check[]|DefaultValue[]|ForeignKey[]|Index|Index[]|TableSchemaInterface|null
*/
protected function getTableTypeMetadata(
string $type,
Expand All @@ -465,25 +489,50 @@ protected function getTableTypeMetadata(
}

/**
* Resolves the table name and schema name (if any).
*
* @param string $name The table name.
*
* @throws NotSupportedException If the DBMS doesn't support this method.
* Clears the full name. Removes the schema name if it is the default schema name, removes curly brackets
* from the name, and replaces the percentage character '%' with {@see ConnectionInterface::getTablePrefix()}.
*/
protected function clearFullName(string $fullName): string
{
return $this->resolveFullName(...$this->db->getQuoter()->getTableNameParts($fullName));
}

/**
* Find and initialize table constraints.
*
* @return TableSchemaInterface The with resolved table, schema, etc. names.
* @param TableSchemaInterface $table The table metadata.
*/
protected function findConstraints(TableSchemaInterface $table): void
{
$tableName = $this->resolveFullName($table->getName(), $table->getSchemaName());

$table->checks(...$this->getTableMetadata($tableName, SchemaInterface::CHECKS));
$table->defaultValues(...$this->getTableMetadata($tableName, SchemaInterface::DEFAULT_VALUES));
$table->foreignKeys(...$this->getTableMetadata($tableName, SchemaInterface::FOREIGN_KEYS));
$table->indexes(...$this->getTableMetadata($tableName, SchemaInterface::INDEXES));
}

/**
* Resolves the full table name, considering the default schema name. Removes curly brackets from the names,
* and replaces the percentage character '%' with {@see ConnectionInterface::getTablePrefix()}.
*/
protected function resolveTableName(string $name): TableSchemaInterface
protected function resolveFullName(string $name, string $schemaName = ''): string
{
throw new NotSupportedException(static::class . ' does not support resolving table names.');
$quoter = $this->db->getQuoter();
$rawName = $quoter->getRawTableName($name);

return match ($schemaName) {
'', $this->defaultSchema => $rawName,
default => $quoter->getRawTableName($schemaName) . ".$rawName",
};
}

/**
* Sets the metadata of the given type for the given table.
*
* @param string $rawName The raw table name.
* @param string $type The metadata type.
* @param (Check|DefaultValue|ForeignKey|Index)[]|Index|TableSchemaInterface|null $data The metadata to set.
* @param Check[]|DefaultValue[]|ForeignKey[]|Index|Index[]|TableSchemaInterface|null $data The metadata to set.
*/
protected function setTableMetadata(
string $rawName,
Expand Down Expand Up @@ -515,7 +564,7 @@ private function loadTableMetadataFromCache(string $rawName): void
}

unset($metadata[self::CACHE_VERSION]);
/** @var ((Check|DefaultValue|ForeignKey|Index)[]|Index|TableSchemaInterface|null)[] $metadata */
/** @var (Check[]|DefaultValue[]|ForeignKey[]|Index|Index[]|TableSchemaInterface|null)[] $metadata */
$this->tableMetadata[$rawName] = $metadata;
}

Expand Down
Loading
Loading