Skip to content

Latest commit

 

History

History
470 lines (362 loc) · 20.3 KB

analisadores-sql-especificos-do-driver-pdo.md

File metadata and controls

470 lines (362 loc) · 20.3 KB
source_url revision status license
1718723742
ready

Analisadores SQL Específicos do Driver PDO

Introdução

A extensão PDO contém um analisador SQL, cujo objetivo principal é reconhecer espaços reservados para parâmetros dentro de consultas (ou seja, ? e :paramName), para saber quantos e quais parâmetros esperar para uma consulta, e passar as informações para o driver PDO em uso.

Este analisador foi historicamente modelado para funcionar com o SQL padrão de fato no ecossistema PHP da época: MySQL. No entanto, o dialeto SQL usado pelo MySQL lida com literais de string de forma diferente do SQL padrão, seguido por outros fornecedores de banco de dados, como PostgreSQL e SQLite.

Especificamente, o MySQL trata o caractere de barra invertida como um caractere de escape:

'This \'word\' is a single quoted'

Ao passo que o SQL padrão usa aspas simples duplas:

'This ''word'' is a single quoted'

Ao usar bancos de dados diferentes do MySQL com PDO, consultas válidas com literais de string terminando com uma barra invertida estão prejudicando o analisador e causando falhas aparentemente inexplicáveis.

Por exemplo:

SELECT 'foo\' AS a, '?' AS b

fará com que o PDO considere 'foo\' AS a, ' como uma string literal e analise o ? seguinte como um espaço reservado para parâmetro posicional. Na verdade, se você prestar bastante atenção, até mesmo o formatador de código SQL da DocuWiki fica confuso com este exemplo.

Temos vários relatos de falhas semelhantes [1], [2], [3], e possivelmente outras duplicatas[7].

Resumindo, a função scanner SQL do PDO não precisa analisar completamente o dialeto SQL em uso. É suficiente reconhecer adequadamente literais de string entre aspas, literais de identificadores entre aspas e comentários para ignorar a detecção de espaço reservado dentro deles.

Melhoria de bônus

A limitação de um analisador SQL global no PDO significava que minha RFC anterior para suportar o operador ? somente do PostgreSQL[4] teve que aplicar a alteração do analisador a todos os tipos de banco de dados. A mudança não teve efeitos colaterais conhecidos, mas sinto que esta RFC poderia melhorar mantendo essa peculiaridade apenas dentro do scanner pdo_pgsql.

Proposta

Após uma pesquisa detalhada (veja abaixo) para cada um dos bancos de dados atualmente suportados pelo PDO no núcleo, a proposta é permitir que os drivers forneçam opcionalmente uma função scanner personalizada para lidar com seu dialeto SQL específico e:

  1. Alterar o scanner PDO padrão para esperar apenas SQL padrão:
    1. literais entre aspas simples e duplas, com duplicação como mecanismo de escape
    2. comentários com dois traços e no estilo C (não aninhados, pois parece ser o formato mais comum, já implementado no PDO)
  2. Adicionar uma função scanner específica do MySQL:
    1. literais entre aspas simples e duplas com duplicação e barra invertida como mecanismos de escape (padrão do MySQL)
    2. literais de crase com duplicação como mecanismo de escape (eu também testei e parece que o MySQL não aceita barras invertidas como escapes)
    3. dois traços (se seguidos por 1 espaço em branco), comentários no estilo C e comentários com cerquilha
    4. testes, conforme necessário
  3. Adicionar uma função scanner específica do PgSQL:
    1. literais entre aspas simples e duplas, com duplicação como mecanismo de escape
    2. literais de string de escape no estilo C
    3. literais de string delimitadas por cifrão
    4. comentários de dois traços e no estilo C (não aninhados, pois o aninhamento exigiria alterações indesejadas na funcionalidade comum do analisador)
    5. suporte para ?? como sequência de escape para o operador ?
    6. testes, conforme necessário
  4. Adicionar uma função scanner específica do SQLite:
    1. literais entre aspas simples, duplas e crase, com duplicação como mecanismo de escape
    2. colchetes para delimitar identificadores
    3. comentários de dois traços e no estilo C (não aninhados)
    4. testes, conforme necessário

Para manter a mudança o mais simples possível, a proposta tenta cobrir a sintaxe SQL padrão para cada banco de dados o mais próximo possível, sem grandes alterações no código do analisador comum.

Uma coisa importante a mencionar é que as alterações propostas visam apenas a parte do PDO que examina a consulta SQL em busca de espaços reservados para parâmetros. A notação de literais ou o uso de parâmetros em consultas não serão afetados.

Proposta Detalhada

Para executar o plano, um novo membro será anexado a:

