Files
Thomas Vincent 57b8088e13 docs: comprehensive validation audit, Spine poller overhaul, and technical refactor (#229)
* docs: comprehensive audit, Spine poller overhaul, and technical refactor

- Overhaul Spine documentation (Spine.md) with complete end-to-end guide: architecture, prerequisites for modern distros (EL8/9 CRB, Ubuntu 22/24, Debian 11/12, FreeBSD), SUID root permissions, secure spine.conf setup (0640), Web UI configuration (Paths, Poller type, Data Collectors tuning), MySQL max_connections sizing, and troubleshooting.
- Fix broken links and anchors (Hardening, SNMP data queries, Hook API references).
- Fix broken image reference in Sites.md (add-site.JPG -> add-site.png).
- Fix syntax errors in documentation PHP code blocks, including invalid parenthesization, missing SQL execute quotes, and malformed comments in FAQ.
- Fix DevOps installation commands: use config.php.dist, correct systemctl service names for MySQL/MariaDB/PHP-FPM, remove nonexistent php-json, add missing composer install, and correct Debian/Ubuntu systemd environment path.
- Correct grammatical errors, repeated words, and typos across all markdown documents.
- Remove obsolete unreferenced duplicate installation guides.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* docs: correct reviewed examples and guidance

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* docs: correct guidance that disagrees with the Cacti source

Verified each claim against include/global_arrays.php, lib/auth.php,
lib/functions.php, host_templates.php and the Spine config parser. The
notable ones: api_plugin_safe_exec() does not exist on either branch,
Spine reads DB_Pass rather than DB_Password, and the CDEF special data
source table was missing five entries and renamed four others.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* docs: wrap Spine prose and fence the connection sizing formula

The formula's bare asterisks rendered as emphasis, dropping the multiplication signs.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* chore: refresh copyright year in scripts and issue templates

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
2026-09-08 09:41:21 -04:00

4.4 KiB

SQL Standards

Special care must be taken for Cacti to properly support multiple DBMS's.

Quotes

When writing queries do not use quotes around numeric values. While MySQL is not strict about types, most DBMS's assume that any value contained within quotes is a string.

As well, single quotes are used unless a variable is referenced

$graphs = db_fetch_assoc('SELECT * FROM graph WHERE height >= 150');
$rows = db_fetch_cell("SELECT count(*) FROM $archive_table");

DBMS Functions

Do not use functions in SQL queries that are provided only by a single DBMS. These functions are generally not uniformly support or even available in all DBMS's. Common examples of such functions are MD5(), CONCAT(), and NOW().

SQL Injection

Cacti provides support for prepared statements. You should use these prepared statement functions as much as possible to avoid the risk of SQL Injection attacks from the Cacti website. Examples include:

// Example prepared statements
$hostname = db_fetch_cell_prepared('SELECT hostname FROM host WHERE id = ?', array($id));
$host     = db_fetch_row_prepared('SELECT * FROM host WHERE id = ?', array($id));
$graphs   = db_fetch_assoc_prepared('SELECT * FROM graph_local WHERE host_id = ?', array($id));

db_execute_prepared('UPDATE host SET description = ? WHERE id = ?', array('Local Machines', $id));

// Escape strings if using legacy functions. db_qstr() returns the value
// already wrapped in quotes, so do not quote it again.
$hostname = db_qstr('myhost.com');
db_execute("UPDATE host SET hostname = $hostname WHERE id = " . (int) $id);

Format of SQL statements

  • For short statements, SQL calls may be made on a single row.

  • If the SQL statement starts to grow and be unreadable without scrolling, it should be wrapped onto a continuation line for ease of use and readability. The following keywords should also be placed at the start of a new line:

    • joins statements (excluding JOIN itself which should not be used) (eg, INNER JOIN, LEFT JOIN, RIGHT JOIN, OUTER JOIN, CROSS JOIN)

      Note: *Any implicit join or JOIN without one of the modifiers should be converted to an explicit join with a modifier. When converting the implicit join statement, remember to remove the WHERE joining clause.

    • AND / OR

    • FROM

    • WHERE

    • ORDER

    • GROUP BY

    • HAVING

    • LIMIT

    • prepared statement arrays

  • Indentation should consist of one extra tab when any of the following occur:

    • continuation lines

    • Subqueries

  • There should always be 'AS' when defining an alias for any table, field or query to clearly define the definition of said alias.

  • There should always be a space following a comma and before/after an operator.

Example of single line SQL

Before formatting

$templates = db_fetch_assoc('SELECT DISTINCT gt.id, gt.name FROM graph_templates AS gt INNER JOIN graph_templates_graph AS gtg ON gt.id = gtg.graph_template_id INNER JOIN graph_templates_item AS gti ON gtg.graph_template_id=gti.graph_template_id INNER JOIN data_template_rrd AS dtr ON gti.task_item_id=dtr.id INNER JOIN data_template_data AS dtd ON dtd.data_template_id=dtr.data_template_id AND dtd.local_data_id = 0 WHERE gtg.local_graph_id=0 AND dtr.local_data_id = 0 AND dtd.local_data_id = 0 AND dtd.data_input_id in (2,11,12) ORDER BY gt.name;');

Corrected formatting with prepared usage

$templates = db_fetch_assoc_prepared('SELECT DISTINCT gt.id, gt.name
    FROM graph_templates AS gt
    INNER JOIN graph_templates_graph AS gtg
    ON gt.id = gtg.graph_template_id
    INNER JOIN graph_templates_item AS gti
    ON gtg.graph_template_id=gti.graph_template_id
    INNER JOIN data_template_rrd AS dtr
    ON gti.task_item_id=dtr.id
    INNER JOIN data_template_data AS dtd
    ON dtd.data_template_id=dtr.data_template_id
    AND dtd.local_data_id = 0
    WHERE gtg.local_graph_id=0
    AND dtr.local_data_id = 0
    AND dtd.local_data_id = 0
    AND dtd.data_input_id in (?,?,?)
    ORDER BY gt.name',
    array(2,11,12));

Example of implicit join

Before formatting

$user_realms = db_fetch_assoc('SELECT ua.id, uar.realm_id
    FROM user_auth ua, user_auth_realms uar
    WHERE ua.id = uar.user_id');

Corrected formatting

$user_realms = db_fetch_assoc('SELECT ua.id, uar.realm_id
    FROM user_auth ua
    INNER JOIN user_auth_realms uar
    ON ua.id = uar.user_id');

Copyright (c) 2004-2026 The Cacti Group