Banco de Dados II — 2026.2

Módulo 4, parte 2
Análise semântica e execução

👨‍🏫 Prof. Gustavo Pinto 🏛️ UFPA 📅 22 de outubro de 2026 🕐 7h30 – 9h

O caminho de uma consulta

1

Tokenizador

Quebra o texto em tokens: palavra reservada, nome, número, texto, símbolo

2

Parser

Confere a ordem dos tokens contra a gramática e monta a árvore sintática

3

Análise semântica

A tabela existe? A coluna existe? O tipo bate? Quem responde é o catálogo

4

Execução

No M4, um if por comando, direto sobre a árvore. No M5, operadores

Na terça, as etapas 1 e 2. Hoje, a 3 e a 4, e a aula inteira usa um único comando.

O comando de hoje

INSERT INTO aluno VALUES (1, 20260001, 'Ana');

Etapa 1, tokenizador: treze tokens, contando o FIM

INSERTpalavraINTOpalavraalunonomeVALUESpalavra(símbolo1número,símbolo20260001número,símbolo'Ana'texto)símbolo;símboloFIMfim

Etapa 2, parser: a árvore

{"op": "INSERT", "tabela": "aluno", "valores": [1, 20260001, "Ana"]}

É daqui que a aula de hoje parte. O texto acabou: as etapas 3 e 4 só olham a árvore.

Análise semântica

Análise semântica é conferir a árvore contra o que existe no banco.

Para o nosso INSERT, são três perguntas:

Nenhuma resposta está na árvore. O parser sabe que veio um nome e três valores, mas não sabe que tabelas existem. Quem sabe é o catálogo.

O catálogo

O catálogo é uma tabela que descreve as outras tabelas. Uma linha por tabela.

página 1 do arquivo, registro de 236 bytes

nome      CHAR(32)    "aluno"
primeira  INT         2                         // página onde aluno começa
colunas   CHAR(200)   "id INT, matricula INT, nome CHAR(20)"

As três respostas estão nessa linha: a tabela existe porque a linha existe, e as colunas, com seus tipos, estão no último campo. De brinde, a linha diz em que página escrever.

O catálogo mora no disco

ESQUEMA_CATALOGO = Esquema([("nome", "CHAR", 32), ("primeira", "INT", 4),
                            ("colunas", "CHAR", 200)])
PAGINA_CATALOGO = 1                                # fixa, combinada no código

class Catalogo:
    def __init__(self, cache):                     # ao abrir o arquivo
        self.tabelas = {}
        for nome, primeira, colunas in le_registros(cache, PAGINA_CATALOGO, ESQUEMA_CATALOGO):
            self.tabelas[nome] = (primeira, Esquema(analisa_colunas(colunas)))

    def busca(self, nome):
        if nome not in self.tabelas:
            raise ErroSemantico(f"tabela {nome} não existe")
        return self.tabelas[nome]

Só duas coisas ficam no código: o esquema do próprio catálogo e a página onde ele está. O resto é lido do disco, e por isso um processo novo sabe que aluno existe.

A semântica rodando à mão

INSERT INTO aluno VALUES (1, 20260001, 'Ana');
PerguntaO catálogo respondeVeredito
aluno existe?sim, começa na página 2passa
3 valores para quantas colunas?3: id, matricula, nomepassa
id recebe 1id é INTpassa
matricula recebe 20260001matricula é INTpassa
nome recebe 'Ana'nome é CHAR(20), e 'Ana' tem 3 bytespassa

Tudo passa, e o comando segue para a execução já sabendo duas coisas: o esquema de aluno e a página 2.

Quando a semântica recusa

INSERT INTO aluno VALUES (1, 'Ana', 20260001);

Os mesmos valores, em outra ordem. O tokenizador e o parser aceitam: a árvore é válida.

PerguntaO catálogo respondeVeredito
id recebe 1id é INTpassa
matricula recebe 'Ana'matricula é INTfalha
erro semântico: coluna matricula é INT, veio 'Ana'

A semântica vem antes da execução, então nenhum byte foi escrito. O banco recusa o comando inteiro, e não fica meio registro na página.

A semântica em pseudocódigo

confere(tabela, valores):
    se tabela não está no catálogo:
        erro "tabela não existe"
    colunas = colunas da tabela, lidas do catálogo
    se número de valores ≠ número de colunas:
        erro "esperava N valores, vieram M"
    para cada par (coluna, valor):
        se coluna é INT e valor não é número:
            erro "coluna é INT"
        se coluna é CHAR(n) e valor não é texto de até n bytes:
            erro "coluna é CHAR(n)"

É toda a análise semântica do INSERT. Nada aqui olha token: a tabela e os valores vêm da árvore, e as colunas vêm do catálogo.

A execução em pseudocódigo

executa(sql):
    árvore = analisa(sql)                         // etapas 1 e 2
    se árvore é INSERT:
        confere(árvore.tabela, árvore.valores)    // etapa 3
        registro = serializa(árvore.valores)      // etapa 4: 28 bytes
        página = fixa(primeira página da tabela)
        escreve registro no primeiro slot livre
        soma 1 ao número de slots ocupados
        solta(página), marcada como suja
        devolve "OK 1"

A execução não tem nada novo: serializa é do M1, fixa e solta são do M2. O que muda é quem fornece os argumentos: a página vem do catálogo, os valores vêm da árvore.

Exercício de hoje

Para trazer no papel ao laboratório.

Gabarito

Gabarito do item 2

$ hexdump -C -s 8192 -n 48 dados.db
00002000  01 00 1c 00 02 00 00 00  00 00 00 00 00 00 00 00  |................|
00002010  01 00 00 00 a1 24 35 01  41 6e 61 00 00 00 00 00  |.....$5.Ana.....|
00002020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|