Savvi Studio

Introspection Examples

Purpose: Code examples for using introspection classes
Last Updated: 2024-11-28

Basic Usage

Bootstrap Introspection

import { IntrospectionBootstrap } from '@/lib/codegen/introspection';

const bootstrap = new IntrospectionBootstrap();
await bootstrap.connect();
await bootstrap.loadSchema('graph');

const typeCatalog = bootstrap.getTypeCatalog();
const functionCatalog = bootstrap.getFunctionCatalog();

Using Type Catalog

const catalog = new TypeCatalog(client);
await catalog.loadSchema('graph');

// Get specific type
const nodeType = catalog.getType(oid);

// Get all types in dependency order
const sorted = catalog.topologicalSort();

// Get statistics
const stats = catalog.getStats();

Using Function Catalog

const catalog = new FunctionCatalog(client, typeCatalog);
await catalog.loadSchema('graph');

// Get all functions
const functions = catalog.getFunctions();

// Get specific function
const func = catalog.getFunction('create_resource');

SQL Queries

Base Types Query

SELECT 
  t.oid,
  t.typname,
  t.typnamespace::regnamespace::text as schema_name,
  t.typtype,
  t.typlen,
  t.typbyval
FROM pg_type t
WHERE t.typnamespace = 'pg_catalog'::regnamespace
  AND t.typname IN ('int4', 'int8', 'text', 'bool', ...);

Enum Types Query

SELECT 
  t.oid,
  t.typname,
  array_agg(e.enumlabel ORDER BY e.enumsortorder) as values
FROM pg_type t
JOIN pg_enum e ON t.oid = e.enumtypid
WHERE t.typnamespace = $1::regnamespace
GROUP BY t.oid, t.typname;

Composite Types Query

SELECT 
  t.oid,
  t.typname,
  a.attname,
  a.atttypid,
  a.attnum
FROM pg_type t
JOIN pg_attribute a ON t.typrelid = a.attrelid
WHERE t.typnamespace = $1::regnamespace
  AND t.typtype = 'c'
  AND a.attnum > 0
  AND NOT a.attisdropped
ORDER BY t.oid, a.attnum;

Domain Types Query

SELECT 
  t.oid,
  t.typname,
  t.typbasetype,
  pg_get_constraintdef(c.oid) as constraint_def
FROM pg_type t
LEFT JOIN pg_constraint c ON t.oid = c.contypid
WHERE t.typnamespace = $1::regnamespace
  AND t.typtype = 'd';

Array Types Query

SELECT 
  t.oid,
  t.typname,
  t.typelem as element_oid
FROM pg_type t
WHERE t.typnamespace = $1::regnamespace
  AND t.typelem != 0;

Function Metadata Query

SELECT 
  p.oid,
  p.proname,
  p.pronamespace::regnamespace::text as schema_name,
  p.pronargs,
  p.proargtypes,
  p.proargnames,
  p.proargmodes,
  p.prorettype,
  p.proretset,
  obj_description(p.oid, 'pg_proc') as comment
FROM pg_proc p
WHERE p.pronamespace = $1::regnamespace
  AND p.prokind = 'f'  -- functions only, not procedures
ORDER BY p.proname;

Parameter Information Query

SELECT 
  unnest(p.proargnames) as name,
  unnest(p.proargtypes::oid[]) as type_oid,
  unnest(p.proargmodes) as mode
FROM pg_proc p
WHERE p.oid = $1;

Return Type Information Query

SELECT 
  p.prorettype,
  p.proretset,
  p.prorows,
  t.typtype
FROM pg_proc p
JOIN pg_type t ON p.prorettype = t.oid
WHERE p.oid = $1;

Dependency Resolution

Type Dependencies

class TypeCatalog {
  getDependencies(type: Type): Type[] {
    switch (type.category) {
      case 'composite':
        // Composite depends on all field types
        return type.fields.map(f => this.getType(f.typeOid));
        
      case 'array':
        // Array depends on element type
        return [this.getType(type.elementOid)];
        
      case 'domain':
        // Domain depends on base type
        return [this.getType(type.baseTypeOid)];
        
      case 'range':
        // Range depends on subtype
        return [this.getType(type.subtypeOid)];
        
      default:
        return [];
    }
  }
}

