<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: 阿忠</title>
    <description>The latest articles on DEV Community by 阿忠 (@kekekuki).</description>
    <link>https://dev.to/kekekuki</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4008469%2F6eba2091-3a01-4d56-be18-fb24c852c4b7.jpg</url>
      <title>DEV Community: 阿忠</title>
      <link>https://dev.to/kekekuki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kekekuki"/>
    <language>en</language>
    <item>
      <title>Ghost CMS has 12 tables and 80+ columns. What does each one mean?</title>
      <dc:creator>阿忠</dc:creator>
      <pubDate>Mon, 29 Jun 2026 15:57:18 +0000</pubDate>
      <link>https://dev.to/kekekuki/ghost-cms-has-12-tables-and-80-columns-what-does-each-one-mean-m2m</link>
      <guid>https://dev.to/kekekuki/ghost-cms-has-12-tables-and-80-columns-what-does-each-one-mean-m2m</guid>
      <description>&lt;p&gt;Ghost is one of the most popular open-source blogging platforms — 30K+ GitHub stars, powering millions of blogs.&lt;/p&gt;

&lt;p&gt;Its database has evolved over 10 years. From &lt;code&gt;posts&lt;/code&gt; and &lt;code&gt;users&lt;/code&gt; in 2015 to v6.0's big cleanup in 2025: 8 migrations, 12 tables, 80+ columns, plus Stripe payments, email tracking,&lt;br&gt;
  and a membership system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The SQL migration files are all there in &lt;code&gt;migrations/&lt;/code&gt;. But not a single comment explains what any field actually means.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Can you answer these questions?
&lt;/h2&gt;

&lt;p&gt;Here's a migration from Ghost's codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;updated_by&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;updated_by&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;updated_by&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;updated_by&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;members&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;members&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;updated_by&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Safe cleanup or dangerous destruction? Why remove &lt;code&gt;created_by&lt;/code&gt;? What replaced it?&lt;/p&gt;

&lt;p&gt;Or take the &lt;code&gt;posts&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;  &lt;span class="nv"&gt;`type`&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'post'&lt;/span&gt;
  &lt;span class="nv"&gt;`status`&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'draft'&lt;/span&gt;
  &lt;span class="nv"&gt;`visibility`&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'public'&lt;/span&gt;
  &lt;span class="nv"&gt;`locale`&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What are the valid values for &lt;code&gt;status&lt;/code&gt;? Is it &lt;code&gt;draft/published&lt;/code&gt; or &lt;code&gt;active/inactive&lt;/code&gt;? When did &lt;code&gt;visibility&lt;/code&gt; gain a &lt;code&gt;paid&lt;/code&gt; tier? Why is &lt;code&gt;locale&lt;/code&gt; only 6 characters — can it store&lt;br&gt;
  &lt;code&gt;zh-CN&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Nobody can answer these off the top of their head. The answers are scattered across source code, git history, and the fading memory of whoever wrote the migration three years ago.&lt;/p&gt;


&lt;h2&gt;
  
  
  One command. Thirty seconds.
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npx schemalog generate
  npx schemalog dict
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;8 migrations analyzed. Here's what comes out.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Migration Changelog
&lt;/h3&gt;

&lt;p&gt;A human-readable summary of every migration, with risk levels:&lt;/p&gt;

&lt;p&gt;| # | Timestamp | Change | Risk |&lt;br&gt;
  |---|-----------|--------|------|&lt;br&gt;
  | 1 | 2015-09-15 | Create posts | 🟢 safe |&lt;br&gt;
  | 2 | 2015-09-15 | Create users | 🟢 safe |&lt;br&gt;
  | 3 | 2015-09-15 | Create tags + posts_tags | 🟢 safe |&lt;br&gt;
  | 4 | 2016-04-22 | Create settings + seed data | 🟢 safe |&lt;br&gt;
  | 5 | 2019-05-22 | Create members (subscriptions) | 🟢 safe |&lt;br&gt;
  | 6 | 2020-06-15 | Stripe payments + subscriptions | 🟢 safe |&lt;br&gt;
  | 7 | 2023-05-23 | Email tracking system | 🟢 safe |&lt;br&gt;
  | 8 | 2025-07-22 | &lt;strong&gt;Drop 10 deprecated columns&lt;/strong&gt; | 🔴 danger |&lt;/p&gt;

&lt;p&gt;That last migration gets flagged red automatically. Ten columns dropped across five tables. In a decade-old project, that kind of change shouldn't pass silently.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Per-Migration Analysis
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;  &lt;span class="n"&gt;Analyzing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20250722191500&lt;/span&gt;&lt;span class="n"&gt;_v6_cleanup_deprecated_fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;sql&lt;/span&gt;
    &lt;span class="err"&gt;🔴&lt;/span&gt; &lt;span class="n"&gt;danger&lt;/span&gt;  &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;Removes&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt; &lt;span class="n"&gt;created_by&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;updated_by&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;
                 &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;members&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;plus&lt;/span&gt; &lt;span class="n"&gt;deletes&lt;/span&gt; &lt;span class="n"&gt;obsolete&lt;/span&gt; &lt;span class="n"&gt;AMP&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="err"&gt;⚠️&lt;/span&gt;  &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt; &lt;span class="n"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;detected&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Not just &lt;em&gt;what&lt;/em&gt; changed — &lt;em&gt;why&lt;/em&gt;. The AI infers that &lt;code&gt;created_by&lt;/code&gt; / &lt;code&gt;updated_by&lt;/code&gt; were replaced by Ghost 6.0's Action Log system. All from reading the SQL.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Complete Data Dictionary
&lt;/h3&gt;

