Docs›Banco de dados›Introspecção de schema
Banco de dados

Introspecção de schema

SchemaIntrospector: catálogo multi-engine (tabelas, colunas, FKs, índices), rowCount por estatística e rowCountApprox — sem COUNT(*) por tabela.

Mad\Database\SchemaIntrospector le o catalogo do banco (tabelas, views, colunas, chaves, indices, contagem de linhas) sobre um PDO ja conectado, com o mesmo formato de saida em SQLite, MySQL/MariaDB e PostgreSQL. E o que alimenta o Database Manager do MadBuilder (via driver REST) e qualquer tela que precise mostrar o schema real — nao e um substituto do Query Builder para ler dados de negocio.

Arquivo: packages/mad-framework/src/mad/database/SchemaIntrospector.php.

API

Metodo Retorno Descricao
introspect(PDO $pdo, bool $withCounts = true) array Schema inteiro: ['engine' => ..., 'tables' => [...]]. Com $withCounts = false pula a contagem de linhas.
tableStructure(PDO $pdo, string $table) array Estrutura de UMA tabela: ['name', 'columns', 'indexes'] (sem rowCount).
driver(PDO $pdo) string sqlite | mysql | pgsql — lido de PDO::ATTR_DRIVER_NAME.

Formato de saida

use Mad\Database\SchemaIntrospector;

$pdo    = DB::connection('business')->getPdo();
$schema = (new SchemaIntrospector())->introspect($pdo);

// [
//   'engine' => 'sqlite'|'mysql'|'pgsql',
//   'tables' => [[
//     'name'           => 'mad_iam_user',
//     'type'           => 'table'|'view',
//     'rowCount'       => 1234,   // null quando indisponivel
//     'rowCountApprox' => true,   // o numero e ESTIMADO
//     'columns'        => [[
//        'name', 'type', 'isPrimary', 'isForeign', 'allowNull',
//        'references' => ['table' => ..., 'column' => ...] // ou null
//     ]],
//     'indexes'        => [['name', 'columns' => [...], 'unique' => bool]],
//     // 'error' => '...'  <- so aparece quando AQUELA tabela falhou
//   ]],
// ]

Uma tabela problematica nao derruba a introspecao inteira: ela entra na lista com columns/indexes vazios e uma chave error com a mensagem.

Custo: sem COUNT(*) e sem query por tabela (5.54.0)

Antes da 5.54.0 a introspecao fazia COUNT(*) em toda tabela e mais 3 queries de catalogo POR tabela (colunas, FKs, indices). Num banco com ~400 tabelas isso era ~1600 round-trips mais um full scan por tabela — abrir o explorer virava carga de producao. O que mudou:

  • rowCount vem da estatistica do engine, em 1 query, sem tocar nas linhas: information_schema.tables.table_rows no MySQL, pg_class.reltuples no PostgreSQL.
  • rowCountApprox (bool) diz que o numero e estimado — no front o valor aparece prefixado com ~. Em MySQL/PostgreSQL ele e sempre true.
  • Colunas, FKs, PKs e indices vem em LOTE — 1 query por tipo para o schema inteiro, nao 1 por tabela. No MySQL os indices passaram a vir de information_schema.statistics ordenado por seq_in_index, em vez de um SHOW INDEX por tabela.
  • Degradacao, nao erro: se a query em lote falhar (permissao, engine exotico), cada tabela cai no caminho por-tabela antigo.

Quando rowCount vem null

Situacao rowCount rowCountApprox
PostgreSQL, tabela que nunca passou por ANALYZE (reltuples = -1) null —
MySQL, engine sem estatistica (table_rows nulo) null —
type = 'view' null false
SQLite com sqlite_stat1 presente (ja rodou ANALYZE) estimado true
SQLite sem sqlite_stat1 COUNT(*) exato (arquivo local) false

Rode ANALYZE no banco para que as estimativas existam. Um rowCount null significa "o engine nao sabe", nao "tabela vazia" — nao trate os dois como a mesma coisa na UI.

Seguranca

Identificadores usados em PRAGMA/SHOW (que nao aceitam bind PDO) passam por allowlist de caractere e quote por driver antes de entrar na string SQL — defesa contra injecao por nome de tabela. Continua valendo a regra geral: nunca concatene entrada de usuario em SQL.

Ver tambem