struct pdo_dbh_methods {
	pdo_dbh_close_func		closer;
	pdo_dbh_prepare_func	preparer;
	pdo_dbh_do_func			doer;
	pdo_dbh_quote_func		quoter;
	// ...
	pdo_dbh_sql_scanner		scanner;
}

Cada driver PDO já define sua estrutura. Deixar o novo membro como NULL fará com que o driver use a função scanner PDO padrão. Caso contrário, um ponteiro para uma função scanner personalizada substituirá a função padrão ao analisar consultas. É realmente simples assim.

O resto da implementação é o próprio código do scanner re2c, config.*, alterações no Makefile, etc., necessárias para incorporar o scanner específico do driver na compilação.

Para oferecer suporte a strings delimitadas por cifrão no Postgres, a funcionalidade de delimitação personalizada foi adicionada à função comum do analisador PDO. A alteração não tem efeitos colaterais para outros drivers de banco de dados.

Uma pequena quebra potencial de compatibilidade com versões anteriores foi relatada durante a pesquisa da falha #14244, que basicamente descreve a falta de suporte para delimitação por cifrão no pdo_pgsql. Uma das soluções alternativas atualmente viáveis é usar pontos de interrogação com escape dentro de strings delimitadas por cifrão para evitar a detecção inesperada de espaços reservados. A última versão da implementação ainda permite isso, embora gere o seguinte alerta de descontinuação:

Escaping question marks inside dollar quoted strings is not required anymore
and is deprecated.

Essa compatibilidade com versões anteriores pode ser removida na próxima versão principal.

Pesquisa Sobre Literais de String, Identificadores e Comentários

MySQL

O MySQL, por padrão, aceita aspas com escape de barra invertida e o padrão SQL. Literais de string podem usar aspas simples ou duplas. Veja a documentação (a versão 8.0 está listada aqui, mas a 5.7 e a 8.3 se comportam da mesma forma).

O modo SQL NO_BACKSLASH_ESCAPES desabilitará o reconhecimento da barra invertida como caractere de escape. Se definido, interromperá a análise do SQL, independentemente desta RFC.

O modo SQL ANSI_QUOTES muda o uso de crase para aspas duplas do padrão SQL para os literais de identificadores.

Vários tipos de comentários suportados: -- , # e /* */ (não aninhado).

A RFC visa oferecer suporte a todos os tipos de literais de string acima com variáveis de configuração que afetam strings definidas com seus padrões. Todos os tipos de comentários serão suportados.

PostgreSQL

O escape evoluiu ao longo dos anos. Historicamente, foi aceito \', mas começou mudar gradualmente para o padrão SQL por volta de 2005, saindo da memória. Desde a versão 9.1 (2011+), ele aceita apenas literais de string entre aspas simples por padrão, conforme o padrão SQL. Consulte a documentação.

O Postgres também suporta constantes de string com escapes Unicode, que seguem as mesmas convenções das strings padrão sendo analisadas pelo PDO como strings regulares.

Ele também aceita constantes de string de "escape", por exemplo.

E'This \'word\' is a single quoted'

Por último, constantes de string delimitadas por cifrão são muito comuns, especialmente ao definir funções.

O comportamento das strings também pode ser manipulado de diversas maneiras usando variáveis de configuração, como: standard_conforming_strings, backslash_quote e escape_string_warning.

Sobre comentários, segue o padrão com -- e /* */ (com comentários aninhados permitidos).

A RFC visa oferecer suporte a todos os tipos de literais de string acima com variáveis de configuração que afetam strings definidas com seus padrões. O suporte à delimitação por cifrão requer alterações mínimas na função comum do analisador PDO. Todos os tipos de comentários já são suportados, embora o suporte para comentários aninhados não seja introduzido.

SQLite

Segue o padrão SQL e requer aspas simples duplas para representar as aspas simples em uma string literal. Consulte a documentação.

No entanto, ele aceitará strings entre aspas duplas como literais de string em certas circunstâncias.

Identificadores entre aspas duplas, mas também crases e colchetes.

Comentários quase padrão SQL: -- e /* */ (não aninhado).

A RFC visa oferecer suporte a literais entre aspas simples, aspas duplas, crases e colchetes. Todos os tipos de comentários já são suportados.

SQL Server

Literais de string do padrão SQL, conforme a documentação.

Dependendo da configuração QUOTED_IDENTIFIER, aspas duplas são usadas para strings ou identificadores.

Comentários quase padrão SQL: -- e /* */ (não aninhados).

Nenhum analisador personalizado está planejado nesta RFC: o scanner padrão será usado por padrão, trazendo compatibilidade para literais de string, identificadores e comentários do padrão SQL.

Firebird

Literais de string do padrão SQL, conforme a documentação. Ele também oferece suporte a strings hexadecimais (binárias), por exemplo, x'50444F' e, semelhantemente ao Oracle, strings entre aspas (fora do escopo).

