<?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: Sualeh Fatehi</title>
    <description>The latest articles on DEV Community by Sualeh Fatehi (@sualeh).</description>
    <link>https://dev.to/sualeh</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%2F369393%2Ff51ae666-51c4-4192-98fc-aafda8a84b53.jpg</url>
      <title>DEV Community: Sualeh Fatehi</title>
      <link>https://dev.to/sualeh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sualeh"/>
    <language>en</language>
    <item>
      <title>SchemaCrawler Has Three Programmatic Models - Here Is When to Use Each One</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 20:42:01 +0000</pubDate>
      <link>https://dev.to/sualeh/schemacrawler-has-three-programmatic-models-here-is-when-to-use-each-one-2gb1</link>
      <guid>https://dev.to/sualeh/schemacrawler-has-three-programmatic-models-here-is-when-to-use-each-one-2gb1</guid>
      <description>&lt;p&gt;Your database already contains one of the most valuable maps of your application: its schema. The problem is that this map is usually trapped behind JDBC metadata calls, vendor-specific system tables, and a sprawling tangle of hundreds of objects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.schemacrawler.com/" rel="noopener noreferrer"&gt;SchemaCrawler&lt;/a&gt; is an open-source database schema discovery and documentation tool. Connect it to almost any JDBC-accessible database and it turns raw metadata into searchable documentation, database diagrams, lint reports, machine-readable output, and a rich Java API. It is designed for the moments when you inherit an undocumented database, need to understand the blast radius of a change, or want to make schema knowledge available to developers and AI agents without relying on tribal knowledge.&lt;/p&gt;

&lt;p&gt;The command line is a fast way to explore and document a schema. The Java API is where SchemaCrawler becomes a building block: you can create a custom report, power an internal developer portal, enforce design rules in CI, or teach an agent how your data fits together.&lt;/p&gt;

&lt;p&gt;You connect to a database and call SchemaCrawler. What do you get back?&lt;/p&gt;

&lt;p&gt;Not one enormous, do-everything object. SchemaCrawler gives you three increasingly expressive models:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Catalog&lt;/code&gt; for physical database metadata.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ERModel&lt;/code&gt; for inferred entity-relationship meaning.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ImportanceModel&lt;/code&gt; for dependency topology, table importance, and domain clustering.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Think of them as three lenses over the same database. Start with facts. Add data-model semantics when you need them. Reach for graph analysis when you need to understand what is central, connected, or naturally grouped.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;Catalog&lt;/code&gt;: the facts-on-the-ground model
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Catalog&lt;/code&gt; is SchemaCrawler's foundational model. It is the in-memory result of crawling database metadata through JDBC, and it represents the physical objects your database actually exposes.&lt;/p&gt;

&lt;p&gt;It gives you schemas, tables, views, columns, indexes, primary keys, foreign keys, routines, sequences, synonyms, users, data types, and database/driver information. Every other model in this article starts here.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;schemacrawler.schema.Catalog&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;schemacrawler.schemacrawler.SchemaCrawlerOptions&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;schemacrawler.tools.utility.SchemaCrawlerUtility&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;us.fatehi.utility.datasource.DatabaseConnectionSource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;DatabaseConnectionSource&lt;/span&gt; &lt;span class="n"&gt;connectionSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* configure JDBC connection */&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;SchemaCrawlerOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SchemaCrawlerOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toOptions&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;Catalog&lt;/span&gt; &lt;span class="n"&gt;catalog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SchemaCrawlerUtility&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCatalog&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionSource&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTables&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s: %d columns%n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFullName&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
      &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getColumns&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API is deliberately direct. Need a table by schema and unqualified name? Use &lt;code&gt;lookupTable&lt;/code&gt;. Need all tables in a schema? Use &lt;code&gt;getTables(schema)&lt;/code&gt;. Need routines, sequences, or synonyms? The &lt;code&gt;Catalog&lt;/code&gt; has those too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;Catalog&lt;/code&gt; when...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You are documenting or inspecting the literal database structure.&lt;/li&gt;
&lt;li&gt;You need DDL-oriented facts: columns, nullability, indexes, constraints, triggers, comments, and privileges.&lt;/li&gt;
&lt;li&gt;You are writing a schema linter, migration check, documentation generator, or custom report.&lt;/li&gt;
&lt;li&gt;You want to decide what is present before making a higher-level interpretation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Catalog&lt;/code&gt; is the right answer for questions such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which tables have no primary key?&lt;/p&gt;

&lt;p&gt;Which views reference this table?&lt;/p&gt;

&lt;p&gt;Does &lt;code&gt;ORDERS&lt;/code&gt; have an index beginning with &lt;code&gt;CUSTOMER_ID&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is intentionally not an opinionated business model. A table is a table, even if it looks suspiciously like a join table or a subtype. That restraint is a feature: physical metadata remains the trustworthy base layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;code&gt;ERModel&lt;/code&gt;: turn tables and keys into a data model
&lt;/h2&gt;

&lt;p&gt;Physical metadata tells you that &lt;code&gt;BOOK_AUTHORS&lt;/code&gt; has two foreign keys. It does not, by itself, tell you that the table is probably expressing a many-to-many relationship between &lt;code&gt;BOOKS&lt;/code&gt; and &lt;code&gt;AUTHORS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the job of &lt;code&gt;ERModel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Build it from the catalog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;schemacrawler.ermodel.model.ERModel&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;schemacrawler.tools.utility.SchemaCrawlerUtility&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;ERModel&lt;/span&gt; &lt;span class="n"&gt;erModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SchemaCrawlerUtility&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buildERModel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;erModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEntities&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFullName&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

&lt;span class="n"&gt;erModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getRelationships&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relationship&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFullName&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SchemaCrawler infers entities and relationships from the schema metadata already present in the &lt;code&gt;Catalog&lt;/code&gt;. That includes foreign keys, primary keys, unique indexes, and table shape. The result distinguishes meaningful model concepts such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong entities and weak entities.&lt;/li&gt;
&lt;li&gt;Subtypes and their supertypes.&lt;/li&gt;
&lt;li&gt;One-to-one, one-to-many, and many-to-many relationships.&lt;/li&gt;
&lt;li&gt;Bridge tables, represented as many-to-many relationships rather than ordinary entities.&lt;/li&gt;
&lt;li&gt;Implicit relationships inferred from catalog metadata.&lt;/li&gt;
&lt;li&gt;Tables and references that could not be modeled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is an important distinction: a bridge table is not merely "a table with two foreign keys." SchemaCrawler's inference uses the surrounding metadata to determine whether the pattern actually represents a many-to-many relationship. That is why application code should reuse &lt;code&gt;ERModel&lt;/code&gt; instead of recreating a quick-but-fragile join-table heuristic.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ERModel&lt;/code&gt; also gives you focused lookups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;erModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lookupEntity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PUBLIC.BOOKS.BOOKS"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ifPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEntityType&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

&lt;span class="n"&gt;erModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lookupRelationship&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PUBLIC.BOOKS.BOOKAUTHORS"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ifPresent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relationship&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;relationship&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCardinality&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Relationship names are meaningful identifiers. Depending on the relationship, a name can correspond to a bridge table, a foreign key, or an implicit relationship. Use the model's lookup methods rather than inventing identifiers from table names.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;ERModel&lt;/code&gt; when...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need to explain the schema as a domain model, not just list its objects.&lt;/li&gt;
&lt;li&gt;You are drawing ER diagrams or exposing relationship-aware tooling.&lt;/li&gt;
&lt;li&gt;You need entity classification, cardinality, supertypes, or bridge-table reasoning.&lt;/li&gt;
&lt;li&gt;You are helping an AI agent or a developer understand the meaning behind foreign keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use it for questions such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this table a strong entity, a subtype, or a bridge table?&lt;/p&gt;

&lt;p&gt;What relationship does this foreign key represent?&lt;/p&gt;

&lt;p&gt;Which tables are not accounted for by the inferred data model?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One practical rule: do not use &lt;code&gt;ERModel&lt;/code&gt; as a replacement for &lt;code&gt;Catalog&lt;/code&gt;. It is a semantic interpretation of the catalog, not a second source of database facts. Keep the &lt;code&gt;Catalog&lt;/code&gt; nearby when you need to inspect the actual columns, keys, or index definitions that support an inference.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;code&gt;ImportanceModel&lt;/code&gt;: find what matters and what belongs together
&lt;/h2&gt;

&lt;p&gt;An ER model answers, "What does this relationship mean?" An importance model answers a different class of question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should I understand first, what could this change affect, and which parts of this schema form a coherent domain?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SchemaCrawler's importance module builds an immutable &lt;code&gt;ImportanceModel&lt;/code&gt; from a &lt;code&gt;Catalog&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;schemacrawler.importance.model.ImportanceModel&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;schemacrawler.importance.model.TableImportance&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;schemacrawler.importance.model.implementation.ImportanceModelBuilder&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;ImportanceModel&lt;/span&gt; &lt;span class="n"&gt;importanceModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ImportanceModelBuilder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTables&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TableImportance&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;getAttribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TableImportance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sorted&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;TableImportance&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]).&lt;/span&gt;&lt;span class="na"&gt;compareTo&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;TableImportance&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schemacrawler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Table&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
      &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;importance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TableImportance&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
      &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%3d %s%n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="n"&gt;importance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;importanceScore&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt;
          &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getFullName&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind that small API is a proper directed dependency graph, implemented with &lt;a href="https://jgrapht.org/" rel="noopener noreferrer"&gt;JGraphT&lt;/a&gt;. Vertices represent tables, views, routines, and synonyms. Typed edges represent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Foreign keys.&lt;/li&gt;
&lt;li&gt;Implied associations.&lt;/li&gt;
&lt;li&gt;View dependencies.&lt;/li&gt;
&lt;li&gt;Routine dependencies.&lt;/li&gt;
&lt;li&gt;Synonym resolution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catalog graph is exposed read-only through &lt;code&gt;getCatalogGraph()&lt;/code&gt;, while &lt;code&gt;lookupByVertexId()&lt;/code&gt; maps graph vertices back to their SchemaCrawler objects. That makes the graph useful both for built-in analysis and for your own JGraphT algorithms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Importance is more than "most foreign keys wins"
&lt;/h3&gt;

&lt;p&gt;When the importance model is built, SchemaCrawler calculates topology metrics for every vertex:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In-degree and out-degree.&lt;/li&gt;
&lt;li&gt;Betweenness centrality.&lt;/li&gt;
&lt;li&gt;Dependency reachability: what the object transitively depends on.&lt;/li&gt;
&lt;li&gt;Impact reachability: what can be reached when dependency edges are traversed backward.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For tables and views, it combines those signals with physical and ER-derived traits into a reproducible, catalog-relative score from 0 to 100. Structural signals contribute 50% of the score; entity role, attribute columns, row count, foreign key count, trigger count, and self-reference contribute the other 50%.&lt;/p&gt;

&lt;p&gt;That blend avoids a classic graph-analysis trap. A bridge table can have high betweenness simply because it sits between two entities. The importance calculation knows that a bridge table is structurally useful without mistaking it for the business entity at the center of your system.&lt;/p&gt;

&lt;p&gt;The computed &lt;code&gt;TableImportance&lt;/code&gt; is stored as an attribute on each table, so you can reuse it without rebuilding your own scorecard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clusters reveal domains hiding in plain sight
&lt;/h3&gt;

