@prefix sioc: <http://rdfs.org/sioc/ns#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix content: <http://purl.org/rss/1.0/modules/content/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<https://lhero.org/?post_type=lh-portfolio&#038;p=147049>
  a sioc:Post ;
  dc:title "Database schema" ;
  dcterms:identifier 147049 ;
  dc:modified "2026-09-05T12:04:32Z"^^xsd:dateTime ;
  dc:created "2026-09-05T02:53:06Z"^^xsd:dateTime ;
  sioc:link <https://lhero.org/portfolio/lh-polls/database-schema/> ;
  sioc:has_creator <https://lhero.org/author/1/#account> ;
  sioc:has_container <https://lhero.org/#posts> ;
  content:encoded """<h2>{prefix}lh_poll_options</h2>
<p>Per-site on Multisite.</p>
<table>
<tr>
<th>Column</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>option_id</td>
<td>BIGINT UNSIGNED</td>
<td>Primary key</td>
</tr>
<tr>
<td>poll_id</td>
<td>BIGINT UNSIGNED</td>
<td>Parent poll post ID</td>
</tr>
<tr>
<td>label</td>
<td>VARCHAR(255)</td>
<td>Display label</td>
</tr>
<tr>
<td>menu_order</td>
<td>INT</td>
<td>Sort order</td>
</tr>
</table>
<h2>{prefix}lh_poll_votes</h2>
<p>Per-site on Multisite.</p>
<table>
<tr>
<th>Column</th>
<th>Type</th>
<th>Description</th>
</tr>
<tr>
<td>vote_id</td>
<td>BIGINT UNSIGNED</td>
<td>Primary key</td>
</tr>
<tr>
<td>poll_id</td>
<td>BIGINT UNSIGNED</td>
<td>Parent poll post ID</td>
</tr>
<tr>
<td>option_id</td>
<td>BIGINT UNSIGNED</td>
<td>Option voted for</td>
</tr>
<tr>
<td>user_id</td>
<td>BIGINT UNSIGNED</td>
<td>Voter</td>
</tr>
<tr>
<td>comment_id</td>
<td>BIGINT UNSIGNED</td>
<td>Linked wp_comments row</td>
</tr>
<tr>
<td>voted_at</td>
<td>DATETIME</td>
<td>Vote timestamp</td>
</tr>
</table>
<p>Unique constraint: (option_id, user_id) &#8211; one vote per user per option, enforced at the database level.</p>
<h2>wp_comments (comment_type = lh_vote)</h2>
<p>Every vote creates a wp_comments row of type lh_vote attached to the poll post (comment_post_ID = poll_id). This enables optional comment text alongside a vote, viewing/deleting votes via the standard WP comments admin screen, and vote counts visible as comment_count on the poll post. Vote comments are excluded from all standard comment queries, feeds, and counts on non-poll posts. Deleting a vote comment via the WP comments screen automatically removes the corresponding vote row via the deleted_comment hook. post_parent on lh_poll is unused (default 0) &#8211; not repurposed for anything.</p>
<h2>Caching</h2>
<p>All poll read queries (options list, option IDs, vote counts, per-option vote list, per-user voted flags) are cached as transients backed by the persistent object cache (Redis on LocalHero). Cache is keyed per poll/option/user and flushed explicitly on any vote insert, delete, or option change via LH_Polls_DB::flush_poll_cache() &#8211; TTL (DAY_IN_SECONDS) is a safety net only, since invalidation is explicit on every write.</p>
<h2>Cache invalidation (page cache)</h2>
<p>After a vote is cast or changed, the plugin fires do_action(&#8216;litespeed_purge_url&#8217;, $page_url) to purge the embedding page from LiteSpeed&#8217;s page cache. A no-op on non-LiteSpeed environments.</p>
"""^^rdf:XMLLiteral ;
  sioc:content """{prefix}lh_poll_options
Per-site on Multisite.


Column
Type
Description


option_id
BIGINT UNSIGNED
Primary key


poll_id
BIGINT UNSIGNED
Parent poll post ID


label
VARCHAR(255)
Display label


menu_order
INT
Sort order


{prefix}lh_poll_votes
Per-site on Multisite.


Column
Type
Description


vote_id
BIGINT UNSIGNED
Primary key


poll_id
BIGINT UNSIGNED
Parent poll post ID


option_id
BIGINT UNSIGNED
Option voted for


user_id
BIGINT UNSIGNED
Voter


comment_id
BIGINT UNSIGNED
Linked wp_comments row


voted_at
DATETIME
Vote timestamp


Unique constraint: (option_id, user_id) &#8211; one vote per user per option, enforced at the database level.
wp_comments (comment_type = lh_vote)
Every vote creates a wp_comments row of type lh_vote attached to the poll post (comment_post_ID = poll_id). This enables optional comment text alongside a vote, viewing/deleting votes via the standard WP comments admin screen, and vote counts visible as comment_count on the poll post. Vote comments are excluded from all standard comment queries, feeds, and counts on non-poll posts. Deleting a vote comment via the WP comments screen automatically removes the corresponding vote row via the deleted_comment hook. post_parent on lh_poll is unused (default 0) &#8211; not repurposed for anything.
Caching
All poll read queries (options list, option IDs, vote counts, per-option vote list, per-user voted flags) are cached as transients backed by the persistent object cache (Redis on LocalHero). Cache is keyed per poll/option/user and flushed explicitly on any vote insert, delete, or option change via LH_Polls_DB::flush_poll_cache() &#8211; TTL (DAY_IN_SECONDS) is a safety net only, since invalidation is explicit on every write.
Cache invalidation (page cache)
After a vote is cast or changed, the plugin fires do_action(&#8216;litespeed_purge_url&#8217;, $page_url) to purge the embedding page from LiteSpeed&#8217;s page cache. A no-op on non-LiteSpeed environments.
""" ;
  sioc:topic <https://lhero.org/lh_portfolio-type/doc-section/>, <https://lhero.org/?taxonomy=author&term=cap-1> .

<https://lhero.org/author/1/#account> rdfs:seeAlso <https://lhero.org/author/1/?feed=lhrdf&format=turtle> .
<https://lhero.org/lh_portfolio-type/doc-section/> rdfs:seeAlso <https://lhero.org/lh_portfolio-type/doc-section/?feed=lhrdf&format=turtle> .
<https://lhero.org/?taxonomy=author&term=cap-1> rdfs:seeAlso <https://lhero.org/?taxonomy=author&term=cap-1&feed=lhrdf&format=turtle> .
