Fix manticore indexes check

This commit is contained in:
DariusIII
2026-07-15 15:31:00 +02:00
parent 6210b86e8e
commit 091b4b1265
4 changed files with 127 additions and 10 deletions
+2 -2
View File
@@ -37,8 +37,8 @@ final class InspectManticore extends Command
}
$actual = $client->table($table)->describe();
$comparison = ManticoreSchemaInspector::compareColumns(is_array($actual) ? $actual : [], $definition['columns']);
$actualSettings = $client->sql("SHOW TABLE {$table} SETTINGS", true);
$missingSettings = ManticoreSchemaInspector::missingSettings($actualSettings, $definition['settings']);
$actualSettings = $client->tables()->settings(['table' => $table]);
$missingSettings = ManticoreSchemaInspector::missingSettings($actualSettings, ManticoreIndexRegistry::inspectableSettings());
$needsRebuild = $comparison['missing'] !== [] || $comparison['incompatible'] !== [] || $missingSettings !== [];
$report['tables'][$table] = [...$comparison, 'missing_settings' => $missingSettings, 'needs_rebuild' => $needsRebuild];
$report['compatible'] = $report['compatible'] && ! $needsRebuild;
@@ -83,6 +83,14 @@ final class ManticoreIndexRegistry
};
}
/** @return array<string, int|string> */
public static function inspectableSettings(): array
{
// Manticore 28 omits defaults/no-op settings such as min_prefix_len=0
// and exact_words from SHOW TABLE ... SETTINGS / SHOW CREATE TABLE.
return ['min_infix_len' => 2, 'index_field_lengths' => 1];
}
/** @param array<string, string> $configuredIndexes */
public static function logicalName(string $table, array $configuredIndexes = []): ?string
{
@@ -12,24 +12,24 @@ final class ManticoreSchemaInspector
*/
public static function missingSettings(mixed $actual, array $expected): array
{
$encoded = strtolower((string) json_encode($actual));
$settings = self::normalizeSettings($actual);
return array_values(array_filter(array_keys($expected), static function (string $setting) use ($encoded, $expected): bool {
return ! str_contains($encoded, strtolower($setting))
|| ! str_contains($encoded, strtolower((string) $expected[$setting]));
return array_values(array_filter(array_keys($expected), static function (string $setting) use ($settings, $expected): bool {
return ! array_key_exists(strtolower($setting), $settings)
|| $settings[strtolower($setting)] !== strtolower((string) $expected[$setting]);
}));
}
/**
* @param array<int, array<string, mixed>> $actual
* @param array<int|string, array<string, mixed>> $actual
* @param array<string, array<string, mixed>> $expected
* @return array{missing: list<string>, incompatible: array<string, array{expected: string, actual: string}>}
*/
public static function compareColumns(array $actual, array $expected): array
{
$types = [];
foreach ($actual as $column) {
$name = (string) ($column['Field'] ?? $column['field'] ?? '');
foreach ($actual as $key => $column) {
$name = (string) ($column['Field'] ?? $column['field'] ?? (is_string($key) ? $key : ''));
$type = strtolower((string) ($column['Type'] ?? $column['type'] ?? ''));
if ($name !== '') {
$types[$name] = $type;
@@ -42,11 +42,67 @@ final class ManticoreSchemaInspector
$expectedType = strtolower((string) $definition['type']);
if (! isset($types[$name])) {
$missing[] = $name;
} elseif (! str_contains($types[$name], $expectedType)) {
} elseif (! self::typesAreCompatible($types[$name], $expectedType)) {
$incompatible[$name] = ['expected' => $expectedType, 'actual' => $types[$name]];
}
}
return ['missing' => $missing, 'incompatible' => $incompatible];
}
private static function typesAreCompatible(string $actual, string $expected): bool
{
if ($expected === 'integer') {
return str_contains($actual, 'integer') || str_contains($actual, 'uint');
}
return str_contains($actual, $expected);
}
/** @return array<string, string> */
private static function normalizeSettings(mixed $actual): array
{
if (! is_array($actual)) {
return [];
}
$settings = [];
foreach ($actual as $key => $value) {
if (is_string($key) && ! is_array($value)) {
if (strtolower($key) === 'settings' && is_string($value)) {
foreach (preg_split('/\R/', $value) ?: [] as $line) {
if (preg_match('/^\s*([a-z0-9_]+)\s*=\s*[\'\"]?([^\'\"]+?)[\'\"]?\s*$/i', $line, $match) === 1) {
$settings[strtolower($match[1])] = strtolower(trim($match[2]));
}
}
continue;
}
$settings[strtolower($key)] = strtolower((string) $value);
continue;
}
if (! is_array($value)) {
continue;
}
$name = $value['Setting_name'] ?? $value['setting_name'] ?? $value['Variable_name'] ?? $value['variable_name'] ?? $value['Name'] ?? $value['name'] ?? null;
$settingValue = $value['Value'] ?? $value['value'] ?? null;
if (is_string($name) && $settingValue !== null) {
$settings[strtolower($name)] = strtolower((string) $settingValue);
continue;
}
if (is_string($key)) {
$nestedValue = $value['Value'] ?? $value['value'] ?? reset($value);
if (is_scalar($nestedValue)) {
$settings[strtolower($key)] = strtolower((string) $nestedValue);
}
}
}
return $settings;
}
}
+53
View File
@@ -56,6 +56,59 @@ final class ManticoreIndexRegistryTest extends TestCase
self::assertSame('bigint', $result['incompatible']['passwordstatus']['expected']);
}
#[Test]
public function it_accepts_the_php_clients_associative_describe_response(): void
{
$result = ManticoreSchemaInspector::compareColumns(
[
'passwordstatus' => ['Type' => 'bigint', 'Properties' => []],
'haspreview' => ['Type' => 'bigint', 'Properties' => []],
],
['passwordstatus' => ['type' => 'bigint'], 'haspreview' => ['type' => 'bigint']]
);
self::assertSame([], $result['missing']);
self::assertSame([], $result['incompatible']);
}
#[Test]
public function it_treats_manticore_uint_as_the_integer_schema_type(): void
{
$result = ManticoreSchemaInspector::compareColumns(
['categories_id' => ['Type' => 'uint']],
['categories_id' => ['type' => 'integer']]
);
self::assertSame([], $result['incompatible']);
}
#[Test]
public function it_compares_settings_by_name_and_value(): void
{
$actual = [
'min_infix_len' => ['Value' => '2'],
'exact_words' => ['Value' => '1'],
'index_field_lengths' => ['Value' => '1'],
];
self::assertSame([], ManticoreSchemaInspector::missingSettings($actual, [
'min_infix_len' => 2,
'exact_words' => 1,
'index_field_lengths' => 1,
]));
}
#[Test]
public function it_parses_manticore_28_bundled_settings_response(): void
{
$actual = ['settings' => "min_infix_len = 2\nindex_field_lengths = 1"];
self::assertSame([], ManticoreSchemaInspector::missingSettings(
$actual,
ManticoreIndexRegistry::inspectableSettings()
));
}
#[Test]
public function benchmark_fixture_is_versioned_and_covers_every_table(): void
{