MP-301d · Module 2

Relationship Mapping

3 min read

Tables do not exist in isolation — they are connected through foreign keys, join tables, and implicit relationships. An AI model that knows customers has a one-to-many relationship with orders can construct meaningful queries: "show me the top 5 customers by order volume." Without relationship metadata, the model sees disconnected tables and cannot reason about cross-table queries. Exposing foreign key relationships as a dedicated resource — db://schema/relationships — gives the model a graph view of your data model.

Foreign key extraction from information_schema gives you the explicit relationships — customer_id in orders references id in customers. But many real-world schemas have implicit relationships that are not enforced by foreign keys: a user_email column in logs that corresponds to email in users, or a product_sku in warehouse_inventory that matches sku in products. These should be documented in your relationship resource as "logical" relationships alongside the "physical" foreign key relationships.

interface Relationship {
  type: "foreign_key" | "logical";
  from: { table: string; column: string };
  to: { table: string; column: string };
  cardinality: "one-to-one" | "one-to-many" | "many-to-many";
  description?: string;
}

// Extract foreign key relationships from the database
async function getForeignKeys(): Promise<Relationship[]> {
  const fks = await pool.query(`
    SELECT
      tc.table_name AS from_table,
      kcu.column_name AS from_column,
      ccu.table_name AS to_table,
      ccu.column_name AS to_column
    FROM information_schema.table_constraints tc
    JOIN information_schema.key_column_usage kcu
      ON tc.constraint_name = kcu.constraint_name
    JOIN information_schema.constraint_column_usage ccu
      ON tc.constraint_name = ccu.constraint_name
    WHERE tc.constraint_type = 'FOREIGN KEY'
      AND tc.table_name = ANY($1)
  `, [ALLOWED_TABLE_NAMES]);

  return fks.rows.map((fk) => ({
    type: "foreign_key" as const,
    from: { table: fk.from_table, column: fk.from_column },
    to: { table: fk.to_table, column: fk.to_column },
    cardinality: "one-to-many",
  }));
}

// Add logical relationships not enforced by FK constraints
const LOGICAL_RELATIONSHIPS: Relationship[] = [
  {
    type: "logical",
    from: { table: "audit_log", column: "actor_email" },
    to: { table: "users", column: "email" },
    cardinality: "one-to-many",
    description: "Audit entries linked to users by email address",
  },
];

Do This

  • Expose both foreign key and logical relationships as a dedicated resource
  • Include cardinality (one-to-one, one-to-many) so the model understands join semantics
  • Filter relationships to only include allowed tables — do not leak schema details
  • Document implicit relationships that foreign keys do not capture

Avoid This

  • Assume the model can infer relationships from column names — customer_id is ambiguous without context
  • Expose all foreign keys including internal system tables
  • Omit cardinality — the model needs to know if it should expect one row or many
  • Rely solely on foreign keys — many production schemas use implicit relationships