exec('CREATE TABLE poller_output_boost ( local_data_id INTEGER NOT NULL, rrd_name TEXT NOT NULL, time TEXT NOT NULL, output TEXT NOT NULL, PRIMARY KEY (local_data_id, rrd_name, time) )'); } public function prepare(string $query, array $options = []): PDOStatement|false { if (stripos($query, 'max_allowed_packet') !== false) { $this->packet_lookups++; } return parent::prepare($query, $options); } } class BoostFailingChunkPDO extends BoostPacketLookupPDO { public int $insert_attempts = 0; public function prepare(string $query, array $options = []): PDOStatement|false { if (stripos($query, 'max_allowed_packet') !== false) { $this->packet_lookups++; return parent::prepare("SELECT 'max_allowed_packet' AS Variable_name, '512' AS Value", $options); } if (stripos(ltrim($query), 'INSERT IGNORE INTO poller_output_boost') === 0) { $this->insert_attempts++; if ($this->insert_attempts === 2) { throw new RuntimeException('injected second-chunk failure'); } } return parent::prepare($query, $options); } } beforeEach(function () { global $database_sessions, $database_hostname, $database_port, $database_default; $this->db_globals = [$database_sessions, $database_hostname, $database_port, $database_default]; }); // Put the default db_* connection back. Left cleared, every later // read_config_option() in the run loses its connection and the suite aborts. afterEach(function () { global $database_sessions, $database_hostname, $database_port, $database_default; [$database_sessions, $database_hostname, $database_port, $database_default] = $this->db_globals; }); test('max_allowed_packet is read once per connection, not once per process', function () { global $database_sessions, $database_hostname, $database_port, $database_default; // no default connection is registered, so both writes have to go // through the connection they were handed $database_hostname = 'unit_test_host'; $database_port = '0'; $database_default = 'unit_test_db'; $database_sessions = []; $local = new BoostPacketLookupPDO(); $remote = new BoostPacketLookupPDO(); boost_flush_output_batch(["(1,'ds','2024-01-01 00:00:00','10')"], $local); // the second connection is a different server: it must be asked for its // own limit rather than inheriting the one cached for $local boost_flush_output_batch(["(2,'ds','2024-01-01 00:00:00','20')"], $remote); expect($local->packet_lookups)->toBe(1); expect($remote->packet_lookups)->toBe(1); // the per-connection value is still cached: no repeat lookup on reuse boost_flush_output_batch(["(3,'ds','2024-01-01 00:00:00','30')"], $local); expect($local->packet_lookups)->toBe(1); // and each connection got its own rows foreach ([[$local, 2], [$remote, 1]] as [$conn, $expected]) { $count = (int) $conn->query('SELECT COUNT(*) AS c FROM poller_output_boost')->fetch(PDO::FETCH_ASSOC)['c']; expect($count)->toBe($expected); } }); test('a failed chunk stops the batch before later tuples are staged', function () { $conn = new BoostFailingChunkPDO(); $output = str_repeat('x', 300); $result = boost_flush_output_batch([ "(11,'first','2024-01-01 00:00:00','{$output}')", "(12,'failed','2024-01-01 00:00:00','{$output}')", "(13,'later','2024-01-01 00:00:00','{$output}')", ], $conn); $ids = $conn->query('SELECT local_data_id FROM poller_output_boost ORDER BY local_data_id')->fetchAll(PDO::FETCH_COLUMN); expect($result)->toBeFalse() ->and($conn->insert_attempts)->toBe(2) ->and($ids)->toBe([11]); });