Quebra o texto em tokens: palavra reservada, nome, número, texto, símbolo
Confere a ordem dos tokens contra a gramática e monta a árvore sintática
A tabela existe? A coluna existe? O tipo bate? Quem responde é o catálogo
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.
INSERT INTO aluno VALUES (1, 20260001, 'Ana');
Etapa 1, tokenizador: treze tokens, contando o FIM
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 é conferir a árvore contra o que existe no banco.
Para o nosso INSERT, são três perguntas:
aluno existe?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 é 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.
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.
INSERT INTO aluno VALUES (1, 20260001, 'Ana');
| Pergunta | O catálogo responde | Veredito |
|---|---|---|
aluno existe? | sim, começa na página 2 | passa |
| 3 valores para quantas colunas? | 3: id, matricula, nome | passa |
id recebe 1 | id é INT | passa |
matricula recebe 20260001 | matricula é INT | passa |
nome recebe 'Ana' | nome é CHAR(20), e 'Ana' tem 3 bytes | passa |
Tudo passa, e o comando segue para a execução já sabendo duas coisas: o esquema de aluno e a página 2.
INSERT INTO aluno VALUES (1, 'Ana', 20260001);
Os mesmos valores, em outra ordem. O tokenizador e o parser aceitam: a árvore é válida.
| Pergunta | O catálogo responde | Veredito |
|---|---|---|
id recebe 1 | id é INT | passa |
matricula recebe 'Ana' | matricula é INT | falha |
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.
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.
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.
Para trazer no papel ao laboratório.
(a) INSERT INTO alunos VALUES (2, 20260002, 'Bruno'); (b) INSERT INTO aluno VALUES (2, 20260002); (c) INSERT INTO aluno VALUES (2, 20260002, Bruno); (d) INSERT INTO aluno VALUES (2, 20260002, 'Maria Eduarda Albuquerque');
INSERT da Ana. Escreva a saída de hexdump -C -s 8192 -n 48 dados.db e marque onde o registro começa e terminaINSERT cabem na página 2? O que o executa do slide 10 faz no seguinte?$ 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 |................|
00002000 é 8192, a página 2. No cabeçalho, um slot ocupado e registro de 0x1c = 28 bytes00002010: id = 1, matricula = 0x013524a1 = 20260001, e Ananome é CHAR(20): depois de Ana vêm 17 bytes zero. O registro termina em 0000202b