A documentação menciona "Aspas duplas NÃO SÃO VÁLIDAS para delimitar strings. O padrão SQL reserva aspas duplas para uma finalidade diferente: delimitar identificadores."

Comentários quase padrão SQL: -- e /* */ (não aninhados).

Nenhum analisador personalizado está planejado nesta RFC: o scanner padrão será usado por padrão, trazendo compatibilidade para literais de string, identificadores e comentários do padrão SQL.

ODBC

Como o ODBC pode se conectar a vários tipos de bancos de dados, esperamos que o analisador do padrão SQL seja suficiente.

Oracle

Literais de string do padrão SQL, conforme a documentação. Ele também suporta delimitações alternativas, por exemplo, q'<literal>' e muitas outras variantes, o que está fora do escopo desta RFC.

Identificadores com aspas duplas.

Comentários quase padrão SQL: -- e /* */ (não aninhados).

O driver OCI pode ser encontrado no PECL: o scanner padrão será usado por padrão, trazendo compatibilidade para literais de string, identificadores e comentários do padrão SQL.

Contexto Histórico

Alguns anos atrás, tentei consertar uma falha e criei uma pull request que poderia ser considerada uma prova de conceito para esta RFC. O mesmo tópico também foi levantado por outras pessoas na lista internals, mas ninguém teve tempo de seguir com uma RFC adequada.

Alterações Incompatíveis com Versões Anteriores

Não há quebras de compatibilidade com versões anteriores, mas um alerta de descontinuação será gerado ao usar a solução alternativa "pontos de interrogação com escape dentro de strings delimitadas por cifrão" descrita anteriormente.

As pessoas usuárias que possuem aplicações que podem trabalhar com vários motores de banco de dados ainda devem ter muito cuidado e escrever consultas portáteis, possivelmente usando o método PDO::quote() quando necessário, em vez de codificar strings contendo caracteres de escape.

Versões propostas do PHP

Próximo PHP 8.x, esperançosamente, 8.4.

Impacto da RFC

Para SAPIs

Sem impacto.

Para extensões existentes

Drivers fora do php-src podem precisar ser modificados se fizerem suposições sobre a estrutura da enum pdo_param_type. Eles teriam que ser reconstruídos já que a macro PDO_DRIVER_API seria atualizada.

Isso tem sido historicamente permitido/esperado em versões menores. A última vez que isso aconteceu foi no PHP 7.2 com a RFC Tipos de String Estendidos para o PDO.

Para o OPcache

Nenhum impacto no opcache.

Novas Constantes

Nenhuma nova constante.

Padrões do php.ini

Nenhuma alteração no php.ini.

Issues Abertas

Nenhuma issue aberta no momento.

Funcionalidade PHP Não Afetada

Qualquer coisa não relacionada ao PDO analisando a consulta SQL em busca de espaços reservados para parâmetros.

Fora do Escopo

Mudanças dinâmicas no scanner

Os scanners são gerados quando o PHP é compilado e, atualmente, não podem ser modificados em tempo de execução. Entretanto, alguns bancos de dados permitem que diretivas de configuração ou consultas SET alterem a sintaxe aceita para literais, identificadores, etc.

Conseguir compreender todas as combinações possíveis exigiria rastrear quais diretivas são diferentes do padrão esperado e ter vários scanners dentro de cada driver para cada permutação possível de tais diretivas de configuração.

Escopo Futuro

Validar o suporte a sintaxes "exóticas" nos scanners existentes e/ou adicionar outras funcionalidades personalizadas do scanner.

Votação

A votação estará aberta até segunda-feira, 17 de junho de 2024 às 15h00 UTC. Como habitualmente, é necessária uma maioria de 2/3 para que esta proposta seja aceita.

Implementar analisadores SQL específicos do driver PDO?

Nome Sim Não
adiel (adiel) X
ashnazg (ashnazg) X
crell (crell) X
derick (derick) X
dharman (dharman) X
didou (didou) X
ericmann (ericmann) X
galvao (galvao) X
heiglandreas (heiglandreas) X
jimw (jimw) X
kalle (kalle) X
kguest (kguest) X
kocsismate (kocsismate) X
lufei (lufei) X
mauricio (mauricio) X
mbeccati (mbeccati) X
mcmic (mcmic) X
ocramius (ocramius) X
petk (petk) X
ramsey (ramsey) X
reywob (reywob) X
santiagolizardo (santiagolizardo) X
sergey (sergey) X
theodorejb (theodorejb) X
timwolla (timwolla) X
weierophinney (weierophinney) X
wez (wez) X
Resultado final 27 0

Esta enquete foi encerrada.

Patches e Testes

Pull request da implementação.

Referências