&lt;p&gt;Every table, every column, explained. Here's the &lt;code&gt;posts&lt;/code&gt; table:&lt;/p&gt;

&lt;p&gt;| Column | Type | Description |&lt;br&gt;
  |--------|------|-------------|&lt;br&gt;
  | &lt;code&gt;id&lt;/code&gt; | VARCHAR(24) | Primary key, unique identifier |&lt;br&gt;
  | &lt;code&gt;uuid&lt;/code&gt; | VARCHAR(36) | Universally unique identifier |&lt;br&gt;
  | &lt;code&gt;title&lt;/code&gt; | VARCHAR(2000) | Post title |&lt;br&gt;
  | &lt;code&gt;slug&lt;/code&gt; | VARCHAR(191) | URL-friendly version of title |&lt;br&gt;
  | &lt;code&gt;mobiledoc&lt;/code&gt; | TEXT | Content in Mobiledoc rich-text format |&lt;br&gt;
  | &lt;code&gt;html&lt;/code&gt; | TEXT | Rendered HTML content |&lt;br&gt;
  | &lt;code&gt;plaintext&lt;/code&gt; | TEXT | Plain text version of content |&lt;br&gt;
  | &lt;code&gt;featured_image&lt;/code&gt; | VARCHAR(2000) | URL of the featured image |&lt;br&gt;
  | &lt;code&gt;featured&lt;/code&gt; | TINYINT(1) | Whether the post is featured (0/1) |&lt;br&gt;
  | &lt;code&gt;type&lt;/code&gt; | VARCHAR(50) | &lt;code&gt;post&lt;/code&gt; or &lt;code&gt;page&lt;/code&gt; |&lt;br&gt;
  | &lt;code&gt;status&lt;/code&gt; | VARCHAR(50) | &lt;code&gt;draft&lt;/code&gt;, &lt;code&gt;published&lt;/code&gt;, &lt;code&gt;scheduled&lt;/code&gt; |&lt;br&gt;
  | &lt;code&gt;visibility&lt;/code&gt; | VARCHAR(50) | &lt;code&gt;public&lt;/code&gt;, &lt;code&gt;members&lt;/code&gt;, &lt;code&gt;paid&lt;/code&gt; |&lt;br&gt;
  | &lt;code&gt;locale&lt;/code&gt; | VARCHAR(6) | Language code: &lt;code&gt;en&lt;/code&gt;, &lt;code&gt;zh-CN&lt;/code&gt; |&lt;br&gt;
  | &lt;code&gt;author_id&lt;/code&gt; | VARCHAR(24) | Foreign key to &lt;code&gt;users.id&lt;/code&gt; |&lt;br&gt;
  | &lt;code&gt;published_at&lt;/code&gt; | DATETIME | When the post was published |&lt;/p&gt;

&lt;p&gt;80+ columns across 12 tables. Every single one gets an AI-inferred description, type explanation, and enum values where applicable.&lt;/p&gt;


&lt;h2&gt;
  
  
  This isn't Ghost's problem. It's yours.
&lt;/h2&gt;

&lt;p&gt;Ghost is a well-designed project. But even the best projects have database documentation that slowly rots.&lt;/p&gt;

&lt;p&gt;Your project is no different. Every release adds more &lt;code&gt;.sql&lt;/code&gt; files to &lt;code&gt;migrations/&lt;/code&gt;. Three months later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What's the difference between &lt;code&gt;user_status&lt;/code&gt; and &lt;code&gt;account_state&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Is &lt;code&gt;legacy_score&lt;/code&gt; still used anywhere?&lt;/li&gt;
&lt;li&gt;Who ran that &lt;code&gt;DROP TABLE&lt;/code&gt; and did anyone notice?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't about lacking someone to write documentation. &lt;strong&gt;Humans shouldn't be doing this job.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SQL migrations are already machine-readable. Translating them into human-readable docs is exactly what AI should be doing.&lt;/p&gt;


&lt;h2&gt;
  
  
  Schemalog — your database docs stop lying
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; schemalog

  &lt;span class="nb"&gt;cd &lt;/span&gt;your-project
  schemalog init &lt;span class="nt"&gt;--dir&lt;/span&gt; migrations
  schemalog generate       &lt;span class="c"&gt;# → SCHEMA.md&lt;/span&gt;
  schemalog dict           &lt;span class="c"&gt;# → DATA_DICTIONARY.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Supported formats&lt;/strong&gt;: &lt;code&gt;YYYYMMDD_description.sql&lt;/code&gt; or &lt;code&gt;YYYYMMDDHHMMSS_description.sql&lt;/code&gt;&lt;br&gt;
  Works with Supabase, Drizzle, Flyway, raw SQL — whatever produces timestamped migration files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Providers&lt;/strong&gt;: DeepSeek or OpenAI (GPT-4o-mini). A typical analysis costs a fraction of a cent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incremental caching&lt;/strong&gt;: Second run costs zero — only new or changed migrations hit the AI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Danger detection&lt;/strong&gt;: &lt;code&gt;DROP TABLE&lt;/code&gt;, &lt;code&gt;DROP COLUMN&lt;/code&gt;, &lt;code&gt;ALTER TYPE&lt;/code&gt; flagged locally with zero latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Action&lt;/strong&gt;: PR contains a &lt;code&gt;.sql&lt;/code&gt; migration → auto-comments the changelog. No more digging through migration folders during code review.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Try it out&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; schemalog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;npm&lt;/strong&gt;: &lt;a href="https://www.npmjs.com/package/schemalog" rel="noopener noreferrer"&gt;npmjs.com/package/schemalog&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Questions or feedback? Drop a comment below.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;First release. All feedback welcome.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>database</category>
      <category>sql</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