&lt;p&gt;SchemaCrawler also detects table clusters: groups of related tables and views that often correspond to a functional area of the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;importanceModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTableClusters&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tableCluster&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Anchor: %s%n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tableCluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;anchorVertexId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
  &lt;span class="n"&gt;tableCluster&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;memberVertexIds&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The clustering process intentionally ignores dependency direction for this purpose. A customer table and an order table are closely related whether the foreign-key arrow points from orders to customers or the other way around. SchemaCrawler takes the table/view subgraph, treats it as undirected for affinity, and runs deterministic label-propagation clustering.&lt;/p&gt;

&lt;p&gt;Only clusters with at least three members are reported. Within each one, members are ordered by importance, and the highest-ranked member becomes its anchor. The result is stable enough to use in reports, onboarding material, or an AI-assisted schema exploration workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;ImportanceModel&lt;/code&gt; when...
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You inherited a large, undocumented database and need a sensible starting point.&lt;/li&gt;
&lt;li&gt;You need blast-radius analysis before changing a table, view, or routine.&lt;/li&gt;
&lt;li&gt;You want to identify central tables without over-rewarding thin bridge tables.&lt;/li&gt;
&lt;li&gt;You want to group a sprawling schema into likely business domains.&lt;/li&gt;
&lt;li&gt;You want to apply custom JGraphT traversals or graph algorithms to schema dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the model for questions such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What are the five most important tables to learn first?&lt;/p&gt;

&lt;p&gt;What depends on this table, directly or transitively?&lt;/p&gt;

&lt;p&gt;Which tables belong to the same likely functional area?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Pick the smallest lens that answers the question
&lt;/h2&gt;

&lt;p&gt;Here is the cheat sheet:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you need to...&lt;/th&gt;
&lt;th&gt;Start with...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inspect columns, indexes, keys, routines, or database metadata&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Catalog&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explain entity roles, cardinality, subtypes, or many-to-many relationships&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ERModel&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rank central objects, analyze impact, find paths, or identify domains&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ImportanceModel&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The models are complementary, not competing. The most effective workflow is usually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Catalog -&amp;gt; ERModel -&amp;gt; ImportanceModel
 facts     meaning    topology and priorities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start with the catalog to establish what is real. Build the ER model when the shape of the schema needs interpretation. Build the importance model when the question becomes "where do I start?" or "what is connected to what?"&lt;/p&gt;

&lt;p&gt;That progression turns raw JDBC metadata into something much more useful: a map of the database, a model of its relationships, and a guide to the parts that deserve your attention first.&lt;/p&gt;

&lt;p&gt;For a deeper dive into scores, metrics, and the &lt;code&gt;importance&lt;/code&gt; command, see &lt;a href="https://dev.to/sualeh/rank-your-database-tables-by-importance-with-schemacrawler-1gib"&gt;Rank Your Database Tables by Importance with SchemaCrawler&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>java</category>
      <category>opensource</category>
      <category>tools</category>
    </item>
    <item>
      <title>Rank Your Database Tables by Importance with SchemaCrawler</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Sun, 06 Sep 2026 20:22:22 +0000</pubDate>
      <link>https://dev.to/sualeh/rank-your-database-tables-by-importance-with-schemacrawler-1gib</link>
      <guid>https://dev.to/sualeh/rank-your-database-tables-by-importance-with-schemacrawler-1gib</guid>
      <description>&lt;p&gt;You've just been handed a database with 400 tables and no documentation. Where do you even start?&lt;/p&gt;

&lt;p&gt;Most people start guessing. Maybe the table with the most rows? Maybe the one with the longest name, because surely someone spent a lot of time on it? Maybe just alphabetical order, because at least it's deterministic? None of these actually tell you which tables matter.&lt;/p&gt;

&lt;p&gt;SchemaCrawler has a better answer: build a graph of your schema's dependencies, run some graph theory on it, blend in a bit of data-modeling common sense, and hand you a single ranked list. That's the &lt;code&gt;importance&lt;/code&gt; command, and this article walks through why it exists, how the score is computed, and how you can plug it into an AI agent so it can explore your schema the same way an experienced engineer would.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why importance, not just size
&lt;/h2&gt;

&lt;p&gt;Row count and column count are cheap proxies for "this table matters," but they're misleading. A &lt;code&gt;CUSTOMERS&lt;/code&gt; table with a thousand rows and a dozen well-designed foreign keys pointing at it is probably far more important to your application's logic than a million-row &lt;code&gt;EVENT_LOG&lt;/code&gt; table nobody queries directly. Size measures volume. It doesn't measure how central a table is to the way the rest of the schema depends on it.&lt;/p&gt;

&lt;p&gt;What you actually want to know is: if I had to explain this schema to a new teammate, or if I had to change this table and needed to know what else might break, which tables would I need to understand first?&lt;/p&gt;

&lt;p&gt;That's a graph problem, not a row-counting problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Graph theory made accessible
&lt;/h2&gt;

&lt;p&gt;SchemaCrawler builds a directed graph over your entire catalog: tables, views, routines, and synonyms are nodes, and foreign keys, view dependencies, routine table access, and synonym resolution become edges. Once that graph exists, some classic graph metrics fall out of it for free, computed once for every table and view:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In-degree&lt;/strong&gt; and &lt;strong&gt;out-degree&lt;/strong&gt; — how many things point at this table, and how many things this table points at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Betweenness centrality&lt;/strong&gt; — computed on an undirected view of the graph, this measures how often a table sits on the shortest path between two &lt;em&gt;other&lt;/em&gt; tables. High betweenness means a table is a structural bridge — remove it, and other parts of the schema become harder to reach from each other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency reachability count&lt;/strong&gt; — how many other objects this table transitively depends on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact reachability count&lt;/strong&gt; — how many other objects would be reachable (and therefore potentially affected) if you changed this table, by walking dependency edges backward into it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've ever used a graph library like &lt;a href="https://jgrapht.org/" rel="noopener noreferrer"&gt;JGraphT&lt;/a&gt; to analyze a social network or a road network, this will feel familiar — a database schema is just another kind of graph, and the same centrality and reachability concepts apply directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why centrality alone isn't enough — bridge tables versus real entities
&lt;/h2&gt;

&lt;p&gt;Here's the catch: betweenness centrality, on its own, tends to reward &lt;strong&gt;bridge tables&lt;/strong&gt; — the thin many-to-many association tables (think &lt;code&gt;BOOK_AUTHORS&lt;/code&gt; linking &lt;code&gt;BOOKS&lt;/code&gt; to &lt;code&gt;AUTHORS&lt;/code&gt;) that sit between two other entities purely to connect them. A bridge table can have very high betweenness centrality simply because it's structurally in the middle of a lot of shortest paths, even though it might only have two or three foreign key columns and carry no meaningful business data of its own.&lt;/p&gt;

&lt;p&gt;Meanwhile, a rich strong entity table like &lt;code&gt;BOOKS&lt;/code&gt; — with real attribute columns, a solid primary key, indexes, and a well-understood business meaning — might have lower betweenness centrality just because it's not sitting &lt;em&gt;between&lt;/em&gt; other tables in the graph.&lt;/p&gt;

&lt;p&gt;If you rank purely by centrality, bridge tables can out-rank the tables that actually carry your business data. That's backwards from what most people mean when they ask "which tables are important."&lt;/p&gt;

&lt;p&gt;SchemaCrawler fixes this by computing a &lt;strong&gt;composite importance score&lt;/strong&gt; — an integer from 0 to 100 — that blends structural graph signals with data-modeling signals:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Structural Half&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;betweenness centrality — graph‑based influence measure
&lt;/li&gt;
&lt;li&gt;impact reachability — propagation potential across dependencies
&lt;/li&gt;
&lt;li&gt;total degree — combined in‑ and out‑degree&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Data‑Modeling Half (50%)&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;entity role — conceptual importance in schema
&lt;/li&gt;
&lt;li&gt;attribute column count — structural richness
&lt;/li&gt;
&lt;li&gt;row count — data volume
&lt;/li&gt;
&lt;li&gt;foreign key count — relational connectivity
&lt;/li&gt;
&lt;li&gt;trigger count — behavioral complexity
&lt;/li&gt;
&lt;li&gt;self‑referencing — recursive relationships&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entity role term draws on SchemaCrawler's entity-relationship classification (strong entity, weak entity, subtype, non-entity) plus one graph-specific addition, &lt;code&gt;bridge_table&lt;/code&gt;, and weights them so that strong and weak entities rank above bridge tables &lt;em&gt;within that term&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;entity type&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;score&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a&gt;strong_entity&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a&gt;weak_entity&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a&gt;subtype&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a&gt;bridge_table&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.55&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a&gt;non_entity&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a&gt;unknown&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two dampeners then shave points off tables with design smells — no primary key (-15%) or no indexes at all (-10%) — because a table that's missing these fundamentals is less trustworthy as a "load-bearing" part of your schema, no matter how central it looks structurally.&lt;/p&gt;

&lt;p&gt;The result: a genuinely central, richly-attributed entity table scores highest. A well-connected bridge table can still score respectably (structural signals are half the formula, after all), but it no longer beats out the entities it's connecting purely by an accident of graph position.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;,target&lt;span class="o"&gt;=&lt;/span&gt;/home/schcrwlr/share &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  schemacrawler/schemacrawler &lt;span class="se"&gt;\&lt;/span&gt;
  /opt/schemacrawler/bin/schemacrawler.sh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sc.db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--info-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;standard &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;importance &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;text &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;share/importance.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;--table-filter='.*\.BOOKS.*'&lt;/code&gt; with a regular expression to narrow the report to matching tables and views. Output is available as &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;, or &lt;code&gt;yaml&lt;/code&gt;, sorted by importance score (descending), with betweenness centrality as a tie-breaker. Communities are calculated once when SchemaCrawler builds the schema graph, then reused in the report.&lt;/p&gt;

&lt;p&gt;Here is an excerpt of what the output looks like in JSON format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clusters"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a61b0ea7-c688-3e35-ab3b-b7593f7fd788"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"anchor_table_full_name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLIC.BOOKS.BOOKS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_cluster_size"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"member_table_full_names"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLIC.BOOKS.BOOKS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLIC.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;PUBLISHER SALES&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;.SALES"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLIC.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;PUBLISHER SALES&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;.REGIONS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLIC.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;PUBLISHER SALES&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;.SALESDATA"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tables"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"table_full_name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUBLIC.BOOKS.BOOKS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"table_importance"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"importance_score"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"table_traits"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"self_referencing"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"entity_model_type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"strong_entity"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"table_counts"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"attribute_column_count"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"column_count"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"foreign_key_count"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"index_count"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"trigger_count"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"importance_metrics"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"in_degree"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"out_degree"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"betweenness_centrality"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"dependency_reachability_count"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"impact_reachability_count"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Letting an AI agent use this directly
&lt;/h2&gt;

