禁用带引号的值到整数的隐式转换

xmo*_*xmo 2 postgresql

# CREATE TABLE foo ( id serial, val integer );
CREATE TABLE
# INSERT INTO foo (val) VALUES ('1'), (2);
INSERT 0 2
# SELECT * FROM foo WHERE id='1';
 id | val 
----+-----
  1 |   1
(1 row)
Run Code Online (Sandbox Code Playgroud)

这里,在插入和选择时,postgres 都会隐式地将带引号的字符串转换为整型,而不是引发类型错误,除非带引号的值非常明确地键入为 varchar:

# INSERT INTO foo (val) VALUES (varchar '1');
ERROR:  column "val" is of type integer 
        but expression is of type character varying
LINE 1: INSERT INTO foo (val) VALUES (varchar '1');
                                              ^
HINT:  You will need to rewrite or cast the expression.
Run Code Online (Sandbox Code Playgroud)

这里的问题是针对没有隐式转换的动态类型语言(例如 Ruby 或 Python)

  • 带引号的值映射到字符串
  • 整数映射到整数
  • 这些不兼容,因此根据连接应用程序的体系结构,此行为可能会导致缓存不一致等

有没有办法禁用它并强制引用值始终为 varchar(除非显式转换)?

编辑:因为人们显然关注不相关的内容,这些查询来自参数化语句,psycopg2 会将字符串转换为带引号的值,然后将带引号的值转换回字符串,因此无论访问方法如何,不匹配都存在,这是一个红鲱鱼。这与参数化语句完全相同:

import psycopg2.extensions

with psycopg2.connect(dbname='postgres') as cn:
    cn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
    with cn.cursor() as cx:
        cx.execute("DROP DATABASE IF EXISTS test")
        cx.execute("CREATE DATABASE test")

with psycopg2.connect(dbname='test') as cn:
    with cn.cursor() as cx:
        cx.execute("CREATE TABLE foo ( id serial, val integer )")
        cx.execute("INSERT INTO foo (val) VALUES (%s), (%s)",
                   (1, '2'))
        cx.execute("SELECT * FROM foo WHERE id=%s",
                   ('1',))
        print cx.fetchall()
Run Code Online (Sandbox Code Playgroud)

其输出:

[(1, 1)]
Run Code Online (Sandbox Code Playgroud)

Cra*_*ger 5

不可以,您不能禁用带引号的文字到任何目标类型的隐式转换。PostgreSQL 认为此类文字是类型unknown,除非被强制转换或文字类型说明符覆盖,并且会转换为unknown任何类型。没有从unknown到 中的类型的转换pg_cast;这是隐含的。所以你不能丢掉它。

\n\n

据我所知,PostgreSQL 遵循 SQL 规范,接受带引号的文字作为整数。

\n\n

对于 PostgreSQL 的类型引擎,1integer, ,\'1\'unknown类型推断为integer传递给integer函数、运算符或字段的 if 的类型。您不能禁用类型推断unknown或强制unknown将其视为text不直接攻击解析器/查询规划器。

\n\n

您应该做的是使用参数化语句,而不是将文字替换为 SQL。如果这样做,就不会遇到此问题,因为客户端类型是已知的或可以指定。那当然可以与 Python (psycopg2) 和 Ruby (Pg gem) 配合使用不符合我对 psycopg2 的想法,见下文。

\n\n
\n\n

问题澄清后更新:在此处描述的狭义情况下,psycopg2 的客户端参数化语句虽然正确,但不会产生原始发布者期望的结果。运行更新中的演示表明 psycopg2 没有使用 PostgreSQL 的 v3 绑定/执行协议,它使用简单查询协议并在本地进行参数替换。因此,当您在 Python 中使用参数化语句时,您不会在 PostgreSQL 中使用参数化语句。我在上面说 psycopg2 中的参数化语句可以解决这个问题,这是错误的。

\n\n

该演示从 PostgreSQL 日志运行此 SQL:

\n\n
< 2014-07-07 18:17:24.450 WST >LOG:  statement: INSERT INTO foo (val) VALUES (1), (\'2\')\n< 2014-07-07 18:17:24.451 WST >LOG:  statement: SELECT * FROM foo WHERE id=\'1\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意缺少放置参数。它们被客户端取代。

\n\n

所以如果你想要psycopg2更严格,则必须调整客户端框架。

\n\n

psycopg2是可扩展的,所以这应该非常实用 - 您需要根据适应新类型str来重写,unicodeinteger(或者,在 Python3 中,bytes,strinteger)的类型处理程序。甚至还有一个关于覆盖 psycopg2 处理的常见问题解答条目作为示例:http ://initd.org/psycopg/docs/faq.html#faq-floatpsycopg2.extrasfloat

\n\n

由于无限递归, na\xc3\xafve 方法不起作用:

\n\n
def adapt_str_strict(thestr):\n    return psycopg2.extensions.AsIs(\'TEXT \' + psycopg2.extensions.adapt(thestr))\n\npsycopg2.extensions.register_adapter(str, adapt_str_strict)\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此您需要绕过类型适配器注册来调用原始底层适配器str. 这会的,尽管它很难看:

\n\n
def adapt_str_strict(thestr):\n    return psycopg2.extensions.AsIs(\'TEXT \' + str(psycopg2.extensions.QuotedString(thestr)))\n\npsycopg2.extensions.register_adapter(str, adapt_str_strict)\n
Run Code Online (Sandbox Code Playgroud)\n\n

运行你的演示,你会得到:

\n\n
psycopg2.ProgrammingError: parameter $1 of type text cannot be coerced to the expected type integer\nHINT:  You will need to rewrite or cast the expression.\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

(顺便说一句,使用服务器端PREPAREEXECUTE行不通的,因为在将值传递给EXECUTEvia时,您只会遇到相同的键入问题psycopg2时您只会遇到相同的键入问题)。

\n