$columnList * @param bool $unique * @return string|null */ public static function signature(array $columnList, bool $unique): ?string { if (empty($columnList)) { return null; } $parts = array(); foreach ($columnList as $column) { $name = isset($column['column']) && is_scalar($column['column']) ? strtolower(trim((string)$column['column'])) : ''; if ($name === '') { return null; } $prefix = isset($column['prefix']) ? '(' . (int)$column['prefix'] . ')' : ''; $parts[] = $name . $prefix; } return ($unique ? 'u:' : 'n:') . implode(',', $parts); } /** * The signature of a DDL spec produced by * {@see ABJ_404_Solution_CreateTableIndexParser::fromCreateTableSql()}, or * NULL when that spec's column fragment did not parse -- a truncated or * corrupted create*Table.sql, which defensive philosophy #7 says to expect. * * @param array{name: string, columns: string, unique: bool} $spec * @return string|null */ public static function signatureOfDdlSpec(array $spec): ?string { return self::signature( ABJ_404_Solution_CreateTableIndexParser::ddlColumnList( isset($spec['columns']) ? (string)$spec['columns'] : ''), !empty($spec['unique']) ); } /** * The signature of a live definition produced by * {@see ABJ_404_Solution_TableIndexDefinitions::readLive()}, or NULL when * the engine's description of it was incomplete. * * The describability check is repeated here rather than left to the caller * on purpose: a call site that forgets it gets a comparable string for an * index nobody described, and the only thing it can do with the resulting * mismatch is rewrite the table. Making the undescribable case impossible to * ask a comparable question about is cheaper than auditing every future * caller for the gate. * * @param array{name?: string, columns?: array, unique?: bool, describable?: bool} $definition * @return string|null */ public static function signatureOfLiveDefinition(array $definition): ?string { if (!ABJ_404_Solution_TableIndexDefinitions::isDescribable($definition)) { return null; } $columns = isset($definition['columns']) && is_array($definition['columns']) ? $definition['columns'] : array(); return self::signature($columns, !empty($definition['unique'])); } /** * Whether a live index has been ESTABLISHED to differ from the DDL that * declares it. * * The one question the repair path acts on. Collapsing "could not be * established" into "not drifted" happens here, once, where the two * signatures are read, so that no caller has to re-derive that null is not * a difference. Comparing the signatures directly is exactly the mistake * this method exists to prevent: PHP reports null !== 'n:url(190)' as a * difference, and that difference is a table rewrite. * * @param array{columns?: array, unique?: bool, describable?: bool} $liveDefinition * @param array{name: string, columns: string, unique: bool} $ddlSpec * @return bool */ public static function isDriftedFromDdlSpec(array $liveDefinition, array $ddlSpec): bool { $liveSignature = self::signatureOfLiveDefinition($liveDefinition); $goalSignature = self::signatureOfDdlSpec($ddlSpec); if ($liveSignature === null || $goalSignature === null) { return false; } return $liveSignature !== $goalSignature; } }