&lt;p&gt;This is where it gets genuinely useful for AI-assisted workflows. The &lt;a href="https://github.com/schemacrawler/SchemaCrawler-AI-MCP-Server-Usage" rel="noopener noreferrer"&gt;SchemaCrawler AI MCP Server&lt;/a&gt; exposes these same metrics as tools an AI agent can call directly, without you having to explain your schema by hand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;table_importance&lt;/code&gt;&lt;/strong&gt; — returns graph importance metrics for tables and views, including dependency centrality, table counts, and traits. Accepts an optional regular expression filter (&lt;code&gt;table_name&lt;/code&gt;) and an optional maximum table count (&lt;code&gt;max_tables&lt;/code&gt;, defaulting to 5; pass 0 or a negative integer to return all tables).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;table_path&lt;/code&gt;&lt;/strong&gt; — finds the shortest forward dependency path from one table or view to another, preferring foreign-key relationships and falling back to implied associations only when no formal foreign key exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put these together, and an AI agent connected to your database through the MCP server can answer questions like "what are the five most important tables in this schema, and why?" or "what's the shortest dependency path from &lt;code&gt;ORDERS&lt;/code&gt; to &lt;code&gt;SHIPPING_ADDRESSES&lt;/code&gt;?" — grounded in actual graph analysis of your schema, not a guess based on table names. That turns a cold, undocumented database into something an AI coding assistant can reason about the same way a human engineer familiar with the system would.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;If you want the full formula, the complete weight tables, and sample JSON output, see the &lt;a href="https://www.schemacrawler.com/importance.html" rel="noopener noreferrer"&gt;graph metrics and importance page&lt;/a&gt; on the SchemaCrawler website. If you want to see this in action against your own database, all you need is the &lt;code&gt;importance&lt;/code&gt; command and a JDBC connection — no separate setup required.&lt;/p&gt;

&lt;p&gt;Understand SchemaCrawler's programmatic models. See &lt;a href="https://dev.to/sualeh/schemacrawler-has-three-programmatic-models-here-is-when-to-use-each-one-2gb1"&gt;SchemaCrawler Has Three Programmatic Models - Here Is When to Use Each One&lt;/a&gt;&lt;/p&gt;

</description>
      <category>data</category>
      <category>database</category>
      <category>tools</category>
    </item>
    <item>
      <title>Migrate from SchemaSpy to SchemaCrawler Without Rewriting Your Runbooks</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Wed, 26 Aug 2026 23:10:12 +0000</pubDate>
      <link>https://dev.to/sualeh/migrate-from-schemaspy-to-schemacrawler-without-rewriting-your-runbooks-34k9</link>
      <guid>https://dev.to/sualeh/migrate-from-schemaspy-to-schemacrawler-without-rewriting-your-runbooks-34k9</guid>
      <description>&lt;p&gt;If you already use SchemaSpy to document a database, moving to SchemaCrawler does not have to mean replacing every script at once.&lt;/p&gt;

&lt;p&gt;The latest SchemaCrawler release includes a SchemaSpy-compatible shim. It accepts common SchemaSpy command-line arguments, connects to the database, and runs SchemaCrawler Scribe to produce documentation in Google Open Knowledge Format (OKF).&lt;/p&gt;

&lt;p&gt;That gives you a gradual migration path: keep the command-line shape your team knows, while changing the output from a generated website bundle to a set of Markdown files that can live in Git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the SchemaSpy command you already have
&lt;/h2&gt;

&lt;p&gt;The shim is called &lt;code&gt;schemaspy&lt;/code&gt; and is included in the SchemaCrawler Docker image and the other SchemaCrawler installers.&lt;/p&gt;

&lt;p&gt;For example, a SchemaSpy command for a SQLite database might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;schemaspy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; sqlite &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-db&lt;/span&gt; ./sc.db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; sa &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; ./schema-output.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With SchemaCrawler installed, the same command produces an OKF bundle. The adapter translates the SchemaSpy options and invokes SchemaCrawler Scribe behind the scenes.&lt;/p&gt;

&lt;p&gt;You can use the Docker image without installing Java locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;,target&lt;span class="o"&gt;=&lt;/span&gt;/home/schcrwlr/share &lt;span class="se"&gt;\&lt;/span&gt;
  schemacrawler/schemacrawler &lt;span class="se"&gt;\&lt;/span&gt;
  schemaspy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; sqlite &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-db&lt;/span&gt; /home/schcrwlr/sc.db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; sa &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; share/schema.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell, use a backtick instead of a backslash for line continuation. The Docker mount makes the database and generated ZIP available in your current directory.&lt;/p&gt;

&lt;p&gt;To see the database types supported by the adapter, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;schemaspy &lt;span class="nt"&gt;-dbhelp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; schemacrawler/schemacrawler schemaspy &lt;span class="nt"&gt;-dbhelp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shim is intended to make migration practical, not to promise that every SchemaSpy-specific option has an identical meaning. Supported options are translated, compatibility options may be accepted as no-ops, and unsupported options report an explicit error. Check the adapter documentation when converting a more specialized command.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Google OKF changes
&lt;/h2&gt;

&lt;p&gt;SchemaSpy's usual output is a browser-oriented HTML report. That is useful when someone wants to click through a schema, but HTML is not always convenient as a source artifact. It is difficult to review meaningfully in a pull request, search with ordinary text tools, or give to another tool without first scraping the pages.&lt;/p&gt;

&lt;p&gt;Google OKF uses Markdown files with YAML frontmatter and links between related documents. A generated bundle contains files such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;index.md
tables/
  authors.md
  books.md
cross-references/
  index.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact tree depends on the database, but the important property is that the documentation is ordinary text. Developers can open it in Visual Studio Code, review it in GitHub, and diff one schema snapshot against another.&lt;/p&gt;

&lt;p&gt;The pages can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tables, columns, keys, constraints, triggers, and references&lt;/li&gt;
&lt;li&gt;routines such as functions and stored procedures&lt;/li&gt;
&lt;li&gt;cross-reference pages for relationships&lt;/li&gt;
&lt;li&gt;Mermaid diagrams embedded in Markdown&lt;/li&gt;
&lt;li&gt;optional lint reports for schema design issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The YAML frontmatter also makes each document easier for static-site generators and indexing tools to understand. For an AI agent, the result is a collection of small, named, linked documents instead of one opaque report. For a developer, it is still just Markdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run Scribe directly when you are ready
&lt;/h2&gt;

&lt;p&gt;Once the compatibility shim is working, you can switch to SchemaCrawler's native command line. This makes the output options explicit and gives you access to SchemaCrawler features such as linting and row counts.&lt;/p&gt;

&lt;p&gt;Here is a Docker example that writes an expanded OKF directory instead of a ZIP file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;,target&lt;span class="o"&gt;=&lt;/span&gt;/home/schcrwlr/share &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  schemacrawler/schemacrawler &lt;span class="se"&gt;\&lt;/span&gt;
  /opt/schemacrawler/bin/schemacrawler.sh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/schcrwlr/sc.db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--info-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;maximum &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--command&lt;/span&gt; scribe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-format&lt;/span&gt; okf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Books Database"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--expanded-output&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--include-lint&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--load-row-counts&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;share/schema.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compatibility command is a good first step. The native command is a good long-term choice for a new automation job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the documentation into GitHub
&lt;/h2&gt;

&lt;p&gt;An OKF directory is a natural build artifact for a documentation repository. A simple repository layout might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── .github/workflows/schema-docs.yml
├── schema/
│   ├── index.md
│   ├── tables/
│   └── cross-references/
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can generate the files on a schedule or whenever your schema migrations are merged. Commit the generated Markdown to a branch, or publish it as the Pages source from a dedicated documentation repository.&lt;/p&gt;

&lt;p&gt;Checking in generated output has a useful side effect: schema changes become visible in ordinary pull requests. A changed column, new foreign key, or removed table appears as a text diff. The database remains the source of truth, while Git records how its documented shape changed over time.&lt;/p&gt;

&lt;p&gt;Do not commit database passwords or connection strings containing secrets. Store credentials in GitHub Actions secrets and pass them to the container as environment variables or workflow inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publish the OKF bundle with GitHub Pages
&lt;/h2&gt;

&lt;p&gt;GitHub Pages can publish Markdown content using Jekyll. Because OKF pages include YAML frontmatter, the generated directory can be used as the source for a Pages site. A minimal workflow can generate the documentation and deploy it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish schema documentation&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;pages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate OKF documentation&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;mkdir -p public&lt;/span&gt;
          &lt;span class="s"&gt;docker run --rm \&lt;/span&gt;
            &lt;span class="s"&gt;--mount type=bind,source="$PWD",target=/home/schcrwlr/share \&lt;/span&gt;
            &lt;span class="s"&gt;schemacrawler/schemacrawler \&lt;/span&gt;
            &lt;span class="s"&gt;/opt/schemacrawler/bin/schemacrawler.sh \&lt;/span&gt;
            &lt;span class="s"&gt;--server=sqlite \&lt;/span&gt;
            &lt;span class="s"&gt;--database=/home/schcrwlr/share/sc.db \&lt;/span&gt;
            &lt;span class="s"&gt;--info-level=maximum \&lt;/span&gt;
            &lt;span class="s"&gt;--command scribe \&lt;/span&gt;
            &lt;span class="s"&gt;--output-format okf \&lt;/span&gt;
            &lt;span class="s"&gt;--title "Books Database" \&lt;/span&gt;
            &lt;span class="s"&gt;--expanded-output \&lt;/span&gt;
            &lt;span class="s"&gt;--include-lint \&lt;/span&gt;
            &lt;span class="s"&gt;--output-file=/home/schcrwlr/share/public&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure Pages&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/configure-pages@v5&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build with Jekyll&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/jekyll-build-pages@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;public&lt;/span&gt;
          &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;_site&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Pages artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-pages-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;_site&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to GitHub Pages&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deployment&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/deploy-pages@v4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a database that is not a file in the repository, replace the SQLite connection with your database server and provide the connection values through GitHub Actions secrets. You may also choose to commit the generated &lt;code&gt;schema&lt;/code&gt; directory and use a separate Pages workflow that publishes that checked-in directory.&lt;/p&gt;

&lt;p&gt;After enabling GitHub Pages for the repository, the workflow publishes &lt;code&gt;index.md&lt;/code&gt; as the entry point and keeps the links to table and cross-reference pages intact. The result is a browsable schema reference at the same URL your team can use in tickets, pull requests, and onboarding notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical migration sequence
&lt;/h2&gt;

&lt;p&gt;You do not need to change everything in one pull request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Replace the SchemaSpy executable with the SchemaCrawler &lt;code&gt;schemaspy&lt;/code&gt; shim.&lt;/li&gt;
&lt;li&gt;Confirm that your database connection and filtering options produce the expected OKF bundle.&lt;/li&gt;
&lt;li&gt;Add the generated Markdown to a Git repository and review the first schema diff.&lt;/li&gt;
&lt;li&gt;Publish the directory with GitHub Pages if a hosted reference site is useful.&lt;/li&gt;
&lt;li&gt;Move mature scripts to the native &lt;code&gt;scribe&lt;/code&gt; command and add options such as &lt;code&gt;--include-lint&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SchemaSpy and SchemaCrawler both work with relational database metadata over JDBC, so the database connection remains familiar. The main change is what you do with the result: instead of treating documentation as a report to regenerate and distribute, you can treat it as structured source material that belongs in your repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn more
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.schemacrawler.com/" rel="noopener noreferrer"&gt;SchemaCrawler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.schemacrawler.com/scribe.html" rel="noopener noreferrer"&gt;SchemaCrawler Scribe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.schemacrawler.com/schemaspy-adapter.html" rel="noopener noreferrer"&gt;SchemaSpy Adapter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md" rel="noopener noreferrer"&gt;Google Open Knowledge Format specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>devops</category>
      <category>github</category>
      <category>opensource</category>
    </item>
    <item>
      <title>SchemaCrawler Scribe + Google OKF: AI-Ready Database Docs You Can Keep in Git</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Tue, 14 Jul 2026 23:58:09 +0000</pubDate>
      <link>https://dev.to/sualeh/schemacrawler-scribe-google-okf-ai-ready-database-docs-you-can-keep-in-git-2off</link>
      <guid>https://dev.to/sualeh/schemacrawler-scribe-google-okf-ai-ready-database-docs-you-can-keep-in-git-2off</guid>
      <description>&lt;p&gt;If your database documentation is always behind production, this is for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SchemaCrawler Scribe&lt;/strong&gt; generates structured database documentation directly from live schema metadata, using &lt;strong&gt;&lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/d44368c15e38e7c92481c5992e4f9b5b421a801d/okf/SPEC.md" rel="noopener noreferrer"&gt;Google Open Knowledge Format (OKF)&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The result is documentation that works for both developers and AI agents, without creating a second documentation workflow.&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/schemacrawler/SchemaCrawler/tree/main/schemacrawler-scribe" rel="noopener noreferrer"&gt;SchemaCrawler/schemacrawler-scribe&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem SchemaCrawler Scribe Solves
