From 1e7cafb01e98eec54a603ceeff92cf464f23fb62 Mon Sep 17 00:00:00 2001 From: TheWitness Date: Thu, 15 Oct 2026 09:25:54 -0400 Subject: [PATCH] Fix fatal PRIMARY KEY (`Array`) on fresh install from array primary key --- includes/database.php | 26 +++++++++++++++++++++++--- tests/Unit/GexportSetupTableTest.php | 21 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/includes/database.php b/includes/database.php index b352fbd..c84f6a9 100644 --- a/includes/database.php +++ b/includes/database.php @@ -110,6 +110,26 @@ function gexport_graph_exports_tasks_table_data(): array { return $data; } +/** + * Returns a copy of a table definition with an array-valued 'primary' + * flattened to the backtick-joined scalar form api_plugin_db_table_create() + * expects. Down to this plugin's Cacti 1.2.29 compat floor that API + * interpolates $data['primary'] straight into PRIMARY KEY (`...`), so an + * array would render as PRIMARY KEY (`Array`) and fail the create. The array + * form is kept in the *_table_data() definitions for db_update_table(). + * + * @param array $data The table definition (array primary). + * + * @return array The definition with a scalar primary key. + */ +function gexport_create_definition(array $data): array { + if (isset($data['primary']) && is_array($data['primary'])) { + $data['primary'] = implode('`,`', $data['primary']); + } + + return $data; +} + /** * Creates every table this plugin owns via the tracked plugin table API * (idempotent, so it is safe to call on both install and upgrade). Called @@ -141,7 +161,7 @@ function gexport_setup_table() { * @return bool Always returns true. */ function gexport_create_table() { - api_plugin_db_table_create('gexport', 'graph_exports', gexport_graph_exports_table_data()); + api_plugin_db_table_create('gexport', 'graph_exports', gexport_create_definition(gexport_graph_exports_table_data())); return true; } @@ -154,7 +174,7 @@ function gexport_create_table() { * @return bool Always returns true. */ function gexport_create_table_tasks() { - api_plugin_db_table_create('gexport', 'graph_exports_tasks', gexport_graph_exports_tasks_table_data()); + api_plugin_db_table_create('gexport', 'graph_exports_tasks', gexport_create_definition(gexport_graph_exports_tasks_table_data())); return true; } @@ -197,7 +217,7 @@ function gexport_upgrade_tables() { if (db_table_exists($table)) { db_update_table($table, $data); } else { - api_plugin_db_table_create('gexport', $table, $data); + api_plugin_db_table_create('gexport', $table, gexport_create_definition($data)); } } } diff --git a/tests/Unit/GexportSetupTableTest.php b/tests/Unit/GexportSetupTableTest.php index 0ffddd7..010e839 100644 --- a/tests/Unit/GexportSetupTableTest.php +++ b/tests/Unit/GexportSetupTableTest.php @@ -80,3 +80,24 @@ expect($refreshed)->toContain('graph_exports') ->and($refreshed)->toContain('graph_exports_tasks'); }); + +it('hands api_plugin_db_table_create a scalar primary key while keeping the array form for db_update_table', function () { + // api_plugin_db_table_create() interpolates data['primary'] straight into + // PRIMARY KEY (`...`) down to the 1.2.29 compat floor, so an array would + // render as PRIMARY KEY (`Array`) and fail the create on a fresh install. + // db_update_table() needs the array form, so the canonical definitions keep it. + gexport_setup_table(); + + $creates = array_filter($GLOBALS['__test_db_calls'], function ($call) { + return $call['fn'] === 'api_plugin_db_table_create'; + }); + + expect($creates)->not->toBeEmpty(); + + foreach ($creates as $call) { + expect($call['data']['primary'])->toBeString(); + } + + expect(gexport_graph_exports_table_data()['primary'])->toBeArray() + ->and(gexport_graph_exports_tasks_table_data()['primary'])->toBeArray(); +});