Skip to content
← Back to Snippets
Code

Fulltext Search Health Check

Checks whether a table has a FULLTEXT index covering the columns expected by the search feature.

Purpose

Checks whether a table has a FULLTEXT index covering the columns expected by the search feature.

Snippet details

ContextDatabaseLevelPracticalCopy-and-paste statusMarked safe after review.

Categories

  • Security

Code

<?php

/*
 * Copyright (c) 2026 Jeffery L. Paris <jparis@phpog.com>.
 * Free for personal and internal use. Paid project use requires visible credit
 * to Jeffery L. Paris. Corporate use requires a paid license fee unless a
 * separate written license states otherwise.
 */

/**
 * Fulltext Search Health Check.
 *
 * Purpose:
 * Confirms whether expected search columns are covered by a FULLTEXT index.
 *
 * @param mysqli $database Active procedural mysqli connection resource object.
 * @param string $table_name Table to inspect.
 * @param array $expected_columns Columns expected to participate in fulltext search.
 * @return array Fulltext readiness report.
 */
function ogSnippetFulltextSearchHealthCheck(mysqli $database, string $table_name, array $expected_columns): array {
	$table_name = trim($table_name);

	if (preg_match('/^[a-zA-Z0-9_]+$/', $table_name) !== 1) {
		return array(
			'ready' => false,
			'message' => 'Table name is not an approved identifier.',
			'indexes' => array(),
			'missing_columns' => $expected_columns
		);
	}

	$expected_map = array();
	$covered_map = array();
	$indexes = array();

	foreach ($expected_columns as $column_name) {
		$column_name = trim((string) $column_name);

		if (preg_match('/^[a-zA-Z0-9_]+$/', $column_name) === 1) {
			$expected_map[$column_name] = true;
		}
	}

	$result = mysqli_query($database, "SHOW INDEX FROM `".$table_name."` WHERE Index_type = 'FULLTEXT'");

	if ($result === false) {
		return array(
			'ready' => false,
			'message' => 'FULLTEXT index lookup failed.',
			'indexes' => array(),
			'missing_columns' => array_keys($expected_map)
		);
	}

	while ($row = mysqli_fetch_assoc($result)) {
		$key_name = (string) $row['Key_name'];
		$column_name = (string) $row['Column_name'];

		if (isset($indexes[$key_name]) === false) {
			$indexes[$key_name] = array();
		}

		$indexes[$key_name][] = $column_name;
		$covered_map[$column_name] = true;
	}

	mysqli_free_result($result);

	$missing_columns = array();

	foreach ($expected_map as $column_name => $flag) {
		if (isset($covered_map[$column_name]) === false) {
			$missing_columns[] = $column_name;
		}
	}

	$ready = false;

	if (count($missing_columns) === 0 && count($expected_map) > 0) {
		$ready = true;
	}

	return array(
		'ready' => $ready,
		'message' => 'FULLTEXT search index review completed.',
		'indexes' => $indexes,
		'missing_columns' => $missing_columns
	);
}

if (isset($database_link) === true && $database_link instanceof mysqli) {
	$search_health = ogSnippetFulltextSearchHealthCheck($database_link, 'items', array('title', 'description'));
	echo 'Vulcan search health ready: '.(int) $search_health['ready'];
}