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:
rowCountvem da estatistica do engine, em 1 query, sem tocar nas linhas:information_schema.tables.table_rowsno MySQL,pg_class.reltuplesno PostgreSQL.rowCountApprox(bool) diz que o numero e estimado — no front o valor aparece prefixado com~. Em MySQL/PostgreSQL ele e sempretrue.- 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.statisticsordenado porseq_in_index, em vez de umSHOW INDEXpor 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
ANALYZEno banco para que as estimativas existam. UmrowCountnullsignifica "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
- Migrations —
Schema::connection()e os engines suportados. - Query Builder fluente — leitura de dados, nao de catalogo.