Summary
Hi @lovasoa , when building a cache layer for external API calls, we need sqlpage.fetch_with_meta() to execute only when the cache is empty. Unfortunately, SQLite does not guarantee short-circuit evaluation for side-effect functions, so the fetch is executed every time regardless of the condition.
Problem
We want this pattern:
SET cached = (SELECT data FROM cache WHERE ...);
-- Only fetch if cache is empty
SET response = CASE WHEN $cached IS NULL THEN sqlpage.fetch_with_meta(...) END;
But sqlpage.fetch_with_meta() is always called, even when $cached IS NOT NULL.
What we tested
Test 1: CASE WHEN — ❌ Does NOT block evaluation
SET cached = (SELECT data FROM cache WHERE updated_at > datetime('now', '-1 hour'));
SET response = CASE
WHEN $cached IS NULL
THEN sqlpage.fetch_with_meta(json_object('method', 'GET', 'url', 'https://api.example.com/data'))
END;
Result: sqlpage.fetch_with_meta() is called EVERY time, even when $cached IS NOT NULL.
Log evidence:
Fetching https://api.example.com/data with metadata ← fetch executed!
[API] cached=HIT, response=NULL ← but cache was HIT
SQLite's CASE expression does not guarantee short-circuit evaluation for functions with side effects.
Test 2: Subquery with WHERE — ❌ Does NOT block evaluation
SET response = (
SELECT sqlpage.fetch_with_meta(json_object('method', 'GET', 'url', 'https://api.example.com/data'))
FROM (SELECT 1) AS _dummy
WHERE $cached IS NULL
);
Result: sqlpage.fetch_with_meta() is STILL called, even when WHERE would return 0 rows.
Log evidence:
Fetching https://api.example.com/data with metadata ← fetch executed!
[API] cached=HIT, response=NULL ← but cache was HIT
SQLite's query planner evaluates scalar functions in SELECT regardless of the WHERE clause.
Test 3: localhost fallback — ✅ Works (but is a hack)
SET should_fetch = CASE WHEN $cached IS NULL THEN 1 ELSE 0 END;
SET request = CASE WHEN $should_fetch = 1 THEN
json_object('method', 'GET', 'url', 'https://api.example.com/data')
ELSE
json_object('method', 'HEAD', 'url', 'http://127.0.0.1:8080/style.css')
END;
SET response = sqlpage.fetch_with_meta($request);
Result: Works! When cache is HIT, the request goes to localhost instead of the external API. No external transfer, no API quota used.
Log evidence:
Fetching http://127.0.0.1:8080/style.css with metadata ← localhost ping
[API] should_fetch=0, status=200 — cache HIT ← no external call
But this is a hack — it makes 4 unnecessary localhost HTTP requests per page load (one per API: Google, Open-Meteo, IMGW, IAU).
Why this matters
- The localhost hack adds ~4 unnecessary HTTP round-trips per page load
- It pollutes logs with
Fetching http://127.0.0.1:8080/style.css messages
- It requires a running web server to respond to the dummy requests
- It's conceptually wrong — we're making requests we don't need
Proposed solutions
Option A: New conditional fetch function
SET response = sqlpage.fetch_if($cached IS NULL, json_object(...));
Only executes sqlpage.fetch_with_meta() when the first argument is TRUE.
Option B: Lazy evaluation for SET
SET response = LAZY sqlpage.fetch_with_meta(...);
The function is only evaluated when $response is first accessed.
Option C: Built-in cache helper
SET response = sqlpage.cached_fetch(
cache_key => 'google_rating',
cache_ttl => '24 hours',
request => json_object('method', 'GET', 'url', '...')
);
sqlpage handles caching internally.
Environment
- sqlpage version: latest (v0.44.1)
- OS: Linux (Docker)
- 4 external APIs cached: Google Places, Open-Meteo, IMGW Hydro, IAU Meteors
Workaround (current)
We use the localhost fallback pattern for all 4 APIs. It works but is not ideal.
Thank you
sqlpage is amazing — this is the only issue we've encountered in production. Great work!
Summary
Hi @lovasoa , when building a cache layer for external API calls, we need
sqlpage.fetch_with_meta()to execute only when the cache is empty. Unfortunately, SQLite does not guarantee short-circuit evaluation for side-effect functions, so the fetch is executed every time regardless of the condition.Problem
We want this pattern:
But
sqlpage.fetch_with_meta()is always called, even when$cached IS NOT NULL.What we tested
Test 1: CASE WHEN — ❌ Does NOT block evaluation
Result:
sqlpage.fetch_with_meta()is called EVERY time, even when$cached IS NOT NULL.Log evidence:
SQLite's CASE expression does not guarantee short-circuit evaluation for functions with side effects.
Test 2: Subquery with WHERE — ❌ Does NOT block evaluation
Result:
sqlpage.fetch_with_meta()is STILL called, even whenWHEREwould return 0 rows.Log evidence:
SQLite's query planner evaluates scalar functions in SELECT regardless of the WHERE clause.
Test 3: localhost fallback — ✅ Works (but is a hack)
Result: Works! When cache is HIT, the request goes to localhost instead of the external API. No external transfer, no API quota used.
Log evidence:
But this is a hack — it makes 4 unnecessary localhost HTTP requests per page load (one per API: Google, Open-Meteo, IMGW, IAU).
Why this matters
Fetching http://127.0.0.1:8080/style.cssmessagesProposed solutions
Option A: New conditional fetch function
Only executes
sqlpage.fetch_with_meta()when the first argument is TRUE.Option B: Lazy evaluation for SET
The function is only evaluated when
$responseis first accessed.Option C: Built-in cache helper
sqlpage handles caching internally.
Environment
Workaround (current)
We use the localhost fallback pattern for all 4 APIs. It works but is not ideal.
Thank you
sqlpage is amazing — this is the only issue we've encountered in production. Great work!