Skip to content
← Back to Functions
Code

Index Recommendation Reporter

Analyzes common filters/sorts and suggests useful indexes.

Function signature

ogRecommendIndexes(usage = array(), schema = array(), options = array())

Categories

  • Database Integrity

Parameters

usageQuery usage rows keyed by table or as table/columns rows.schemaExisting schema/index metadata. Recognized keys: `indexes`.optionsOptional minimum usage threshold. Recognized keys: `minimum_uses`.

Return value

Public-safe status string returned by the function.

  • success
  • message
  • data

Compatibility

Existing function name, slug, path, and call order preserved; advertised metadata corrected to the actual source behavior.

Minimum PHP version: 7.4

Security notes

Use caller-owned allowlists and procedural mysqli prepared execution where SQL plans are returned; validate file paths, MIME policies, and permissions before file or download workflows.

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.
 */

/**
 * Builds conservative index recommendations from observed filter and sort usage.
 *
 * The output is advisory only. It does not execute schema-changing statements.
 *
 * @param array $usage Query usage rows keyed by table or as table/columns rows.
 * @param array $schema Existing schema/index metadata.
 * @param array $options Optional minimum usage threshold.
 * @return array Index recommendation report.
 */
function ogRecommendIndexes($usage = array(), $schema = array(), $options = array()) {
	$result = array(
		'success' => false,
		'message' => '',
		'data' => array()
	);

	if (!is_array($usage)) {
		$result['message'] = 'Usage data must be an array.';
		return $result;
	}

	if (!is_array($schema)) {
		$schema = array();
	}
	if (!is_array($options)) {
		$options = array();
	}

	$minimum_uses = 2;
	if (!empty($options['minimum_uses'])) {
		$minimum_uses = (int)$options['minimum_uses'];
	}
	if ($minimum_uses < 1) {
		$minimum_uses = 1;
	}

	$existing = array();
	if (!empty($schema['indexes']) && is_array($schema['indexes'])) {
		foreach ($schema['indexes'] as $index) {
			if (!is_array($index) || empty($index['table']) || empty($index['columns'])) {
				continue;
			}
			$existing_key = $index['table'] . ':' . implode(',', (array)$index['columns']);
			$existing[$existing_key] = true;
		}
	}

	$recommendations = array();
	foreach ($usage as $row) {
		if (!is_array($row) || empty($row['table']) || empty($row['columns'])) {
			continue;
		}
		$table = trim((string)$row['table']);
		if (!preg_match('/^[a-zA-Z0-9_]+$/', $table)) {
			continue;
		}

		$columns = array();
		foreach ((array)$row['columns'] as $column) {
			$column = trim((string)$column);
			if (preg_match('/^[a-zA-Z0-9_]+$/', $column)) {
				$columns[] = $column;
			}
		}
		if (empty($columns)) {
			continue;
		}

		$uses = 1;
		if (!empty($row['uses'])) {
			$uses = (int)$row['uses'];
		}
		if ($uses < $minimum_uses) {
			continue;
		}

		$key = $table . ':' . implode(',', $columns);
		if (!empty($existing[$key])) {
			continue;
		}

		$index_name = 'idx_' . strtolower($table . '_' . implode('_', $columns));
		$recommendations[] = array(
			'table' => $table,
			'columns' => $columns,
			'uses' => $uses,
			'index_name' => substr($index_name, 0, 64),
			'reason' => 'Repeated filter or sort usage without a matching known index.'
		);
	}

	$result['success'] = true;
	$result['message'] = 'Index recommendations prepared.';
	$result['data'] = array('recommendations' => $recommendations);

	return $result;
}