81 $property =
new ReflectionProperty(LGV_TZ_Lookup_PDO::class,
'_pdo');
82 if (PHP_VERSION_ID < 80100) { $property->setAccessible(
true); }
84 $connection->setAttribute(PDO::ATTR_STATEMENT_CLASS, [DatabaseTestStatement::class, []]);
88 $bounds = [
'east' => 4,
'west' => 0,
'north' => 4,
'south' => 0];
89 $miss = [[0, 0], [4, 0], [4, 1], [0, 1], [0, 0]];
90 $hit = [[0, 0], [4, 0], [4, 4], [0, 4], [0, 0]];
101 databaseCheck(
$hit, array_map(
static fn($p) => array_map(
'intval', $p),
$decoded[0][
'polygon']),
'Legacy decoded-polygon API');
105 for ($i = 0; $i < 2053; ++$i) {
106 $angle = 2 * M_PI * $i / 2053;
107 $ring[] = [40 + cos($angle), 40 + sin($angle)];
110 $bounds = [
'east' => 41,
'west' => 39,
'north' => 41,
'south' => 39];
112 $database->store_entity(
new LGV_TZ_Lookup_Entity(
'Region/CircleMiss', $bounds, [[39, 39], [41, 39], [41, 39.001], [39, 39.001], [39, 39]]));
113 foreach ([-0.99, -0.7, 0.0, 0.7, 0.99] as $x) {
114 foreach ([-0.99, -0.7, 0.0, 0.7, 0.99] as $y) {
115 databaseCheck($x * $x + $y * $y < 1 ?
'Region/Circle' :
'',
$lookup->get_tz(40 + $x, 40 + $y),
'Analytic circle '.$x.
','.$y);
119 foreach ($ring as $point) { $sourceBytes .= pack(
'd2', round($point[0], 6), round($point[1], 6)); }
120 $row = iterator_to_array(
$database->get_tz_polygons([6]))[0];
121 databaseCheck($sourceBytes, $row[
'polygon'],
'Packed double bytes survive database round trip');
122 unset($ring, $row, $sourceBytes);
125 $type =
$driver ===
'pgsql' ?
'BYTEA' :
'LONGBLOB';
126 $wrapper->preparedStatement(
'CREATE TEMPORARY TABLE binary_fixture (id INTEGER PRIMARY KEY, payload '.$type.
')');
127 $blob = str_repeat(implode(
'', array_map(
'chr', range(0, 255))), 4096);
128 for ($i = 1; $i <= 24; ++$i) {
129 $wrapper->preparedStatement(
'INSERT INTO binary_fixture VALUES (?, ?)', [$i, $blob],
false, [1 => PDO::PARAM_LOB]);
131 $wrapper->preparedStatement(
'INSERT INTO binary_fixture VALUES (?, ?)', [25,
''],
false, [1 => PDO::PARAM_LOB]);
132 databaseCheck(
'',
$wrapper->preparedStatement(
'SELECT payload FROM binary_fixture WHERE id = 25', [],
true)[0][
'payload'],
'Empty binary value');
133 if (function_exists(
'memory_reset_peak_usage')) { memory_reset_peak_usage(); }
134 $before = memory_get_usage();
137 foreach (
$wrapper->preparedRows(
'SELECT payload FROM binary_fixture WHERE id <= ? ORDER BY id', [24],
false) as $row) {
138 databaseCheck(hash(
'sha256', $blob), hash(
'sha256', $row[
'payload']),
'All 256 byte values in row '.++$count);
139 if (
$driver ===
'pgsql' && defined(
'Pdo\\Pgsql::ATTR_RESULT_MEMORY_SIZE')) {
145 if (function_exists(
'memory_reset_peak_usage')) {
146 $extra = memory_get_peak_usage() - $before;
147 databaseCheck(
true, $extra < 8 * 1048576,
'Binary reader stays below eight MiB extra PHP memory');
148 printf(
" Binary reader peak extra PHP memory: %.2f MiB for 24 MiB of rows.\n", $extra / 1048576);
150 if ($nativePeak > 0) {
151 databaseCheck(
true, $nativePeak < 4 * 1048576,
'PostgreSQL native result buffer holds one row');
152 printf(
" PostgreSQL native result buffer peak: %.2f MiB.\n", $nativePeak / 1048576);
155 $rows =
$wrapper->preparedRows(
'SELECT payload FROM binary_fixture ORDER BY id', [],
false);
156 foreach (
$rows as $row) {
break; }
158 databaseCheck(3, (
int)
$wrapper->preparedStatement(
'SELECT 3 AS value', [],
true)[0][
'value'],
'Connection usable after early cursor exit');
161 $rows =
$wrapper->preparedRows(
'SELECT payload FROM binary_fixture ORDER BY id', [],
false);
162 foreach (
$rows as $row) {
break; }
165 $wrapper->preparedStatement(
'INSERT INTO binary_fixture VALUES (?, ?)', [26, $blob],
false, [1 => PDO::PARAM_LOB]);
168 databaseCheck(0, (
int)
$wrapper->preparedStatement(
'SELECT COUNT(*) AS count FROM binary_fixture WHERE id = 26', [],
true)[0][
'count'],
'Caller rollback undoes the write');
169 foreach ([
true,
false] as $write) {
171 if ($write) {
$wrapper->preparedStatement(
'INSERT INTO lgv_tz_missing_table VALUES (1)'); }
172 else { iterator_to_array(
$wrapper->preparedRows(
'SELECT * FROM lgv_tz_missing_table', [],
false)); }
173 throw new RuntimeException(
'Missing table should fail.');
174 }
catch (Exception
$error) {
175 databaseCheck(
true,
$error->getPrevious() instanceof PDOException,
'Database exception retains PDO context');
177 databaseCheck(9, (
int)
$wrapper->preparedStatement(
'SELECT 9 AS value', [],
true)[0][
'value'],
'Connection usable after database error');
182 $cursor =
$connection->prepare(
'SELECT payload FROM binary_fixture WHERE id = ?', [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
183 $cursor->execute([1]);
184 $stream = $cursor->fetch(PDO::FETCH_ASSOC)[
'payload'];
185 databaseCheck($blob, stream_get_contents($stream),
'PostgreSQL server cursor preserves binary values');
187 $cursor->closeCursor();
189 databaseCheck(0, (
int)
$connection->query(
"SELECT COUNT(*) FROM pg_cursors WHERE name LIKE 'pdo_crsr_%'")->fetchColumn(),
'PostgreSQL server cursor released');
195 $stream = fopen(
'php://memory',
'w+b');
196 fwrite($stream, json_encode([
'type' =>
'FeatureCollection',
'features' => [[
197 'type' =>
'Feature',
'properties' => [
'tzid' =>
'Etc/GMT'],
198 'geometry' => [
'type' =>
'MultiPolygon',
'coordinates' => [[
$hit ], [ [[10, 10], [14, 10], [14, 14], [10, 14], [10, 10]] ]]],
199 ]]], JSON_THROW_ON_ERROR));
201 try { (
new JsonStreamingParser\Parser($stream, $listener))->parse(); }
202 finally { fclose($stream); }
203 databaseCheck(2, (
int)
$wrapper->preparedStatement(
'SELECT COUNT(*) AS count FROM timezones', [],
true)[0][
'count'],
'Loader resets schema and loads both polygons');
204 databaseCheck([1, 2], array_map(
static fn($r) => (
int)$r[
'id'],
$wrapper->preparedStatement(
'SELECT id FROM timezones ORDER BY id', [],
true)),
'Reset restarts generated IDs');
209$host = getenv(
'LGV_TZ_TEST_HOST') ?:
'localhost';
210$port = (int)(getenv(
'LGV_TZ_TEST_PORT') ?: (
$driver ===
'pgsql' ? 5432 : 3306));
211$user = getenv(
'LGV_TZ_TEST_USER') ?: (
$driver ===
'pgsql' ? (getenv(
'USER') ?: get_current_user()) :
'root');
214if (
$driver ===
'pgsql') {
$dsn .=
';dbname='.(getenv(
'LGV_TZ_TEST_ADMIN_DATABASE') ?:
'postgres'); }
foreach([[1, 'America/New_York', [0, 0, 4, 0, 4, 1, 0, 1, 0, 0]], [2, 'America/Chicago', [0, 0, 4, 0, 4, 4, 0, 4, 0, 0]], [3, 'Etc/GMT', [0, 0, 4, 0, 4, 4, 0, 4, 0, 0]],] as[$id, $zone, $points]) $wrapper