From c8030b759de5df874451e4b9cb7f2cee6f18f2b8 Mon Sep 17 00:00:00 2001 From: Timothy Allen Date: Sat, 26 Sep 2026 14:22:44 +0200 Subject: [PATCH] Leave the image data out of the story lists. A page of 50 stories read 50 images from the database only to see which stories had one. --- get_data.lib.php | 15 +++++++++++---- tests/php/Pages/SubmitTest.php | 2 +- tests/php/Support/SiteData.php | 8 +++++++- tests/php/Unit/QueryTest.php | 28 ++++++++++++++++++++++------ 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/get_data.lib.php b/get_data.lib.php index 62c8a4f..4df302f 100644 --- a/get_data.lib.php +++ b/get_data.lib.php @@ -150,6 +150,13 @@ function get_count() { return $row_count[0]; } +/* + * The story columns that the pages read. The image data stays in the + * database (display.php reads it): img is 1 for a story with an image, + * and NULL for one without. + */ +define('STORY_COLUMNS', 'id, title, author, email, date, story, hidden, mime, comments, editor, IF(LENGTH(img) > 0, 1, NULL) AS img'); + /* * Return array of results, correctly keyed to column names. */ @@ -169,13 +176,13 @@ function get_query($id = NULL) { // If id= is defined, show the corresponding application only if (is_numeric($id)) { if (!$is_admin) - $query = "SELECT * FROM $table WHERE 1 AND hidden IS NOT TRUE AND id = ".(int)$id." LIMIT 0, 1"; + $query = "SELECT ".STORY_COLUMNS." FROM $table WHERE 1 AND hidden IS NOT TRUE AND id = ".(int)$id." LIMIT 0, 1"; else - $query = "SELECT * FROM $table WHERE 1 AND id = ".(int)$id." LIMIT 0, 1"; + $query = "SELECT ".STORY_COLUMNS." FROM $table WHERE 1 AND id = ".(int)$id." LIMIT 0, 1"; // if search= is defined, search.... } else if ($search) { - $query = "SELECT * FROM $table WHERE". + $query = "SELECT ".STORY_COLUMNS." FROM $table WHERE". " (title LIKE ? OR author LIKE ? OR date LIKE ? OR story LIKE ?)"; $params = array_fill(0, 4, "%$search%"); if (!$is_admin) @@ -183,7 +190,7 @@ function get_query($id = NULL) { $query .= " ORDER BY ".$sort." ".$order.$limit; // Otherwise, fall back to default view } else { - $query = "SELECT * FROM ".$table." WHERE 1"; + $query = "SELECT ".STORY_COLUMNS." FROM ".$table." WHERE 1"; if (!$is_admin) $query .= " AND hidden IS NOT TRUE"; $query .= " ORDER BY ".$sort." ".$order.$limit; diff --git a/tests/php/Pages/SubmitTest.php b/tests/php/Pages/SubmitTest.php index b3fe4fe..e782301 100644 --- a/tests/php/Pages/SubmitTest.php +++ b/tests/php/Pages/SubmitTest.php @@ -105,7 +105,7 @@ class SubmitTest extends PageTestCase { $this->assertStringNotContainsString('author@example.org', $page['body']); $this->assertStringNotContainsString('Live one', $page['body']); } - $this->assertSame(array(), $this->queries('/^SELECT \* FROM xxx/')); + $this->assertSame(array(), $this->queries('/^SELECT id, title, .* FROM xxx/')); } public function testAPostWithoutTheTokenOnlyGetsAPreview(): void { diff --git a/tests/php/Support/SiteData.php b/tests/php/Support/SiteData.php index 6d1abe2..ea64f6e 100644 --- a/tests/php/Support/SiteData.php +++ b/tests/php/Support/SiteData.php @@ -22,7 +22,13 @@ class SiteData { self::$lastInsertId = 0; $stories = '(xxx|queue)'; - FakeDb::on("/^SELECT \\* FROM $stories\\b/", array(self::class, 'selectStories')); + FakeDb::on("/^SELECT id, title, author, email, date, story, hidden, mime, comments, editor, IF\\(LENGTH\\(img\\) > 0, 1, NULL\\) AS img FROM $stories\\b/", + function ($sql, $params) { + return array_map(function ($row) { + $row['img'] = strlen((string)$row['img']) > 0 ? 1 : NULL; + return $row; + }, self::selectStories($sql, $params)); + }); FakeDb::on("/^SELECT COUNT\\(\\*\\) FROM $stories\\b/", function ($sql, $params) { return array(array('COUNT(*)' => (string)count(self::filterStories($sql, $params)))); }); diff --git a/tests/php/Unit/QueryTest.php b/tests/php/Unit/QueryTest.php index 08e3714..9b5c166 100644 --- a/tests/php/Unit/QueryTest.php +++ b/tests/php/Unit/QueryTest.php @@ -7,6 +7,9 @@ use Xxx\Tests\Support\TestCase; // get_count() and get_query() in get_data.lib.php class QueryTest extends TestCase { + // Every story column but the image data + private const COLUMNS = 'id, title, author, email, date, story, hidden, mime, comments, editor, IF(LENGTH(img) > 0, 1, NULL) AS img'; + protected function setUp(): void { parent::setUp(); SiteData::install(array( @@ -66,7 +69,7 @@ class QueryTest extends TestCase { $GLOBALS['num_stories'] = 5; $rows = get_query(); $this->assertSame(array('2', '1'), array_column($rows, 'id')); - $this->assertSame('SELECT * FROM xxx WHERE 1 AND hidden IS NOT TRUE ORDER BY date desc LIMIT 0 , 5', $this->lastQuery()['sql']); + $this->assertSame('SELECT ' . self::COLUMNS . ' FROM xxx WHERE 1 AND hidden IS NOT TRUE ORDER BY date desc LIMIT 0 , 5', $this->lastQuery()['sql']); $this->assertSame(array(0, 0), array_column($rows, 'queue_url')); } @@ -79,19 +82,19 @@ class QueryTest extends TestCase { public function testSortOrderStartAndShow(): void { $this->request(array('sort' => 'title', 'order' => 'asc', 'start' => '1', 'show' => '1')); $rows = get_query(); - $this->assertSame('SELECT * FROM xxx WHERE 1 AND hidden IS NOT TRUE ORDER BY title asc LIMIT 1 , 1', $this->lastQuery()['sql']); + $this->assertSame('SELECT ' . self::COLUMNS . ' FROM xxx WHERE 1 AND hidden IS NOT TRUE ORDER BY title asc LIMIT 1 , 1', $this->lastQuery()['sql']); $this->assertSame(array('Banana'), array_column($rows, 'title')); } public function testBadSortAndOrderValuesFallBack(): void { $this->request(array('sort' => 'id; DROP TABLE xxx', 'order' => 'desc; --', 'start' => 'x', 'show' => 'y')); get_query(); - $this->assertSame('SELECT * FROM xxx WHERE 1 AND hidden IS NOT TRUE ORDER BY date desc LIMIT 0 , 1', $this->lastQuery()['sql']); + $this->assertSame('SELECT ' . self::COLUMNS . ' FROM xxx WHERE 1 AND hidden IS NOT TRUE ORDER BY date desc LIMIT 0 , 1', $this->lastQuery()['sql']); } public function testOneStoryById(): void { $rows = get_query('2'); - $this->assertSame('SELECT * FROM xxx WHERE 1 AND hidden IS NOT TRUE AND id = 2 LIMIT 0, 1', $this->lastQuery()['sql']); + $this->assertSame('SELECT ' . self::COLUMNS . ' FROM xxx WHERE 1 AND hidden IS NOT TRUE AND id = 2 LIMIT 0, 1', $this->lastQuery()['sql']); $this->assertSame(array('Banana'), array_column($rows, 'title')); } @@ -102,7 +105,7 @@ class QueryTest extends TestCase { public function testEditorsCanOpenAHiddenStory(): void { $this->asEditor(); $this->assertSame(array('Cherry'), array_column(get_query('3'), 'title')); - $this->assertSame('SELECT * FROM xxx WHERE 1 AND id = 3 LIMIT 0, 1', $this->lastQuery()['sql']); + $this->assertSame('SELECT ' . self::COLUMNS . ' FROM xxx WHERE 1 AND id = 3 LIMIT 0, 1', $this->lastQuery()['sql']); } public function testIdIsCastToAnInteger(): void { @@ -120,10 +123,23 @@ class QueryTest extends TestCase { $this->request(array('search' => '%_')); get_query(); $query = $this->lastQuery(); - $this->assertStringStartsWith('SELECT * FROM xxx WHERE (title LIKE ? OR author LIKE ? OR date LIKE ? OR story LIKE ?) AND hidden IS NOT TRUE ORDER BY', $query['sql']); + $this->assertStringStartsWith('SELECT ' . self::COLUMNS . ' FROM xxx WHERE (title LIKE ? OR author LIKE ? OR date LIKE ? OR story LIKE ?) AND hidden IS NOT TRUE ORDER BY', $query['sql']); $this->assertSame(array_fill(0, 4, '%%_%'), $query['params']); } + // A page of 50 stories would otherwise read 50 images, only to see which stories have one + public function testImageDataStaysInTheDatabase(): void { + SiteData::install(array('xxx' => array( + array('id' => 1, 'story' => '', 'img' => str_repeat('P', 1000), 'mime' => 'image/png'), + array('id' => 2), + ))); + $GLOBALS['num_stories'] = 5; + $rows = array_column(get_query(), NULL, 'id'); + $this->assertSame(1, $rows['1']['img']); + $this->assertNull($rows['2']['img']); + $this->assertSame(1, get_query('1')[0]['img']); + } + public function testQueueRowsAreMarked(): void { $this->asEditor(); $this->request(array('queue' => '1'));