&lt;/h2&gt;

&lt;p&gt;Most teams end up in one of two modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Manual docs that drift from reality&lt;/li&gt;
&lt;li&gt;Generated docs that are hard to read, hard to diff, or hard to reuse&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SchemaCrawler Scribe targets the middle ground:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;readable Markdown that developers can review&lt;/li&gt;
&lt;li&gt;structured content that AI systems can parse&lt;/li&gt;
&lt;li&gt;plain text artifacts that belong in version control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns documentation into part of your engineering workflow, not a side task.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Get With SchemaCrawler Scribe
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AI-agent-friendly database documentation&lt;/li&gt;
&lt;li&gt;Documentation generated directly from schema metadata&lt;/li&gt;
&lt;li&gt;Localized output (German, French, and more)&lt;/li&gt;
&lt;li&gt;Mermaid diagrams embedded in Markdown&lt;/li&gt;
&lt;li&gt;Artifacts that are both human-readable and machine-parseable&lt;/li&gt;
&lt;li&gt;Straightforward use in VS Code and other Markdown tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Google OKF is a Strong Format for Database Docs
&lt;/h2&gt;

&lt;p&gt;SchemaCrawler Scribe outputs in &lt;a href="https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/d44368c15e38e7c92481c5992e4f9b5b421a801d/okf/SPEC.md" rel="noopener noreferrer"&gt;Google Open Knowledge Format (OKF)&lt;/a&gt;, which gives you one format that serves multiple use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Human-friendly&lt;/strong&gt;: Markdown pages are readable in editors, code review tools, and docs portals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-friendly&lt;/strong&gt;: structured sections and metadata make extraction and grounding more reliable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git-friendly&lt;/strong&gt;: text output is diffable, reviewable, and easy to version&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool-friendly&lt;/strong&gt;: open format reduces lock-in and keeps migration options open&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team-friendly&lt;/strong&gt;: works well in Visual Studio Code and any Markdown-centric workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: one documentation artifact that supports people, automation, and long-term maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  If You Use SchemaSpy Today, This Will Feel Familiar
&lt;/h2&gt;

&lt;p&gt;SchemaCrawler Scribe is in the same family of tooling as SchemaSpy: both crawl schema metadata and generate browsable documentation.&lt;/p&gt;

&lt;p&gt;Like SchemaSpy-style documentation workflows, Scribe covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;per-table pages (columns, keys, constraints, triggers, references)&lt;/li&gt;
&lt;li&gt;cross-reference pages (what references what)&lt;/li&gt;
&lt;li&gt;routine documentation (functions and stored procedures)&lt;/li&gt;
&lt;li&gt;relationship visualization (Mermaid in Markdown)&lt;/li&gt;
&lt;li&gt;lint and anomaly reporting in generated output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if your team already likes auto-generated schema docs and relationship views, SchemaCrawler Scribe keeps that experience while producing &lt;strong&gt;Google OKF Markdown&lt;/strong&gt; artifacts that are easier to review in Git and consume with AI tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;Run standard SchemaCrawler from the command line or Docker. Use the &lt;code&gt;scribe&lt;/code&gt; command with &lt;code&gt;okf&lt;/code&gt; output format.&lt;/p&gt;

&lt;p&gt;Tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;--title&lt;/code&gt; to label the generated documentation set&lt;/li&gt;
&lt;li&gt;By default, output is zipped; add &lt;code&gt;--expanded-output&lt;/code&gt; to generate a directory tree instead&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;--include-lint&lt;/code&gt; to generate schema design issue reports
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;,target&lt;span class="o"&gt;=&lt;/span&gt;/home/schcrwlr/share &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  schemacrawler/schemacrawler &lt;span class="se"&gt;\&lt;/span&gt;
  /opt/schemacrawler/bin/schemacrawler.sh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sc.db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--info-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;maximum &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--command&lt;/span&gt; scribe &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-format&lt;/span&gt; okf &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"Books Database"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--expanded-output&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--include-lint&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--load-row-counts&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;share/schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using PowerShell, replace each trailing backslash with a backtick for line continuation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Also see
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/schemacrawler/SchemaCrawler-AI-MCP-Server-Usage" rel="noopener noreferrer"&gt;SchemaCrawler AI MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>documentation</category>
      <category>okf</category>
    </item>
    <item>
      <title>How to Diff Database Migrations Automatically in CI/CD</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Sun, 21 Jun 2026 16:16:25 +0000</pubDate>
      <link>https://dev.to/sualeh/how-to-diff-database-migrations-automatically-in-cicd-n1g</link>
      <guid>https://dev.to/sualeh/how-to-diff-database-migrations-automatically-in-cicd-n1g</guid>
      <description>&lt;p&gt;Database regressions rarely start with application code. They start when a migration changes a column type, removes an index, or introduces a nullable relationship that your code assumed was mandatory. The pull request looks fine, tests pass, and then production fails because the shape of the database drifted.&lt;/p&gt;

&lt;p&gt;You can catch this in CI/CD by generating a text snapshot of your schema and diffing it as part of every pull request.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.schemacrawler.com/" rel="noopener noreferrer"&gt;SchemaCrawler&lt;/a&gt; is a practical fit for this workflow because it outputs stable, diff-friendly schema text and runs cleanly in GitHub Actions through the &lt;a href="https://github.com/schemacrawler/SchemaCrawler-Action" rel="noopener noreferrer"&gt;SchemaCrawler Action&lt;/a&gt; (which uses Docker) or the &lt;a href="https://github.com/schemacrawler/SchemaCrawler-Local-Action" rel="noopener noreferrer"&gt;SchemaCrawler Local Action&lt;/a&gt; (which installs SchemaCrawler on your runner).&lt;/p&gt;

&lt;p&gt;The approach below gives you two protections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A readable schema diff in your build logs.&lt;/li&gt;
&lt;li&gt;A failed CI check when the schema changes unexpectedly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Generate Diff-Friendly Schema Snapshots Locally
&lt;/h2&gt;

&lt;p&gt;Start by proving the workflow on your machine. Use two SQLite files (for example, before and after applying a migration) and compare the generated schema snapshots. Let us say that the files are called "database-before.db" and "database-after.db" on your local file system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Schema Before Migration&lt;/span&gt;
docker run &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;,target&lt;span class="o"&gt;=&lt;/span&gt;/home/schcrwlr/share &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
schemacrawler/schemacrawler &lt;span class="se"&gt;\&lt;/span&gt;
/opt/schemacrawler/bin/schemacrawler.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;share/database-before.db &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--info-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;standard &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;schema &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--output-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;text &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--output-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;share/schema-before.txt

&lt;span class="c"&gt;# Schema After Migration&lt;/span&gt;
docker run &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--mount&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;,source&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;,target&lt;span class="o"&gt;=&lt;/span&gt;/home/schcrwlr/share &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
schemacrawler/schemacrawler &lt;span class="se"&gt;\&lt;/span&gt;
/opt/schemacrawler/bin/schemacrawler.sh &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sqlite &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;share/database-after.db &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--info-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;standard &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;schema &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--output-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;text &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--output-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;share/schema-after.txt

&lt;span class="c"&gt;# Diff&lt;/span&gt;
diff &lt;span class="nt"&gt;-u&lt;/span&gt; schema-before.txt schema-after.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using PowerShell on Windows, replace each trailing backslash with a back-tick.&lt;/p&gt;

&lt;p&gt;The output is plain text, so your diffs are reviewable in pull requests and easy to archive as artifacts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Add Schema Diff to GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Now automate the same check in CI/CD. The workflow below runs on pull requests, generates one schema snapshot from the base branch and one from the PR branch, then fails if they differ.&lt;/p&gt;

&lt;p&gt;It uses &lt;a href="https://github.com/schemacrawler/SchemaCrawler-Action" rel="noopener noreferrer"&gt;schemacrawler/SchemaCrawler-Action&lt;/a&gt; to run SchemaCrawler in the pipeline. You can also use &lt;a href="https://github.com/schemacrawler/SchemaCrawler-Local-Action" rel="noopener noreferrer"&gt;SchemaCrawler Local Action&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Detect Database Schema Drift&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schema-diff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout PR branch&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

      &lt;span class="c1"&gt;# In a real project, replace this with your migration/build step&lt;/span&gt;
      &lt;span class="c1"&gt;# that creates the database file used for comparison.&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schema-head&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate schema snapshot (PR)&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schemacrawler/SchemaCrawler-Action@v17.11.3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/schemacrawler.sh&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="s"&gt;--server=sqlite&lt;/span&gt;
            &lt;span class="s"&gt;--database=/home/schcrwlr/sc.db&lt;/span&gt;
            &lt;span class="s"&gt;--info-level=standard&lt;/span&gt;
            &lt;span class="s"&gt;--command=schema&lt;/span&gt;
            &lt;span class="s"&gt;--output-format=text&lt;/span&gt;
            &lt;span class="s"&gt;--output-file=schema-pr.txt&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout base branch commit&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git checkout ${{ github.event.pull_request.base.sha }}&lt;/span&gt;

      &lt;span class="c1"&gt;# Run your base-branch migration/build step here as well.&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schema-base&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate schema snapshot (base)&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schemacrawler/SchemaCrawler-Action@v17.11.3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/schemacrawler.sh&lt;/span&gt;
          &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="s"&gt;--server=sqlite&lt;/span&gt;
            &lt;span class="s"&gt;--database=/home/schcrwlr/sc.db&lt;/span&gt;
            &lt;span class="s"&gt;--info-level=standard&lt;/span&gt;
            &lt;span class="s"&gt;--command=schema&lt;/span&gt;
            &lt;span class="s"&gt;--output-format=text&lt;/span&gt;
            &lt;span class="s"&gt;--output-file=schema-base.txt&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Diff schema snapshots&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git checkout ${{ github.sha }}&lt;/span&gt;
          &lt;span class="s"&gt;if ! diff -u schema-base.txt schema-pr.txt &amp;gt; schema.diff; then&lt;/span&gt;
            &lt;span class="s"&gt;echo "Schema drift detected between base and PR."&lt;/span&gt;
            &lt;span class="s"&gt;cat schema.diff&lt;/span&gt;
            &lt;span class="s"&gt;exit 1&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload schema diff artifact&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schema-diff&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;schema.diff&lt;/span&gt;
          &lt;span class="na"&gt;if-no-files-found&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ignore&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Make the Differences Actionable
&lt;/h2&gt;

