source_url | revision | status | license |
---|---|---|---|
1718723742 |
ready |
- Versão: 1.0
- Data: 11/04/2024
- Pessoas autoras: Matteo Beccati [email protected]
- Situação: Implementada
- Versão alvo: 8.4
- Implementação: https://github.com/php/php-src/commit/fbe317bf2179c65b750cc945c3530b28db1670e0
- Publicada pela primeira vez em: https://wiki.php.net/rfc/pdo_driver_specific_parsers
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.
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
.
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:
- Alterar o
scanner PDO padrão
para esperar apenas SQL padrão:
- literais entre aspas simples e duplas, com duplicação como mecanismo de escape
- comentários com dois traços e no estilo C (não aninhados, pois parece ser o formato mais comum, já implementado no PDO)
- Adicionar uma função scanner específica do MySQL:
- literais entre aspas simples e duplas com duplicação e barra invertida como mecanismos de escape (padrão do MySQL)
- 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)
- dois traços (se seguidos por 1 espaço em branco), comentários no estilo C e comentários com cerquilha
- testes, conforme necessário
- Adicionar uma função scanner específica do PgSQL:
- literais entre aspas simples e duplas, com duplicação como mecanismo de escape
- literais de string de escape no estilo C
- literais de string delimitadas por cifrão
- 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)
- suporte para
??
como sequência de escape para o operador?
- testes, conforme necessário
- Adicionar uma função scanner específica do SQLite:
- literais entre aspas simples, duplas e crase, com duplicação como mecanismo de escape
- colchetes para delimitar identificadores
- comentários de dois traços e no estilo C (não aninhados)
- 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.
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.
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.
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.
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.
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.
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.
Como o ODBC pode se conectar a vários tipos de bancos de dados, esperamos que o analisador do padrão SQL seja suficiente.
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.
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.
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.
Próximo PHP 8.x, esperançosamente, 8.4.
Sem impacto.
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.
Nenhum impacto no opcache
.
Nenhuma nova constante.
Nenhuma alteração no php.ini
.
Nenhuma issue aberta no momento.
Qualquer coisa não relacionada ao PDO analisando a consulta SQL em busca de espaços reservados para parâmetros.
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.
Validar o suporte a sintaxes "exóticas" nos scanners existentes e/ou adicionar outras funcionalidades personalizadas do scanner.
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.
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.
Pull request da implementação.
- [1] https://bugs.php.net/bug.php?id=78534
- [2] https://bugs.php.net/bug.php?id=79276
- [3] https://bugs.php.net/bug.php?id=80340
- [4] RFC PHP: Escapar o espaço reservado para parâmetro
?
no PDO - [5] Implementação da prova de conceito de um
scanner
pdo_pgsql
personalizado - [6] Discussão anterior do tópico na lista internals
- [7] Falha mais recente