Class: RailsAiBridge::Introspectors::Schema::StaticStructureSqlParser

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_ai_bridge/introspectors/schema/static_structure_sql_parser.rb

Overview

Parses a db/structure.sql file as plain text, without a live database connection. This is the schema_format = :sql counterpart to StaticSchemaParser: apps that keep their schema as SQL (common on Postgres, where schema.rb cannot represent partitions, views, extensions, or custom SQL) have no db/schema.rb to fall back to in offline environments (CI, Claude Code, agent contexts).

Each instance is single-use: construct it with the file content and a configuration object, call #call, and discard. No mutable state escapes the instance.

Supported DDL (pg_dump / structure.sql form)

  • CREATE TABLE [IF NOT EXISTS] [schema.]name ( — opens a table context
  • <name> <type> ... — a column line inside the table body; the leading identifier is the column and the remainder (minus +NOT NULL+/+DEFAULT+) is the SQL type. Table-level constraint lines (+CONSTRAINT+, PRIMARY KEY, FOREIGN KEY, …) are skipped.
  • ); — closes the current table context
  • CREATE [UNIQUE] INDEX name ON [schema.]table USING method (cols) — adds an index entry (first simple column) to the named table. Functional/expression indexes (e.g. lower(email)) are skipped.
  • +ALTER TABLE [ONLY] table ADD CONSTRAINT ... FOREIGN KEY (col) REFERENCES ref_table (pk)+ — adds a foreign-key entry to table (pg_dump emits these in a separate constraints section).

Unlike StaticSchemaParser (whose schema.rb static form leaves foreign keys empty), structure.sql spells foreign keys out as parseable DDL, so this parser populates them offline — matching what the live RailsAiBridge::Introspectors::SchemaIntrospector path reports.

Internal Rails tables (+ar_internal_metadata+, schema_migrations) and any table matching Config::Introspection#excluded_tables are silently skipped.

Examples:

content = File.read("db/structure.sql")
result  = StaticStructureSqlParser.new(content: content, config: RailsAiBridge.configuration).call
# => { adapter: "static_parse", tables: { ... }, total_tables: N, note: "..." }

See Also:

Constant Summary collapse

TABLE_LINE =

Regex matching a CREATE TABLE declaration, tolerating IF NOT EXISTS, a schema qualifier (+public.+), and optional quoting of either part.

/\ACREATE TABLE (?:IF NOT EXISTS\s+)?(?:[\w"]+\.)?"?([A-Za-z_]\w*)"?\s*\(/
TABLE_END_LINE =

Regex matching the end of a table body (+);+ at column zero).

/\A\)/
COLUMN_LINE =

Regex matching a column definition inside a table body: leading whitespace, an identifier (optionally quoted), then the type/modifiers.

/\A\s+"?([A-Za-z_]\w*)"?\s+(.+)/
INDEX_LINE =

Regex matching a CREATE INDEX statement. Captures the target table and the raw parenthesised column list; only the first column is kept (parity with RailsAiBridge::Introspectors::Schema::StaticSchemaParser).

/\ACREATE\s+(?:UNIQUE\s+)?INDEX\s+.+?\s+ON\s+(?:[\w"]+\.)?"?([A-Za-z_]\w*)"?\s+(?:USING\s+\w+\s+)?\(([^)]+)\)/
ALTER_TABLE_LINE =

Regex matching an ALTER TABLE [ONLY] [schema.]table statement, which in pg_dump precedes an ADD CONSTRAINT line. Captures the target table.

/\AALTER TABLE (?:ONLY\s+)?(?:[\w"]+\.)?"?([A-Za-z_]\w*)"?/
FOREIGN_KEY_LINE =

Regex matching an +ADD CONSTRAINT ... FOREIGN KEY (cols) REFERENCES [schema.]ref_table (pk)+ clause. Captures local columns, referenced table, and referenced columns.

/FOREIGN KEY\s*\(([^)]+)\)\s*REFERENCES\s+(?:[\w"]+\.)?"?([A-Za-z_]\w*)"?\s*\(([^)]+)\)/
ON_DELETE =

Regex matching an ON DELETE <action> clause on a foreign-key line.

/ON DELETE ([A-Z ]+?)(?=\s+ON UPDATE|\s+(?:NOT\s+)?(?:DEFERRABLE|VALID)|[,;)]|\z)/i
ON_UPDATE =

Regex matching an ON UPDATE <action> clause on a foreign-key line.

/ON UPDATE ([A-Z ]+?)(?=\s+(?:NOT\s+)?(?:DEFERRABLE|VALID)|[,;)]|\z)/i
INTERNAL_TABLES =

Rails-managed tables that must never appear in introspection output.

%w[ar_internal_metadata schema_migrations].freeze
CONSTRAINT_KEYWORDS =

Table-level constraint keywords that share a column line's shape but are not columns.

%w[CONSTRAINT PRIMARY FOREIGN UNIQUE CHECK EXCLUDE LIKE DEFERRABLE].freeze

Instance Method Summary collapse

Constructor Details

#initialize(content:, config:) ⇒ StaticStructureSqlParser

Returns a new instance of StaticStructureSqlParser.

Parameters:



90
91
92
93
94
95
96
97
# File 'lib/rails_ai_bridge/introspectors/schema/static_structure_sql_parser.rb', line 90

def initialize(content:, config:)
  @content       = content
  @config        = config
  @tables        = {}
  @current_table = nil
  @in_table      = false
  @alter_target  = nil
end

Instance Method Details

#callHash{Symbol => Object}

Parse the structure.sql content and return the tables hash. Never raises — malformed or non-UTF-8 input is caught and reported as an error hash, per the introspector contract.

Returns:

  • (Hash{Symbol => Object})

    with keys :adapter, :tables, :total_tables, and :note; or { error: } on failure



105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/rails_ai_bridge/introspectors/schema/static_structure_sql_parser.rb', line 105

def call
  @content.each_line { |line| parse_line(line) }

  {
    adapter: 'static_parse',
    tables: @tables,
    total_tables: @tables.size,
    note: 'Parsed from db/structure.sql (no DB connection)'
  }
rescue StandardError => error
  { error: "Failed to parse db/structure.sql: #{error.message}" }
end