&lt;p&gt;A failing check is useful only if developers can fix it quickly. Use these conventions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the diff in CI logs so reviewers can see what changed.&lt;/li&gt;
&lt;li&gt;Upload &lt;code&gt;schema.diff&lt;/code&gt; as a build artifact for larger diffs.&lt;/li&gt;
&lt;li&gt;Decide policy per repository: just produce an informative report without failing, or fail on every schema change, or fail only when specific objects change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your schema changes are expected, the PR should include both migration code and reviewer-approved schema diff output. If a diff is unexpected, you have caught a regression before merge.&lt;/p&gt;

&lt;p&gt;This is the core CI/CD hook:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Catch database schema regressions in pull requests before they reach production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Notes for Production Databases
&lt;/h2&gt;

&lt;p&gt;For PostgreSQL, MySQL, SQL Server, Oracle, and other JDBC databases, keep the same pattern and change only connection options. The strongest setup is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provision an ephemeral database in CI.&lt;/li&gt;
&lt;li&gt;Apply base branch migrations and snapshot schema.&lt;/li&gt;
&lt;li&gt;Apply PR branch migrations and snapshot schema.&lt;/li&gt;
&lt;li&gt;Diff snapshots and fail on unexpected changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can start with SQLite to validate the workflow and then switch to your production engine without changing the review model.&lt;/p&gt;

&lt;p&gt;If you want a ready-made starting point, look at the &lt;a href="https://github.com/schemacrawler/SchemaCrawler-Action-Usage-Example" rel="noopener noreferrer"&gt;SchemaCrawler Action usage example&lt;/a&gt; and adapt the workflow to your migration process.&lt;/p&gt;

</description>
      <category>database</category>
      <category>migration</category>
    </item>
    <item>
      <title>Reverse Engineer Any Database into dbdiagram.io, PlantUML, Mermaid, or QuickDBD - Then Keep Designing</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Sat, 30 May 2026 20:06:07 +0000</pubDate>
      <link>https://dev.to/sualeh/reverse-engineer-any-database-into-dbdiagramio-plantuml-mermaid-or-quickdbd-then-keep-25e7</link>
      <guid>https://dev.to/sualeh/reverse-engineer-any-database-into-dbdiagramio-plantuml-mermaid-or-quickdbd-then-keep-25e7</guid>
      <description>&lt;p&gt;Most database diagram tools stop at documentation. They connect to your database, inspect the schema, and generate a report or a picture. That is useful, but it does not help if your next step is design work.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What if you want to start from an existing database, open the result in a design tool, add a few new tables, adjust relationships, and then turn that updated design back into SQL?&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.schemacrawler.com/" rel="noopener noreferrer"&gt;SchemaCrawler&lt;/a&gt; supports that workflow.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SchemaCrawler can connect to any database with a JDBC driver and generate editable output in &lt;strong&gt;four&lt;/strong&gt; useful formats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DBML&lt;/strong&gt; for &lt;a href="https://dbdiagram.io/home" rel="noopener noreferrer"&gt;dbdiagram.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PlantUML&lt;/strong&gt; for &lt;a href="https://www.planttext.com/" rel="noopener noreferrer"&gt;PlantText&lt;/a&gt;, IntelliJ, and other PlantUML tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mermaid&lt;/strong&gt; for GitHub, GitLab, Notion, and the &lt;a href="https://mermaid.live/" rel="noopener noreferrer"&gt;Mermaid Live Editor&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QuickDBD&lt;/strong&gt; for &lt;a href="https://app.quickdatabasediagrams.com/#/" rel="noopener noreferrer"&gt;QuickDatabaseDiagrams.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives you a practical round-trip workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connect&lt;/strong&gt; to an existing database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export&lt;/strong&gt; the schema into DBML, PlantUML, Mermaid or QuickDBD&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit&lt;/strong&gt; the design in the tool you already use&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate DDL&lt;/strong&gt; from the updated design when you need SQL again&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SchemaSpy is strong when you want a browsable HTML report for stakeholders. But HTML is the end of the line. You can read it, click through it, and share it, but you cannot open it in a design tool and keep working. If you need reverse engineer -&amp;gt; edit design -&amp;gt; generate DDL, &lt;strong&gt;&lt;a href="https://www.schemacrawler.com/" rel="noopener noreferrer"&gt;SchemaCrawler&lt;/a&gt;&lt;/strong&gt; is the better fit.&lt;/p&gt;

&lt;p&gt;In this article, I will use the Northwind sample SQLite database for all examples.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Connect
&lt;/h2&gt;

&lt;p&gt;Make sure you have &lt;a href="https://docs.docker.com/desktop/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; installed. Then download the &lt;a href="https://github.com/jpwhite3/northwind-SQLite3/blob/4f56e7f5906dfd23b25244c5bfe8fb5da6402efd/dist/northwind.db?raw=true" rel="noopener noreferrer"&gt;Northwind sample SQLite database&lt;/a&gt; into your current directory.&lt;/p&gt;

&lt;p&gt;All commands below mount your current directory into the SchemaCrawler container, so the generated files are written back to your machine.&lt;/p&gt;

&lt;p&gt;If you are using PowerShell on Windows, replace the trailing backslash on each line with a back-tick "`".&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Export
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Export to DBML for dbdiagram.io
&lt;/h3&gt;

&lt;p&gt;DBML is the best choice if you want to keep designing and later generate SQL from the updated model.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;sh&lt;br&gt;
docker run \&lt;br&gt;
  --mount type=bind,source="$(pwd)",target=/home/schcrwlr/share \&lt;br&gt;
  --rm -it \&lt;br&gt;
  schemacrawler/schemacrawler \&lt;br&gt;
  /opt/schemacrawler/bin/schemacrawler.sh \&lt;br&gt;
  --server=sqlite \&lt;br&gt;
  --database=share/northwind.db \&lt;br&gt;
  --info-level=standard \&lt;br&gt;
  --command=script \&lt;br&gt;
  --script-language=python \&lt;br&gt;
  --script=dbml.py \&lt;br&gt;
  --output-file=share/northwind.dbml&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Open &lt;a href="https://dbdiagram.io/d" rel="noopener noreferrer"&gt;dbdiagram.io&lt;/a&gt;, paste in the contents of &lt;code&gt;northwind.dbml&lt;/code&gt;, and you immediately have an editable diagram based on the live database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Export to PlantUML
&lt;/h3&gt;

&lt;p&gt;PlantUML is a good choice if your team keeps diagrams in source control or already uses PlantUML in docs and architecture notes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;sh&lt;br&gt;
docker run \&lt;br&gt;
  --mount type=bind,source="$(pwd)",target=/home/schcrwlr/share \&lt;br&gt;
  --rm -it \&lt;br&gt;
  schemacrawler/schemacrawler \&lt;br&gt;
  /opt/schemacrawler/bin/schemacrawler.sh \&lt;br&gt;
  --server=sqlite \&lt;br&gt;
  --database=share/northwind.db \&lt;br&gt;
  --info-level=standard \&lt;br&gt;
  --command=script \&lt;br&gt;
  --script-language=python \&lt;br&gt;
  --script=plantuml.py \&lt;br&gt;
  --title="Northwind Database Schema" \&lt;br&gt;
  --output-file=share/northwind.puml&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;northwind.puml&lt;/code&gt; in &lt;a href="https://www.planttext.com/" rel="noopener noreferrer"&gt;PlantText&lt;/a&gt; or your IDE and keep editing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Export to Mermaid
&lt;/h3&gt;

&lt;p&gt;Mermaid is the best choice if you want diagrams that render directly in Markdown-based tools such as GitHub, GitLab, and Notion.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;sh&lt;br&gt;
docker run \&lt;br&gt;
  --mount type=bind,source="$(pwd)",target=/home/schcrwlr/share \&lt;br&gt;
  --rm -it \&lt;br&gt;
  schemacrawler/schemacrawler \&lt;br&gt;
  /opt/schemacrawler/bin/schemacrawler.sh \&lt;br&gt;
  --server=sqlite \&lt;br&gt;
  --database=share/northwind.db \&lt;br&gt;
  --info-level=standard \&lt;br&gt;
  --command=script \&lt;br&gt;
  --script-language=python \&lt;br&gt;
  --script=mermaid.py \&lt;br&gt;
  --title="Northwind Database Schema" \&lt;br&gt;
  --output-file=share/northwind.mmd&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Paste &lt;code&gt;northwind.mmd&lt;/code&gt; into the &lt;a href="https://mermaid.live/" rel="noopener noreferrer"&gt;Mermaid Live Editor&lt;/a&gt; or commit it straight into your documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Export to QuickDBD
&lt;/h3&gt;

&lt;p&gt;QuickDBD is a good choice when you want fast, text-first schema editing in a dedicated diagram editor.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;sh&lt;br&gt;
docker run \&lt;br&gt;
  --mount type=bind,source="$(pwd)",target=/home/schcrwlr/share \&lt;br&gt;
  --rm -it \&lt;br&gt;
  schemacrawler/schemacrawler \&lt;br&gt;
  /opt/schemacrawler/bin/schemacrawler.sh \&lt;br&gt;
  --server=sqlite \&lt;br&gt;
  --database=share/northwind.db \&lt;br&gt;
  --info-level=standard \&lt;br&gt;
  --command=script \&lt;br&gt;
  --script-language=python \&lt;br&gt;
  --script=quickdbd.py \&lt;br&gt;
  --title="Northwind Database Schema" \&lt;br&gt;
  --output-file=share/northwind.quickdbd&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Paste &lt;code&gt;northwind.quickdbd&lt;/code&gt; into &lt;a href="https://app.quickdatabasediagrams.com/#/" rel="noopener noreferrer"&gt;QuickDatabaseDiagrams.com&lt;/a&gt; to continue editing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Edit
&lt;/h2&gt;

&lt;p&gt;This is the part most reverse-engineering tools do not support well.&lt;/p&gt;

&lt;p&gt;Once you have exported the live schema into an editable design language, you are no longer stuck with a read-only report. You can continue designing.&lt;/p&gt;

&lt;p&gt;For example, imagine that after reverse-engineering northwind you want to add a table for storing playlist tags.&lt;/p&gt;

