sql.Identifier 不加引号

sql.Identifier without quoting

我在网络上搜索了 "sql.Identifier without quoting",结果 只相关。它建议使用 .format(sql.Identifier

但是,此方法为标识符添加了双引号,据我所知不能用于在 PostgreSQL 中使用 w/out 引号的标识符。正如我在 here 中读到的专家建议不要在 Postgres 中引用标识符。

我在 sql.Identifier 中没有看到跳过 the document 中的引号和 psycopg2sql 模块中的替代方法的选项。我如何以注入安全的方式使用 Python 中的 PostgreSQL 来获取不带引号的标识符?

添加:我的困惑是由于我使用 "public.abc" 作为 sql.Identifier,正如@klin 在回答中指出的那样,我应该使用两个标识符。整理之后,我看到引号仅用于区分大小写(and/or,其中使用 "other" 符号,如点)。

如果您的数据库 API 经常引用标识符,那是一件 好事 。不要努力解决这个问题。

反对引用标识符的情绪并不是针对这样的引用,而是针对需要 quoting.That不遵循语法的标识符的选择标识符规则或包含大写字母。这样的标识符会引起各种麻烦,例如在 shell 脚本中,引用总是很麻烦。

引用不需要引用的标识符是无害的,实际上是一种很好的安全措施。

However this method adds double quotes to identifiers and cannot as far as I can tell be used for identifiers made w/out quotes in PostgreSQL.

绝对可以。症结在于引用的标识符区分大小写(除其他问题外),而未引用的标识符 case-folded.

这种大小写折叠是您会得到奇怪结果的原因:当您编写 create table Foo ()select Foo from Bar 时,Postgres 首先将其大小写折叠规则应用于未加引号的标识符,然后再执行其他所有操作。因此,如果您引用标识符,则需要匹配该大小写折叠的 result

  • postgres 折叠成小写字母(这是明确记录的),只要你将带引号的标识符小写,它们就会匹配不带引号的标识符,但是不带引号的标识符是写的
  • 然而这不是table因为SQL规范要求大写折叠
  • 并且因为数据库会进行大小写折叠而不是不区分大小写的匹配,所以使用带引号的标识符创建的术语可能根本无法从未带引号的标识符访问,例如如果你创建一个 table “Foo”,select from Foo 甚至看不到它:
# create table "Foo" ();
CREATE TABLE
# select from "Foo";
--
(0 rows)

# select from Foo;
ERROR:  relation "foo" does not exist
LINE 1: select from Foo;
                    ^

你应该区分两种不同的情况。当您想在区分大小写的标识符中使用大写字母时,您 必须 使用双引号。如果您只使用小写字母作为标识符,您可以但不必使用双引号。专家通常建议避免第一种情况。第二种情况,psycopg2自动添加的引号没有问题

注意,public.abc不是一个标识符,它是一个包含两个标识符的表达式。因此你应该像这样使用它:

sql.SQL("select * from {}.{}").format(sql.Identifier(schema_name), sql.Identifier(table_name))

或者这个:

sql.SQL("select * from {}").format(sql.Identifier(schema_name, table_name))

作为(根据 the documentation):

Multiple strings can be passed to the object to represent a qualified name, i.e. a dot-separated sequence of identifiers.

我已经实现了 psycopg2.sql.Identifier 的一个版本,它只会在需要时引用标识符:

"""psycopg2 psycopg2.sql.Identifier that will quote identifiers only when needed"""

import re
import psycopg2
import psycopg2.sql


class SQLIdentifier(psycopg2.sql.Identifier):
    """psycopg2 psycopg2.sql.Identifier that will quote identifiers only when needed"""

    def as_string(self, context: typing.Any) -> str:
        """Return str representation of this identifier, quoted when needed"""
        return ".".join(self.quote_ident_if_required(s, context) for s in self.strings)

    def __str__(self) -> str:
        return ".".join(self.quote_ident_if_required(s, None) for s in self.strings)

    SAFE_IDENTIFIER_PATTERN = re.compile("[a-z_][a-z0-9_]*")

    @staticmethod
    def quote_ident_if_required(identifier: str, context: typing.Any) -> str:
        """Return str quoted if required for use in an identifier"""
        if SQLIdentifier.SAFE_IDENTIFIER_PATTERN.fullmatch(identifier):
            return identifier
        if context is None:
            return '"{}"'.format(identifier.replace('"', '""'))
        return str(psycopg2.extensions.quote_ident(identifier, context))