Topological Sort

class TypeCatalog {
  topologicalSort(): Type[] {
    const visited = new Set<number>();
    const result: Type[] = [];
    
    const visit = (type: Type) => {
      if (visited.has(type.oid)) return;
      visited.add(type.oid);
      
      // Visit dependencies first
      for (const dep of this.getDependencies(type)) {
        visit(dep);
      }
      
      result.push(type);
    };
    
    for (const type of this.types.values()) {
      visit(type);
    }
    
    return result;
  }
}

Circular Dependency Detection

class TypeCatalog {
  hasCircularDependency(type: Type): boolean {
    const visited = new Set<number>();
    
    const check = (t: Type, path: Set<number>): boolean => {
      if (path.has(t.oid)) return true; // Circular!
      if (visited.has(t.oid)) return false;
      
      visited.add(t.oid);
      path.add(t.oid);
      
      for (const dep of this.getDependencies(t)) {
        if (check(dep, new Set(path))) return true;
      }
      
      path.delete(t.oid);
      return false;
    };
    
    return check(type, new Set());
  }
}

Performance Optimization

Batch Queries

async loadTypes(oids: number[]): Promise<Type[]> {
  const query = `
    SELECT * FROM pg_type
    WHERE oid = ANY($1::oid[])
  `;
  const result = await client.query(query, [oids]);
  return result.rows.map(row => this.parseType(row));
}

Caching

class TypeCatalog {
  private cache = new Map<number, Type>();
  
  getType(oid: number): Type | undefined {
    if (this.cache.has(oid)) {
      return this.cache.get(oid);
    }
    
    const type = this.loadType(oid);
    if (type) {
      this.cache.set(oid, type);
    }
    return type;
  }
}

Schema Filtering

async loadSchema(schemaName: string) {
  // Only load types from specified schema
  const query = `
    SELECT * FROM pg_type
    WHERE typnamespace = $1::regnamespace
  `;
  await this.executeQuery(query, [schemaName]);
}

Type Metadata

Type Category Enumeration

enum TypeCategory {
  BASE = 'b',        // Base type
  COMPOSITE = 'c',   // Composite (record)
  DOMAIN = 'd',      // Domain (constrained type)
  ENUM = 'e',        // Enum
  PSEUDO = 'p',      // Pseudo-type
  RANGE = 'r'        // Range type
}

Type Interface

interface Type {
  oid: number;
  name: string;
  schema: string;
  category: TypeCategory;
  length: number;
  byValue: boolean;
  // Category-specific fields
  fields?: CompositeField[];      // for composite
  values?: string[];              // for enum
  baseTypeOid?: number;          // for domain
  elementOid?: number;           // for array
  constraint?: string;           // for domain
}

Bootstrap Process

Full Bootstrap Example

class IntrospectionBootstrap {
  async bootstrap(schemaName: string) {
    // 1. Connect to database
    await this.connect();
    
    // 2. Load utility functions
    await this.loadUtilities();
    
    // 3. Load type catalog
    await this.typeCatalog.loadSchema(schemaName);
    
    // 4. Load function catalog
    await this.functionCatalog.loadSchema(schemaName);
    
    // 5. Validate catalogs
    await this.validate();
  }
}

Error Handling

Missing Types

getType(oid: number): Type {
  const type = this.cache.get(oid);
  if (!type) {
    throw new Error(`Type with OID ${oid} not found`);
  }
  return type;
}

Invalid Dependencies

validateDependencies(type: Type): void {
  for (const dep of this.getDependencies(type)) {
    if (!this.hasType(dep.oid)) {
      throw new Error(
        `Type ${type.name} depends on missing type ${dep.oid}`
      );
    }
  }
}

These examples demonstrate how to use the introspection system to discover database schema information.