&lt;p&gt;In DBML, you could extend the exported design with something like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`dbml&lt;br&gt;
Table PlaylistTag {&lt;br&gt;
  PlaylistTagId integer [pk]&lt;br&gt;
  PlaylistId integer [not null]&lt;br&gt;
  TagName varchar [not null]&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Ref: PlaylistTag.PlaylistId &amp;gt; Playlist.PlaylistId&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That is the key distinction in this workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you start from what is actually in production&lt;/li&gt;
&lt;li&gt;you bring that schema into an editable format&lt;/li&gt;
&lt;li&gt;you extend the design instead of redrawing it from scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same idea works with PlantUML, Mermaid, and QuickDBD. Add entities, adjust relationships, rename columns, or reorganize sections of the model for clarity. SchemaCrawler gets you to a clean starting point from a live database instead of forcing you to recreate the schema by hand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Generate DDL
&lt;/h2&gt;

&lt;p&gt;DBML is especially useful because it can be turned back into SQL.&lt;/p&gt;

&lt;p&gt;Install the DBML CLI:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;sh&lt;br&gt;
npm install -g @dbml/cli&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then generate SQL from your updated design:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;sh&lt;br&gt;
dbml2sql northwind.dbml --postgres&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or for MySQL:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;sh&lt;br&gt;
dbml2sql northwind.dbml --mysql&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now you have a full round-trip flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reverse engineer from a live database&lt;/li&gt;
&lt;li&gt;edit the design in a modeling tool&lt;/li&gt;
&lt;li&gt;generate SQL from the updated design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a much more useful workflow than producing a static HTML report and stopping there.&lt;/p&gt;




&lt;h2&gt;
  
  
  Worked Example: northwind from Live Database to Editable Design
&lt;/h2&gt;

&lt;p&gt;Here is the full DBML flow in one sequence.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Export northwind to DBML
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;sh&lt;br&gt;
docker run \&lt;br&gt;
  --mount type=bind,source="$(pwd)",target=/home/schcrwlr/share \&lt;br&gt;
  --rm -it \&lt;br&gt;
  schemacrawler/schemacrawler \&lt;br&gt;
  /opt/schemacrawler/bin/schemacrawler.sh \&lt;br&gt;
  --server=sqlite \&lt;br&gt;
  --database=share/northwind.db \&lt;br&gt;
  --info-level=standard \&lt;br&gt;
  --command=script \&lt;br&gt;
  --script-language=python \&lt;br&gt;
  --script=dbml.py \&lt;br&gt;
  --output-file=share/northwind.dbml&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Open the result in dbdiagram.io
&lt;/h3&gt;

&lt;p&gt;Paste the contents of &lt;code&gt;northwind.dbml&lt;/code&gt; into &lt;a href="https://dbdiagram.io/d" rel="noopener noreferrer"&gt;dbdiagram.io&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Extend the model
&lt;/h3&gt;

&lt;p&gt;Add new tables, fields, and relationships directly in DBML.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Generate SQL
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;sh&lt;br&gt;
dbml2sql northwind.dbml --postgres&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;At that point you have gone from a real database to an editable design and back into SQL without manually redrawing anything.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Choose Each Output Format
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choose DBML&lt;/strong&gt; if you want the strongest design-tool workflow and the option to generate SQL later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose PlantUML&lt;/strong&gt; if your team prefers text-based diagrams in source control or already uses PlantUML in architecture docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose Mermaid&lt;/strong&gt; if you want diagrams that live directly inside Markdown, GitHub, GitLab, or internal docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose QuickDBD&lt;/strong&gt; if you want rapid text-based editing in the QuickDatabaseDiagrams editor and an easy way to iterate on schema shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not need to choose only one forever. The same database can be exported to all four, depending on what the next step in your workflow looks like.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Reverse engineering is only half the job.&lt;/p&gt;

&lt;p&gt;Developers often inherit an existing database and need to answer more than "what tables are there?" They need to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what should we add next?&lt;/li&gt;
&lt;li&gt;how do we model the next feature without breaking what exists?&lt;/li&gt;
&lt;li&gt;how do we propose changes in a format that is easy to review?&lt;/li&gt;
&lt;li&gt;how do we get from the current schema to future DDL with less manual work?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SchemaCrawler helps because it starts with the live database and produces output you can keep working with.&lt;/p&gt;

&lt;p&gt;That is the real value of DBML, PlantUML, Mermaid, and QuickDBD export. Not just nicer diagrams, but a better workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Start with the northwind sample database and generate one of the editable formats above. Once you have the workflow working locally, switch the connection to PostgreSQL, MySQL, SQL Server, Oracle, DB2, or any other JDBC database supported by SchemaCrawler.&lt;/p&gt;

&lt;p&gt;If you want to customize the generated output, you can find and edit the built-in scripts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/schemacrawler/SchemaCrawler/blob/main/schemacrawler-scripting/src/main/resources/scripts/dbml.py" rel="noopener noreferrer"&gt;dbml.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/schemacrawler/SchemaCrawler/blob/main/schemacrawler-scripting/src/main/resources/scripts/plantuml.py" rel="noopener noreferrer"&gt;plantuml.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/schemacrawler/SchemaCrawler/blob/main/schemacrawler-scripting/src/main/resources/scripts/mermaid.py" rel="noopener noreferrer"&gt;mermaid.py&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/schemacrawler/SchemaCrawler/blob/main/schemacrawler-scripting/src/main/resources/scripts/quickdbd.py" rel="noopener noreferrer"&gt;quickdbd.py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>jdbc</category>
      <category>documentation</category>
    </item>
    <item>
      <title>SchemaSpy vs SchemaCrawler - Which Database Documentation Tool is Right for You?</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Tue, 26 May 2026 23:47:18 +0000</pubDate>
      <link>https://dev.to/sualeh/schemaspy-vs-schemacrawler-which-database-documentation-tool-is-right-for-you-3do9</link>
      <guid>https://dev.to/sualeh/schemaspy-vs-schemacrawler-which-database-documentation-tool-is-right-for-you-3do9</guid>
      <description>&lt;p&gt;Both SchemaSpy and SchemaCrawler are free, open-source tools for documenting and analysing relational databases over JDBC. Both have been around for over 20 years. Both can generate entity-relationship diagrams. Yet the two tools are more different than they look.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I work on SchemaCrawler, so take this with appropriate scepticism. I have tried to represent SchemaSpy fairly.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What SchemaSpy Does Best
&lt;/h2&gt;

&lt;p&gt;SchemaSpy's primary strength is its &lt;strong&gt;interactive HTML report&lt;/strong&gt;. After a single run, you get a navigable website: clickable table pages, hyperlinked foreign keys, anomaly reports, and embedded ER diagrams for every table. It is exactly the kind of output you hand to a non-technical stakeholder, a consultant, or a new team member who needs to understand the data model quickly.&lt;/p&gt;

&lt;p&gt;SchemaSpy also detects &lt;strong&gt;implied relationships&lt;/strong&gt; - potential foreign keys that are not formally declared in the schema. It provides an orphan table page that surfaces tables with no relationships. These are genuinely useful for legacy databases.&lt;/p&gt;

&lt;p&gt;If your goal is a shareable, browsable report that looks great in a browser, SchemaSpy delivers.&lt;/p&gt;




&lt;h2&gt;
  
  
  What SchemaCrawler Does Best
&lt;/h2&gt;

&lt;p&gt;SchemaCrawler's strength is everything a developer needs before and after the report: &lt;strong&gt;searching, diffing, linting, scripting, and integration&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diff-able text output
&lt;/h3&gt;

&lt;p&gt;SchemaCrawler's "schema" command produces clean, structured text output - not HTML. Run it against production and staging, diff the outputs in git, and see exactly what changed. This is the foundation of schema change tracking in CI/CD.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema lint
&lt;/h3&gt;

&lt;p&gt;The "lint" command catches design problems automatically: missing primary keys, nullable columns in unique constraints, redundant indices, tables with no relationships, and more. The lints can be extended to enforce your organization's rules, such as specific naming conventions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grep - regex search across the entire schema
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;--grep-tables&lt;/code&gt; and &lt;code&gt;--grep-columns&lt;/code&gt; let you search all tables, columns, stored procedures, triggers, and foreign keys by regular expression. Find every column referencing a concept across a 500-table database in a single command. Combine it with &lt;code&gt;--parents&lt;/code&gt; and &lt;code&gt;--children&lt;/code&gt; to pull the related tables automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple output formats
&lt;/h3&gt;

&lt;p&gt;Text, HTML, JSON, CSV, Markdown, and ER diagrams (via Graphviz). The Markdown output is useful for documentation-as-code; the JSON output is useful for tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema extension with PlantUML and dbdiagram.io
&lt;/h3&gt;

&lt;p&gt;SchemaCrawler can generate output in &lt;a href="https://plantuml.com/" rel="noopener noreferrer"&gt;PlantUML&lt;/a&gt; and &lt;a href="https://dbdiagram.io/" rel="noopener noreferrer"&gt;dbdiagram.io&lt;/a&gt; formats directly from your live database. This means you can start from what is actually in the database and then edit the diagram to model proposed additions or changes - something neither SchemaSpy nor most ERD tools support directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scripting - Python, JavaScript
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;--command=script&lt;/code&gt; runs a script against live schema metadata. Generate custom reports, validate naming conventions, transform output - without writing a Java application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Full Java API
&lt;/h3&gt;

&lt;p&gt;SchemaCrawler is a JDBC metadata API. Embed it in a Java application and work with tables, columns, indexes, foreign keys, and routines as Java objects. SchemaSpy has no public API.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions integration
&lt;/h3&gt;

&lt;p&gt;There is an &lt;a href="https://github.com/marketplace/actions/schemacrawler-action" rel="noopener noreferrer"&gt;official SchemaCrawler GitHub Action&lt;/a&gt; in the marketplace. Run lint, diff, and schema documentation generation as part of any CI/CD workflow. SchemaSpy has no equivalent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Feature Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;SchemaCrawler&lt;/th&gt;
&lt;th&gt;SchemaSpy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interactive HTML report&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clickable navigation between tables&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ER diagrams&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diff-able text output&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extensible schema lint / design checks&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grep / regex search across schema&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Markdown, JSON, CSV output&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PlantUML and dbdiagram.io output&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scripting (Python, JS, Groovy)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java API&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions integration&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implied relationship detection&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orphan table detection&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Decision Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose SchemaSpy if…
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your primary output is a shareable, interactive HTML report for non-technical stakeholders&lt;/li&gt;
&lt;li&gt;You want clickable navigation between related tables out of the box&lt;/li&gt;
&lt;li&gt;You need implied/ virtual foreign key detection for a legacy schema with missing FK declarations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose SchemaCrawler if…
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need to &lt;strong&gt;track schema changes&lt;/strong&gt; in version control - diff text output between environments&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;catch design problems automatically&lt;/strong&gt; - schema lint in CI&lt;/li&gt;
&lt;li&gt;You need to &lt;strong&gt;search across a large schema&lt;/strong&gt; - find all tables or columns matching a pattern&lt;/li&gt;
&lt;li&gt;You are building schema checks into a &lt;strong&gt;CI/CD pipeline&lt;/strong&gt; - GitHub Actions integration&lt;/li&gt;
&lt;li&gt;You need output in &lt;strong&gt;Markdown, JSON, or CSV&lt;/strong&gt; as well as HTML&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;model future schema designs&lt;/strong&gt; in PlantUML or dbdiagram.io, starting from your live database&lt;/li&gt;
&lt;li&gt;You want to &lt;strong&gt;write scripts&lt;/strong&gt; that process schema metadata programmatically&lt;/li&gt;
&lt;li&gt;You are building a &lt;strong&gt;Java application&lt;/strong&gt; that needs database metadata as objects&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Can You Use Both?
&lt;/h2&gt;

&lt;p&gt;Yes. They serve genuinely different workflows.&lt;/p&gt;

&lt;p&gt;Use SchemaSpy to generate the stakeholder-facing HTML report. Use SchemaCrawler for diff, lint, and grep in your development and CI/CD workflow. The two tools are not competitors - they complement each other.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try SchemaCrawler
&lt;/h2&gt;

&lt;p&gt;The full documentation is at &lt;a href="https://www.schemacrawler.com" rel="noopener noreferrer"&gt;schemacrawler.com&lt;/a&gt;. The source is at &lt;a href="https://github.com/schemacrawler/SchemaCrawler" rel="noopener noreferrer"&gt;github.com/schemacrawler/SchemaCrawler&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>devops</category>
      <category>sql</category>
      <category>opensource</category>
    </item>
    <item>
      <title>3-way Boolean Anti-pattern</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Sat, 14 Feb 2026 18:22:36 +0000</pubDate>
      <link>https://dev.to/sualeh/3-way-boolean-anti-pattern-2fdf</link>
      <guid>https://dev.to/sualeh/3-way-boolean-anti-pattern-2fdf</guid>
      <description>&lt;p&gt;In Java, the "3-way Boolean" anti-pattern is what you get when you use the boxed type Boolean (instead of primitive boolean) and you implicitly allow it to represent three states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; true&lt;/li&gt;
&lt;li&gt; false&lt;/li&gt;
&lt;li&gt; null ← the third, often accidental, state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That "third state" becomes a trap because most code reads like it's dealing with a simple yes/ no flag, but at runtime it can behave differently (or crash) when the value is null.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it's an anti-pattern
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It creates implicit tri-state logic without making it explicit&lt;br&gt;
A variable named enabled, &lt;code&gt;isReady&lt;/code&gt;, &lt;code&gt;shouldRetry&lt;/code&gt;, etc. strongly implies binary logic, but &lt;code&gt;Boolean&lt;/code&gt; quietly allows null, which means unknown, not set, not loaded, not applicable, or sometimes just "bug".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It can throw &lt;code&gt;NullPointerException&lt;/code&gt; (NPE) during unboxing&lt;br&gt;
Common expressions like these will NPE if the Boolean is null:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;   &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getFlag&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="c1"&gt;// could be null&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt;&lt;span class="c1"&gt;// auto-unboxing -&amp;gt; NPE if null&lt;/span&gt;
   &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="err"&gt;&amp;nbsp;&lt;/span&gt; &lt;span class="c1"&gt;// ...&lt;/span&gt;
   &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This exact failure mode is commonly seen during upgrades or refactors because the code compiles fine but fails at runtime.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It leads to confusing "null means false... except when it doesn't" logic
People often "fix" the &lt;code&gt;NullPointerException&lt;/code&gt; by doing inconsistent checks:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
   &lt;span class="c1"&gt;// elsewhere&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;TRUE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
   &lt;span class="c1"&gt;// elsewhere&lt;/span&gt;
   &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FALSE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your codebase has multiple semantic interpretations of null.&lt;/p&gt;

</description>
      <category>codequality</category>
      <category>java</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Generate MCP Tool Schemas Directly From Java Code</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Thu, 20 Nov 2025 02:59:44 +0000</pubDate>
      <link>https://dev.to/sualeh/generate-mcp-tool-schemas-directly-from-java-code-3bif</link>
      <guid>https://dev.to/sualeh/generate-mcp-tool-schemas-directly-from-java-code-3bif</guid>
      <description>&lt;p&gt;If you are building an MCP server, every tool you expose needs an &lt;code&gt;inputSchema&lt;/code&gt;. MCP servers written with &lt;a href="https://spring.io/projects/spring-ai" rel="noopener noreferrer"&gt;Spring AI&lt;/a&gt; support often start with a simple data class for tool inputs. Then come changes: a new field, a renamed property, or updated constraints. The JSON schema in the tool registration rarely keeps up - that means clients may send invalid payloads. By generating the schema from the source of truth — the Java type — you remove that drift.&lt;/p&gt;

&lt;p&gt;Writing that JSON by hand is repetitive, easy to get wrong. &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/schema#primitiveschemadefinition" rel="noopener noreferrer"&gt;MCP supports only a specific sub-type&lt;/a&gt; of the &lt;a href="https://json-schema.org/specification" rel="noopener noreferrer"&gt;JSON Schema specification&lt;/a&gt;. The &lt;a href="https://github.com/sualeh/mcp-json-schema" rel="noopener noreferrer"&gt;MCP JSON Schema&lt;/a&gt; library keeps the parameter schema and the code in lockstep by generating the MCP-compatible JSON Schema from a Jackson 3 annotated Java class or record.&lt;/p&gt;

&lt;p&gt;What you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use of Jackson 3 annotations for naming, required fields, and descriptions that carry  over into the schema&lt;/li&gt;
&lt;li&gt;Use of Jakarta Bean Validation to adds meaningful constraints to the schema (for example, &lt;code&gt;@Max&lt;/code&gt;,&lt;code&gt;@Min&lt;/code&gt;, &lt;code&gt;@Positive&lt;/code&gt;, &lt;code&gt;@PositiveOrZero&lt;/code&gt;, &lt;code&gt;@Negative&lt;/code&gt;, &lt;code&gt;@NegativeOrZero&lt;/code&gt; on numbers, or &lt;code&gt;@Size&lt;/code&gt;, &lt;code&gt;@NotBlank&lt;/code&gt; on strings)&lt;/li&gt;
&lt;li&gt;Automatic handling of required fields, defaults, enums, and descriptions&lt;/li&gt;
&lt;li&gt;Output that targets the MCP JSON Schema subset, not the entire JSON Schema specification&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;Add a dependency to us.fatehi:mcp-json-schema in Maven or Gradle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;us.fatehi&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mcp-json-schema&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define a parameters type as a Jackson‑annotated record or class and let the library produce the &lt;code&gt;inputSchema&lt;/code&gt; JSON. Use annotations to describe intent, and let the library translate that into the MCP schema format.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.fasterxml.jackson.annotation.JsonProperty&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.fasterxml.jackson.annotation.JsonPropertyDescription&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tools.jackson.databind.PropertyNamingStrategies&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tools.jackson.databind.annotation.JsonNaming&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@JsonNaming&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PropertyNamingStrategies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;KebabCaseStrategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;SampleParameters&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@JsonPropertyDescription&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Type of database table dependant objects."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@JsonProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"NONE"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;DependantObjectType&lt;/span&gt; &lt;span class="n"&gt;dependantObjectType&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;

    &lt;span class="nd"&gt;@JsonPropertyDescription&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Table name."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tableName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;DependantObjectType&lt;/span&gt; 
    &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="no"&gt;NONE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;COLUMNS&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;INDEXES&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;FOREIGN_KEYS&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;TRIGGERS&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, generate the MCP &lt;code&gt;inputSchema&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;us.fatehi.mcp_json_schema.McpJsonSchemaUtility&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Provide this value as the tool's input_schema &lt;/span&gt;
&lt;span class="c1"&gt;// in your Spring AI MCP server implementation&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;inputSchemaJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="nc"&gt;McpJsonSchemaUtility&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SampleParameters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer a &lt;code&gt;JsonNode&lt;/code&gt; for programmatic changes? Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;schemaNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
  &lt;span class="nc"&gt;McpJsonSchemaUtility&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generateJsonSchema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SampleParameters&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;The source code is available at &lt;a href="https://github.com/sualeh/mcp-json-schema" rel="noopener noreferrer"&gt;sualeh/mcp-json-schema&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>mcp</category>
      <category>json</category>
      <category>jsonschema</category>
    </item>
    <item>
      <title>Why Your Claude Skills Deserve Better: Escape the Sandbox with MCP Skill Hub</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Mon, 27 Oct 2025 12:21:07 +0000</pubDate>
      <link>https://dev.to/sualeh/why-your-claude-skills-deserve-better-escape-the-sandbox-with-mcp-skill-hub-ffh</link>
      <guid>https://dev.to/sualeh/why-your-claude-skills-deserve-better-escape-the-sandbox-with-mcp-skill-hub-ffh</guid>
      <description>&lt;p&gt;If you've been working with Claude skills, you've probably felt the frustration of hitting sandbox limitations. Your Python code can't access files, make network requests, or interact with your local system. That's where the &lt;strong&gt;MCP Skill Hub&lt;/strong&gt; comes in – it takes your existing Claude skills and unleashes their full potential locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Claude Skills Sandbox Problem
&lt;/h2&gt;

&lt;p&gt;Claude skills are great for quick demonstrations, but they're severely limited:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ No file-system access&lt;/li&gt;
&lt;li&gt;❌ No network requests&lt;/li&gt;
&lt;li&gt;❌ No system commands&lt;/li&gt;
&lt;li&gt;❌ No persistent storage&lt;/li&gt;
&lt;li&gt;❌ No real-world integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your skills can show examples and explain concepts, but they can't actually &lt;em&gt;do&lt;/em&gt; the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills That Actually Work
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/srprasanna/mcp-skill-hub" rel="noopener noreferrer"&gt;MCP Skill Hub&lt;/a&gt; changes everything. It takes your existing Claude Skills (same YAML frontmatter format, same Markdown content) and runs them locally through the &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check out the examples in &lt;a href="https://github.com/srprasanna/mcp-skill-hub" rel="noopener noreferrer"&gt;srprasanna/mcp-skill-hub&lt;/a&gt; for working code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changes When You Go Local
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before (Claude Sandbox):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Here's how you &lt;em&gt;would&lt;/em&gt; read an Excel file..."&lt;/li&gt;
&lt;li&gt;"This code &lt;em&gt;demonstrates&lt;/em&gt; the concept..."&lt;/li&gt;
&lt;li&gt;"In a real environment, you &lt;em&gt;could&lt;/em&gt; do this..."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After (MCP Local):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your skill actually reads files from your computer&lt;/li&gt;
&lt;li&gt;Real database connections, API calls, file operations&lt;/li&gt;
&lt;li&gt;Integration with your actual development workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Beyond Claude: Any Agent, Any Model
&lt;/h2&gt;

&lt;p&gt;Here's the kicker – you're not locked into Claude anymore. The MCP Skill Hub works with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Desktop&lt;/strong&gt; (obvious choice)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cline/Cursor&lt;/strong&gt; (VS Code integration)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open WebUI&lt;/strong&gt; (local models)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Any MCP-compatible agent&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your skills become portable across the entire AI ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started is Dead Simple
&lt;/h2&gt;

&lt;p&gt;The server enforces a clean folder structure that makes sense:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/my-skills/
├── excel-automation/
│   ├── SKILL.md          ← Your existing skill content
│   └── examples/         ← Working Python scripts
├── database-queries/
│   ├── SKILL.md
│   └── examples/
└── file-processing/
    ├── SKILL.md
    └── templates/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; ~/my-skills:/skills:ro &lt;span class="se"&gt;\&lt;/span&gt;
  srprasanna/mcp-skill-hub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hot-reload is built-in – edit your skills and see changes instantly without restarting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Ready Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔄 &lt;strong&gt;Hot-reload&lt;/strong&gt; – edit skills without restarting&lt;/li&gt;
&lt;li&gt;🐳 &lt;strong&gt;Docker support&lt;/strong&gt; – run anywhere containers run&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Rich metadata&lt;/strong&gt; – categories, tags, complexity levels&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Search tools&lt;/strong&gt; – find skills by query or category&lt;/li&gt;
&lt;li&gt;📝 &lt;strong&gt;Full documentation&lt;/strong&gt; – comprehensive guides and examples&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Type-safe&lt;/strong&gt; – modern Python 3.13+ with full type hints&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Your Claude skills are just the beginning. The MCP Skill Hub takes that same familiar format and removes all the limitations. Your skills can finally do real work – read files, make API calls, automate your actual workflow.&lt;/p&gt;

&lt;p&gt;And since it's MCP-compliant, you can use those skills with any compatible AI agent, not just Claude.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to escape the sandbox?&lt;/strong&gt; Check out the &lt;a href="https://github.com/srprasanna/mcp-skill-hub" rel="noopener noreferrer"&gt;MCP Skill Hub on GitHub&lt;/a&gt; or install directly from the &lt;a href="https://registry.modelcontextprotocol.io/v0.1/servers/io.github.srprasanna%2Fmcp-skill-hub/versions" rel="noopener noreferrer"&gt;MCP Registry&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Your skills deserve to run free. 🚀&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The MCP Skill Hub is open source (MIT license) and available on Docker Hub. It's production-ready with comprehensive testing and documentation.&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Revolutionize Your Database Development with SchemaCrawler MCP Server</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Sat, 24 May 2025 22:21:28 +0000</pubDate>
      <link>https://dev.to/sualeh/revolutionize-your-database-development-with-schemacrawler-mcp-server-310i</link>
      <guid>https://dev.to/sualeh/revolutionize-your-database-development-with-schemacrawler-mcp-server-310i</guid>
      <description>&lt;p&gt;Imagine having an AI assistant that &lt;strong&gt;actually understands&lt;/strong&gt; your database schema and helps you make sense of your tables and columns, helps you craft perfect SQL queries that actually work, and saves you from hours of documentation diving. The &lt;strong&gt;SchemaCrawler MCP Server&lt;/strong&gt; is here, and it it free and open source.&lt;/p&gt;

&lt;p&gt;Forget complex installations and configuration headaches. The SchemaCrawler MCP Server runs in a Docker container, meaning you can get up and running with just a few commands, using your favorite MCP Client in "Agent" mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Can It Do For You?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔍 Explore Your Database Structure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;View all tables and views&lt;/strong&gt; at a glance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Examine column details&lt;/strong&gt; including data types and constraints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand relationships&lt;/strong&gt; between tables with foreign key information&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🛠️ Improve Your Database Design
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Find design issues&lt;/strong&gt; with the built-in schema linting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discover missing indexes&lt;/strong&gt; that could improve performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify nullable columns&lt;/strong&gt; in unique constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📝 Simplify SQL Development
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Understand table schemas&lt;/strong&gt; before writing queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;See sample data&lt;/strong&gt; to better understand the information you're working with&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate proper SQL&lt;/strong&gt; based on your database structure&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started in 4 Easy Steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Clone &lt;a href="https://github.com/schemacrawler/SchemaCrawler-MCP-Client-Usage" rel="noopener noreferrer"&gt;https://github.com/schemacrawler/SchemaCrawler-MCP-Client-Usage&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start the Server&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; schemacrawler-mcpserver.yaml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Verify It's Running&lt;/strong&gt;&lt;br&gt;
Check server health at &lt;a href="http://localhost:8080/health" rel="noopener noreferrer"&gt;http://localhost:8080/health&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Connect in VS Code&lt;/strong&gt;&lt;br&gt;
The server is already configured in &lt;code&gt;.vscode/mcp.json&lt;/code&gt; - just open VS Code and start asking questions!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Connect to Your Own Database
&lt;/h2&gt;

&lt;p&gt;Want to use this with your own database? No problem!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stop the current server&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; schemacrawler-mcpserver.yaml down &lt;span class="nt"&gt;-t0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edit the connection details&lt;/strong&gt;&lt;br&gt;
Update &lt;code&gt;schemacrawler-mcpserver.yaml&lt;/code&gt; with your database connection information&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Restart the server&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; schemacrawler-mcpserver.yaml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start Exploring Today!
&lt;/h2&gt;

&lt;p&gt;Simply ask questions about your database in VS Code's chat panel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What tables are available in my database?"&lt;/li&gt;
&lt;li&gt;"Show me the columns in the Books table"&lt;/li&gt;
&lt;li&gt;"What foreign keys reference the Authors table?"&lt;/li&gt;
&lt;li&gt;"Are there any design issues with my database schema?"&lt;/li&gt;
&lt;li&gt;"Write SQL to find books and their authors"&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Powered by &lt;a href="https://www.schemacrawler.com/" rel="noopener noreferrer"&gt;SchemaCrawler&lt;/a&gt; - Free database schema discovery and comprehension tool&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>database</category>
    </item>
    <item>
      <title>Calculate the DORA Lead Time Metric in Python</title>
      <dc:creator>Sualeh Fatehi</dc:creator>
      <pubDate>Thu, 10 Apr 2025 23:03:47 +0000</pubDate>
      <link>https://dev.to/sualeh/calculate-the-dora-lead-time-metric-in-python-2bhn</link>
      <guid>https://dev.to/sualeh/calculate-the-dora-lead-time-metric-in-python-2bhn</guid>
      <description>&lt;p&gt;DORA (DevOps Research and Assessment) metrics have become the gold standard for measuring software delivery performance. Among these metrics, Lead Time for Changes is a good indicator of your team's efficiency to deliver changes in production. Let us understand what this metric is, why it matters, and how you can calculate it using Jira and GitHub data with Python code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the DORA Lead Time Metric?
&lt;/h2&gt;

&lt;p&gt;Lead Time for Changes measures the duration from when code is first committed until it is successfully deployed to production. In simpler terms, it answers the question: "How long does it take for a code change to go from a developer's machine to serving users in production?"&lt;/p&gt;

&lt;p&gt;According to DORA research, organizations typically fall into these performance categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Elite performers&lt;/strong&gt;: Less than one hour&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High performers&lt;/strong&gt;: Between one day and one week&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medium performers&lt;/strong&gt;: Between one week and one month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low performers&lt;/strong&gt;: Between one month and six months&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A shorter lead time indicates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster delivery of features to users&lt;/li&gt;
&lt;li&gt;Quicker bug fixes and security patches&lt;/li&gt;
&lt;li&gt;More agile response to changing requirements&lt;/li&gt;
&lt;li&gt;Less work-in-progress building up&lt;/li&gt;
&lt;li&gt;Reduced context switching for developers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, lead time is a powerful indicator of your development process efficiency. Long lead times often signal bottlenecks in your development pipeline that need addressing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calculating Lead Time
&lt;/h2&gt;

&lt;p&gt;If you use Jira and GitHub, you can calculate lead time by connecting data from both platforms. The calculation involves several steps:&lt;/p&gt;

&lt;p&gt;Projects → Releases → Stories → Pull Requests → Commits&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Projects&lt;/strong&gt;: First, gather all software projects from Jira&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Releases&lt;/strong&gt;: For each project, collect released versions within a specified date range&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stories&lt;/strong&gt;: Identify all Jira stories associated with each release&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull Requests&lt;/strong&gt;: For each story, find the linked GitHub pull requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commits&lt;/strong&gt;: Within each pull request, analyze all commits&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For each pull request, lead time is calculated as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;lead_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;release_date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;earliest_commit_date&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key here is using the &lt;strong&gt;earliest commit date&lt;/strong&gt; rather than the pull request creation date. This captures the true beginning of work, even if the pull request was created later. The final DORA lead time metric is calculated by averaging all individual lead times over a specified time period, for a given set of projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manage This in Jira and GitHub
&lt;/h2&gt;

&lt;p&gt;To effectively use this approach, you need to understand how to manage the key components in Jira. Create Jira releases (or "versions") for each planned release, and set release dates when versions are published. Mark versions as "Released" once deployed. Assign the release to a project. In this context projects typically represent teams, products, or components. Stories are work items (features, bugs, etc.) included in releases. Use the Jira GitHub integration to connect your repositories. Reference Jira issues in pull request titles or descriptions** (e.g., "PROJ-123: Add new feature"). Use smart commits in your commit messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Python to Generate lead Time Reports
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://pypi.org/project/dora-lead-time-metric/" rel="noopener noreferrer"&gt;&lt;code&gt;dora-lead-time&lt;/code&gt;&lt;/a&gt; package provides a simple way to calculate and visualize lead time metrics. It connects to your Jira and GitHub data, calculates lead times, and generates reports. Here is how you might use the package to generate a monthly lead time report:&lt;/p&gt;

&lt;p&gt;First set up your tokens to access Jira and GitHub. Set the following environmental variables.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;ATLASSIAN_TOKEN&lt;/code&gt; containing the API token for Atlassian Jira access.&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;JIRA_INSTANCE&lt;/code&gt; for your Jira instance URL (e.g., &lt;code&gt;company.atlassian.net&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;EMAIL&lt;/code&gt; for your Atlassian account email address.&lt;/li&gt;
&lt;li&gt;Create personal access tokens for each GitHub organization, for example, &lt;code&gt;GITHUB_TOKEN_ORG1&lt;/code&gt;, &lt;code&gt;GITHUB_TOKEN_ORG2&lt;/code&gt;, etc. to authenticate API requests to specific GitHub organizations. Each organization you need to access requires its own token.&lt;/li&gt;
&lt;li&gt;Create an environmental variable &lt;code&gt;GITHUB_ORG_TOKENS_MAP&lt;/code&gt; which is a JSON string mapping organization names to environment variable names. For example:
&lt;code&gt;GITHUB_ORG_TOKENS_MAP={"Org1": "GITHUB_TOKEN_ORG1", "Org2": "GITHUB_TOKEN_ORG2"}&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can optionally set&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;SQLITE_PATH&lt;/code&gt; which is the path where the SQLite database will be created.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;START_DATE&lt;/code&gt; and &lt;code&gt;END_DATE&lt;/code&gt; to define the date range for which to calculate lead time metrics, using ISO date strings (YYYY-MM-DD).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a complete examples which you can put in a ".env" file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GITHUB_TOKEN_ORG1=your_personal_access_token_for_org1
GITHUB_TOKEN_ORG2=your_personal_access_token_for_org2
GITHUB_ORG_TOKENS_MAP={"Org1": "GITHUB_TOKEN_ORG1", "Org2": "GITHUB_TOKEN_ORG2"}
ATLASSIAN_TOKEN=your_atlassian_api_token
JIRA_INSTANCE=your_company.atlassian.net
EMAIL=your_email@your_company.com
SQLITE_PATH=./releases.db
START_DATE=2023-01-01
END_DATE=2023-12-31
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run code similar to the following to generate a lead time report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dora_lead_time.lead_time_report&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LeadTimeReport&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize the report generator
&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LeadTimeReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;releases.db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Define the scope
&lt;/span&gt;&lt;span class="n"&gt;project_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FRONTEND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BACKEND&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MOBILE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;start_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;end_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Generate a monthly report
&lt;/span&gt;&lt;span class="n"&gt;monthly_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;monthly_lead_time_report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_keys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Display and visualize the report
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monthly_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create a visualization
&lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show_plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monthly_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025 Monthly Lead Time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;show_trend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;savefig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lead_time_trend.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The full code is available on &lt;a href="https://github.com/username/dora-lead-time-metric" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The report allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track lead time trends over months&lt;/li&gt;
&lt;li&gt;Compare performance across different projects&lt;/li&gt;
&lt;li&gt;Identify when process changes impact lead time&lt;/li&gt;
&lt;li&gt;Set targets based on DORA performance levels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additionally, the project includes SQL-based outlier reports to identify issues like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Projects without releases&lt;/li&gt;
&lt;li&gt;Releases with open stories&lt;/li&gt;
&lt;li&gt;Stories in multiple releases&lt;/li&gt;
&lt;li&gt;Stories without pull requests&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Calculating the DORA Lead Time metric provides valuable insights into your software delivery performance. By connecting data from Jira and GitHub, this approach gives you an accurate measurement that truly reflects your development process. The real power comes from using this data to identify bottlenecks and continuously improve your delivery pipeline. Whether you're aiming to move from "medium" to "high" performer status or already pursuing "elite" performance, measuring lead time is an essential step in the journey.&lt;/p&gt;

</description>
      <category>dora</category>
      <category>metrics</category>
    </item>
  </channel>
</rss>
