@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-15T23:22:42Z"^^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 "<ul class=\"lh_portfolio-meta\"><li><strong>Part of:</strong> <a href=\"https://lhero.org/portfolio/lh-polls/\">LH Polls</a></li></ul><h2 class=\"wp-block-heading\">{prefix}lh_poll_options</h2><p class=\"wp-block-paragraph\">Per-site on Multisite.</p><figure class=\"wp-block-table blocks-engine-table-a7dbac3364ae-3\"><table><tbody><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></tbody></table></figure><h2 class=\"wp-block-heading\">{prefix}lh_poll_votes</h2><p class=\"wp-block-paragraph\">Per-site on Multisite.</p><figure class=\"wp-block-table blocks-engine-table-a7dbac3364ae-4\"><table><tbody><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></tbody></table></figure><p class=\"wp-block-paragraph\">Unique constraint: (option_id, user_id) &#8211; one vote per user per option, enforced at the database level.</p><h2 class=\"wp-block-heading\">wp_comments (comment_type = lh_vote)</h2><p class=\"wp-block-paragraph\">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 class=\"wp-block-heading\">Caching</h2><p class=\"wp-block-paragraph\">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 class=\"wp-block-heading\">Cache invalidation (page cache)</h2><p class=\"wp-block-paragraph\">After a vote is cast or changed, the plugin fires do_action(&#039;litespeed_purge_url&#039;, $page_url) to purge the embedding page from LiteSpeed&#039;s page cache. A no-op on non-LiteSpeed environments.</p>"^^rdf:XMLLiteral ;
  sioc:content "Part of: LH Polls{prefix}lh_poll_optionsPer-site on Multisite.ColumnTypeDescriptionoption_idBIGINT UNSIGNEDPrimary keypoll_idBIGINT UNSIGNEDParent poll post IDlabelVARCHAR(255)Display labelmenu_orderINTSort order{prefix}lh_poll_votesPer-site on Multisite.ColumnTypeDescriptionvote_idBIGINT UNSIGNEDPrimary keypoll_idBIGINT UNSIGNEDParent poll post IDoption_idBIGINT UNSIGNEDOption voted foruser_idBIGINT UNSIGNEDVotercomment_idBIGINT UNSIGNEDLinked wp_comments rowvoted_atDATETIMEVote timestampUnique 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.CachingAll 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(&#039;litespeed_purge_url&#039;, $page_url) to purge the embedding page from LiteSpeed&#039;s page cache. A no-op on non-LiteSpeed environments." ;
  sioc:topic <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/?taxonomy=author&term=cap-1> rdfs:seeAlso <https://lhero.org/?taxonomy=author&term=cap-1&feed=lhrdf&format=turtle> .
