extract published field for convenience

This commit is contained in:
Devin Zuczek
2026-08-05 15:39:25 -04:00
parent ae3bef4cc4
commit a73dec7c13
2 changed files with 37 additions and 12 deletions
@@ -0,0 +1,21 @@
-- Break the two visibility flags out of the invention JSON blob into queryable
-- generated columns, the same way 0003 did for `IsFeatured`. `IsPublished` and
-- `HideFromPlayer` are always tested together — every feed, the search/browse list and
-- the per-room list ask for "published and not hidden" — so they move together.
-- Generated from src/inventions-db.ts (SCHEMA_DDL) — keep in sync.
--
-- SQLite allows ALTER TABLE ADD COLUMN only for VIRTUAL generated columns (a STORED one
-- would need rewriting existing rows), which is what we want anyway: the value stays
-- derived from `data`, so nothing can drift out of sync with it. json_extract of a JSON
-- `true` is 1, so both columns read 1/0 — and NULL for a blob missing the key, which is
-- neither 1 nor 0 and so fails both filters exactly as the json_extract predicates it
-- replaces did. This is a rename, not a behaviour change.
--
-- No index: both columns are booleans that are overwhelmingly one value (nearly every
-- invention is published and not hidden), so an index on them would be read past rather
-- than used. The selective one is idx_invention_featured, added in 0003, which stays.
ALTER TABLE invention
ADD COLUMN is_published INTEGER GENERATED ALWAYS AS (json_extract(data, '$.IsPublished')) VIRTUAL;
ALTER TABLE invention
ADD COLUMN hide_from_player INTEGER GENERATED ALWAYS AS (json_extract(data, '$.HideFromPlayer')) VIRTUAL;
+16 -12
View File
@@ -1,8 +1,8 @@
/** /**
* Saved-invention storage on the shared `recflare` D1 database. Each invention is * Saved-invention storage on the shared `recflare` D1 database. Each invention is
* a single JSON blob in the `data` column; queryable fields (Id, CreatorPlayerId) * a single JSON blob in the `data` column; queryable fields (Id, CreatorPlayerId, the
* are SQLite generated (virtual) columns extracted from that JSON — the same * visibility flags) are SQLite generated (virtual) columns extracted from that JSON —
* JSON-blob pattern the image/rooms/accounts tables use. * the same JSON-blob pattern the image/rooms/accounts tables use.
* *
* The `api` worker owns this schema/migration (migrations/0002_invention.sql, * The `api` worker owns this schema/migration (migrations/0002_invention.sql,
* applied under its own `migrations_table`). The invention's data file itself is * applied under its own `migrations_table`). The invention's data file itself is
@@ -20,16 +20,20 @@
import { getOwnedInventionIds } from '@repo/domain' import { getOwnedInventionIds } from '@repo/domain'
/** /**
* Schema DDL (mirror of migrations/0002_invention.sql + 0003_invention_featured.sql, * Schema DDL (mirror of migrations/0002_invention.sql + 0003_invention_featured.sql +
* sans any seed rows). `is_featured` backs the featured feed's query; json_extract * 0008_invention_visibility.sql, sans any seed rows). `is_featured` backs the featured
* of a JSON `true` is 1, so the column is 1/0. * feed's query and `is_published`/`hide_from_player` the "may anyone see this" filter
* every feed shares; json_extract of a JSON `true` is 1, so those columns are 1/0 — and
* NULL when the key is missing, which fails a `= 1` or `= 0` test either way.
*/ */
export const SCHEMA_DDL: string[] = [ export const SCHEMA_DDL: string[] = [
`CREATE TABLE IF NOT EXISTS invention ( `CREATE TABLE IF NOT EXISTS invention (
data TEXT NOT NULL, data TEXT NOT NULL,
id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.InventionId')) VIRTUAL, id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.InventionId')) VIRTUAL,
creator_player_id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.CreatorPlayerId')) VIRTUAL, creator_player_id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.CreatorPlayerId')) VIRTUAL,
is_featured INTEGER GENERATED ALWAYS AS (json_extract(data, '$.IsFeatured')) VIRTUAL is_featured INTEGER GENERATED ALWAYS AS (json_extract(data, '$.IsFeatured')) VIRTUAL,
is_published INTEGER GENERATED ALWAYS AS (json_extract(data, '$.IsPublished')) VIRTUAL,
hide_from_player INTEGER GENERATED ALWAYS AS (json_extract(data, '$.HideFromPlayer')) VIRTUAL
)`, )`,
`CREATE UNIQUE INDEX IF NOT EXISTS idx_invention_id ON invention (id)`, `CREATE UNIQUE INDEX IF NOT EXISTS idx_invention_id ON invention (id)`,
`CREATE INDEX IF NOT EXISTS idx_invention_creator ON invention (creator_player_id)`, `CREATE INDEX IF NOT EXISTS idx_invention_creator ON invention (creator_player_id)`,
@@ -335,12 +339,12 @@ export async function searchInventions(
* ones via the indexed `is_featured` column. * ones via the indexed `is_featured` column.
*/ */
async function publicInventions(db: D1Database, featuredOnly = false): Promise<SavedInvention[]> { async function publicInventions(db: D1Database, featuredOnly = false): Promise<SavedInvention[]> {
// json_extract of a JSON `true` is 1, so these filters stay in SQL. // All three are generated columns off the JSON blob, so the filter stays in SQL.
const { results } = await db const { results } = await db
.prepare( .prepare(
`SELECT data FROM invention `SELECT data FROM invention
WHERE json_extract(data, '$.IsPublished') = 1 WHERE is_published = 1
AND json_extract(data, '$.HideFromPlayer') = 0 AND hide_from_player = 0
${featuredOnly ? 'AND is_featured = 1' : ''}` ${featuredOnly ? 'AND is_featured = 1' : ''}`
) )
.all<InventionRow>() .all<InventionRow>()
@@ -614,8 +618,8 @@ export async function getInventionsByRoom(
.prepare( .prepare(
`SELECT data FROM invention `SELECT data FROM invention
WHERE json_extract(data, '$.CreationRoomId') = ?1 WHERE json_extract(data, '$.CreationRoomId') = ?1
AND json_extract(data, '$.IsPublished') = 1 AND is_published = 1
AND json_extract(data, '$.HideFromPlayer') = 0` AND hide_from_player = 0`
) )
.bind(roomId) .bind(roomId)
.all<InventionRow>() .all<InventionRow>()