用于编码字符串的 UDF 在 psql 和 Perl 中有效,但在 Python 中无效
UDF to encode strings works in psql and Perl but not in Python
我在 Postgres 9.4 中编写了一个用户定义的函数来编码字符串:
CREATE OR REPLACE FUNCTION platform.encode_sig(sig text)
RETURNS bigint AS $BODY$
declare sig_id bigint;
begin
lock table platform.sig2encodings in access exclusive mode;
execute 'select sig_id from platform.sig2encodings where sig = ''' || sig || '''' into sig_id;
if sig_id is null
then
raise notice 'I do not have encoding for %', sig;
execute 'insert into platform.sig2encodings (sig) values (''' || sig || ''')';
execute 'select sig_id from platform.sig2encodings where sig = ''' || sig || '''' into sig_id;
else
raise notice 'I do have encoding for %', sig;
end if;
return sig_id;
END;
$BODY$
LANGUAGE plpgsql VOLATILE COST 100;
table:
CREATE TABLE platform.sig2encodings
(
sig_id bigserial NOT NULL,
sig text,
CONSTRAINT sig2encodings_pkey PRIMARY KEY (sig_id ),
CONSTRAINT sig2encodings_sig_key UNIQUE (sig )
)
pgadmin 或 psql 中的调用将数据插入 table:
select * from platform.encode_sig('NM_Gateway_NL_Shutdown');
python 中的调用获取 id
,但不 插入数据:
db="""dbname='XXX' user='XXX' password='XXX' host=XXX port=XXX"""
def encode_sig(sig):
try:
conn=psycopg2.connect(db)
except:
print "I am unable to connect to the database."
exit()
cur = conn.cursor()
try:
sql = "select * from platform.encode_sig('" + sig + "');"
print sql
cur.execute(sql)
except:
print "I can't retrieve sid"
row = cur.fetchone()
return row[0]
print str(encode_sig('NM_Gateway_UDS_CC'))
python 脚本的输出:
$ ./events_insert.py
616
617
618
619
620
621
$ ./events_insert.py
622
623
624
625
626
627
postgres中的table是空的。这是怎么回事?
更新:
以下 perl 脚本有效(包含所有控制台输出 (NOTICE) 和 table 中的行):
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use DBI;
my $dbh = get_connection();
$dbh->do("SELECT platform.encode_sig('blah blah blah')");
$dbh->disconnect();
sub get_connection {
return DBI->connect('dbi:Pg:dbname=XXX;host=XXX;port=XXX',
'XXX', 'XXX', { RaiseError => 1 });
}
数据库配置非常标准。这些行来自 postgresql.conf(因为它们被注释掉了,所以采用默认值):
#fsync = on # turns forced synchronization on or off
#synchronous_commit = on # synchronization level;
# off, local, remote_write, or on
#wal_sync_method = fsync # the default is the first option
# supported by the operating system:
# open_datasync
# fdatasync (default on Linux)
# fsync
# fsync_writethrough
# open_sync
#full_page_writes = on # recover from partial page writes
#wal_log_hints = off # also do full page writes of non-critical updates
# (change requires restart)
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
#commit_delay = 0 # range 0-100000, in microseconds
#commit_siblings = 5 # range 1-1000
不清楚,在您看到 sig_id
返回后,table 是如何为空的。想到的唯一合理的解释:
- 您不小心检查了一个不同的table(在不同的模式或不同的数据库中)。
- 您是 运行
auto_commit = off
并且忘记了 COMMIT
您的交易。 COMMIT
. 之前的其他会话看不到结果
无论哪种方式,您的函数都是不必要的复杂,您不需要动态 SQL 和 EXECUTE
。由于您将未转义的文本参数连接到代码中,因此您很容易出现随机语法错误和 SQL 注入 .
您还很危险地接近参数名称 sig
和列名称 sig
之间的 命名冲突 。你用动态 SQL 抛弃了最后一颗子弹,但它仍然是一把上膛的步兵枪。阅读手册中 PL/pgSQL 的 chapter Variable Substitution 并考虑唯一名称。
最后,每行调用一个函数也是效率极低。 整个过程可以用这个单个SQL语句代替:
LOCK TABLE platform.sig2encodings IN ACCESS EXCLUSIVE MODE;
WITH sel AS (
SELECT e.sig_id, e.sig
, (s.sig IS NULL) AS insert_new
FROM platform.encode_sig e
LEFT JOIN platform.sig2encodings s USING (sig)
)
, ins AS (
INSERT INTO platform.sig2encodings (sig)
SELECT sig FROM sel WHERE insert_new
RETURNING sig_id, sig, true -- value for insert_new
)
SELECT * FROM sel WHERE NOT insert_new
UNION ALL
SELECT * FROM ins;
这会将 encode_sig
中不存在的所有 sig
插入到 sig2encodings
中。它 returns 生成的 sig_id, sig
和 insert_new = true
,附加到未插入的 encode_sig
的 sig_id, sig
和 insert_new = false
。
如果您需要一个用于单行 INSERT-or-SELECT 的函数,它可以安全地同时使用:
- Is SELECT or INSERT in a function prone to race conditions?
或者您希望 INSERT .. ON CONFLICT IGNORE
进入下一个版本以简化事情:
- PL/pgSQL column name the same as variable
更新:已经committed for 9.5. The /devel manual already has instructions。
经过长时间的尝试和错误,我们发现这是由于连接缺少 "commit" 语句造成的。 python(或者 java+postgres 驱动程序+postgres 的组合)在脚本退出时跳过 "commit" 语句,导致数据库状态不一致(序列已更新,但表没有)。所以解决方案是在 python 脚本中添加以下行:
conn.commit()
我在 Postgres 9.4 中编写了一个用户定义的函数来编码字符串:
CREATE OR REPLACE FUNCTION platform.encode_sig(sig text)
RETURNS bigint AS $BODY$
declare sig_id bigint;
begin
lock table platform.sig2encodings in access exclusive mode;
execute 'select sig_id from platform.sig2encodings where sig = ''' || sig || '''' into sig_id;
if sig_id is null
then
raise notice 'I do not have encoding for %', sig;
execute 'insert into platform.sig2encodings (sig) values (''' || sig || ''')';
execute 'select sig_id from platform.sig2encodings where sig = ''' || sig || '''' into sig_id;
else
raise notice 'I do have encoding for %', sig;
end if;
return sig_id;
END;
$BODY$
LANGUAGE plpgsql VOLATILE COST 100;
table:
CREATE TABLE platform.sig2encodings
(
sig_id bigserial NOT NULL,
sig text,
CONSTRAINT sig2encodings_pkey PRIMARY KEY (sig_id ),
CONSTRAINT sig2encodings_sig_key UNIQUE (sig )
)
pgadmin 或 psql 中的调用将数据插入 table:
select * from platform.encode_sig('NM_Gateway_NL_Shutdown');
python 中的调用获取 id
,但不 插入数据:
db="""dbname='XXX' user='XXX' password='XXX' host=XXX port=XXX"""
def encode_sig(sig):
try:
conn=psycopg2.connect(db)
except:
print "I am unable to connect to the database."
exit()
cur = conn.cursor()
try:
sql = "select * from platform.encode_sig('" + sig + "');"
print sql
cur.execute(sql)
except:
print "I can't retrieve sid"
row = cur.fetchone()
return row[0]
print str(encode_sig('NM_Gateway_UDS_CC'))
python 脚本的输出:
$ ./events_insert.py
616
617
618
619
620
621
$ ./events_insert.py
622
623
624
625
626
627
postgres中的table是空的。这是怎么回事?
更新:
以下 perl 脚本有效(包含所有控制台输出 (NOTICE) 和 table 中的行):
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use DBI;
my $dbh = get_connection();
$dbh->do("SELECT platform.encode_sig('blah blah blah')");
$dbh->disconnect();
sub get_connection {
return DBI->connect('dbi:Pg:dbname=XXX;host=XXX;port=XXX',
'XXX', 'XXX', { RaiseError => 1 });
}
数据库配置非常标准。这些行来自 postgresql.conf(因为它们被注释掉了,所以采用默认值):
#fsync = on # turns forced synchronization on or off
#synchronous_commit = on # synchronization level;
# off, local, remote_write, or on
#wal_sync_method = fsync # the default is the first option
# supported by the operating system:
# open_datasync
# fdatasync (default on Linux)
# fsync
# fsync_writethrough
# open_sync
#full_page_writes = on # recover from partial page writes
#wal_log_hints = off # also do full page writes of non-critical updates
# (change requires restart)
#wal_buffers = -1 # min 32kB, -1 sets based on shared_buffers
# (change requires restart)
#wal_writer_delay = 200ms # 1-10000 milliseconds
#commit_delay = 0 # range 0-100000, in microseconds
#commit_siblings = 5 # range 1-1000
不清楚,在您看到 sig_id
返回后,table 是如何为空的。想到的唯一合理的解释:
- 您不小心检查了一个不同的table(在不同的模式或不同的数据库中)。
- 您是 运行
auto_commit = off
并且忘记了COMMIT
您的交易。COMMIT
. 之前的其他会话看不到结果
无论哪种方式,您的函数都是不必要的复杂,您不需要动态 SQL 和 EXECUTE
。由于您将未转义的文本参数连接到代码中,因此您很容易出现随机语法错误和 SQL 注入 .
您还很危险地接近参数名称 sig
和列名称 sig
之间的 命名冲突 。你用动态 SQL 抛弃了最后一颗子弹,但它仍然是一把上膛的步兵枪。阅读手册中 PL/pgSQL 的 chapter Variable Substitution 并考虑唯一名称。
最后,每行调用一个函数也是效率极低。 整个过程可以用这个单个SQL语句代替:
LOCK TABLE platform.sig2encodings IN ACCESS EXCLUSIVE MODE;
WITH sel AS (
SELECT e.sig_id, e.sig
, (s.sig IS NULL) AS insert_new
FROM platform.encode_sig e
LEFT JOIN platform.sig2encodings s USING (sig)
)
, ins AS (
INSERT INTO platform.sig2encodings (sig)
SELECT sig FROM sel WHERE insert_new
RETURNING sig_id, sig, true -- value for insert_new
)
SELECT * FROM sel WHERE NOT insert_new
UNION ALL
SELECT * FROM ins;
这会将 encode_sig
中不存在的所有 sig
插入到 sig2encodings
中。它 returns 生成的 sig_id, sig
和 insert_new = true
,附加到未插入的 encode_sig
的 sig_id, sig
和 insert_new = false
。
如果您需要一个用于单行 INSERT-or-SELECT 的函数,它可以安全地同时使用:
- Is SELECT or INSERT in a function prone to race conditions?
或者您希望 INSERT .. ON CONFLICT IGNORE
进入下一个版本以简化事情:
- PL/pgSQL column name the same as variable
更新:已经committed for 9.5. The /devel manual already has instructions。
经过长时间的尝试和错误,我们发现这是由于连接缺少 "commit" 语句造成的。 python(或者 java+postgres 驱动程序+postgres 的组合)在脚本退出时跳过 "commit" 语句,导致数据库状态不一致(序列已更新,但表没有)。所以解决方案是在 python 脚本中添加以下行:
conn.commit()