【问题标题】:sql.Identifier without quotingsql.Identifier 不带引号
【发布时间】:2020-01-28 06:19:37
【问题描述】:

我对“sql.Identifier without quoting”进行了网络搜索,this post 仅相关。它建议使用.format(sql.Identifier

但是,这种方法在标识符中添加了双引号,据我所知,它不能用于在 PostgreSQL 中不带引号的标识符。正如我在here 中读到的,专家建议不要在 Postgres 中引用标识符。

我在sql.Identifier 中没有看到跳过在the document 中引用的选项以及在psycopg2sql 模块中的替代方法。如何以注入安全的方式将 Python 中的 PostgreSQL 用于未加引号的标识符?

添加:我的困惑是由于我在 sql.Identifier 中使用了“public.abc”,正如@klin 的回答中所指出的那样,我应该使用两个标识符。整理后,我看到引号仅用于区分大小写(和/或使用“其他”符号如点的地方)。

【问题讨论】:

  • 我认为引用标识符很好。为所有标识符选择一致的大小写并坚持使用。双引号让你诚实。图书馆的行为强化了这一点。我的 2p。
  • @wobr,谢谢。只是不熟悉“public.abc”然后“abc”不是“public”。“abc”,abc,public.abc 有效,只有“public.abc”。

标签: python postgresql psycopg2


【解决方案1】:

您应该区分两种不同的情况。当您想在区分大小写的标识符中使用大写字母时,您必须使用双引号。如果您只使用小写字母作为标识符,您可以但不必使用双引号。专家通常建议避免第一种情况。第二种情况,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):

可以将多个字符串传递给对象来表示一个合格的 名称,即以点分隔的标识符序列。

【讨论】:

  • 谢谢!现在我看到我在公共方案中创建了表“public.abc”...
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

    但是,这种方法在标识符中添加了双引号,据我所知,它不能用于在 PostgreSQL 中不带引号的标识符。

    绝对可以。症结在于,带引号的标识符区分大小写(除其他问题外),而未加引号的标识符大小写折叠

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

    • postgres 折叠为小写(这已明确记录),只要您将带引号的标识符小写,它们就会匹配未加引号的标识符,但它是未加引号的标识符
    • 但是这不是可移植的,因为 SQL 规范要求 大写折叠
    • 并且由于数据库进行大小写折叠而不是不区分大小写的匹配,使用带引号的标识符创建的术语可能根本无法从不带引号的标识符访问,例如如果你创建一个表“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;
                        ^
    

    【讨论】:

      【解决方案4】:

      我已经实现了一个 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))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-18
        • 2019-10-11
        • 2014-01-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多