UDF для кодирования строк работает в psql и Perl, но не в 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;

Таблица:

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 вставляет данные в таблицу:

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 пуста. Что происходит?

Обновление:

Работает следующий perl-скрипт (со всеми выводами консоли (УВЕДОМЛЕНИЯМИ) и строками в таблице):

#!/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

person arthur    schedule 13.04.2015    source источник
comment
Пожалуйста, покажите нам вывод вашей программы на Python. Какие учетные данные он использует для подключения к базе данных? Наконец, почему вы пишете свой код таким образом, чтобы он был подвержен атаке путем внедрения SQL-кода?   -  person Colin 't Hart    schedule 14.04.2015
comment
А почему ты запираешь стол? Ваши первичный и уникальный ключи должны обеспечивать более чем достаточную защиту от одновременной вставки конфликтующих значений.   -  person Colin 't Hart    schedule 14.04.2015
comment
Вы должны использовать select platform.encode_sig('" + sig + "'); для скалярных функций. select * from ... предназначен для функций, возвращающих набор результатов (т.е. несколько строк)   -  person a_horse_with_no_name    schedule 14.04.2015
comment
Какой NOTICE вы видите? «У меня есть кодировка» или «у меня нет кодировки»?   -  person Erwin Brandstetter    schedule 15.04.2015
comment
в скрипте python я не вижу уведомлений   -  person arthur    schedule 22.04.2015
comment
мы также протестировали функциональность java. Без оператора фиксации строки исчезли. Возможно, python не выполняет фиксацию после оператора select, что приводит к удалению строк. Почему последовательность обновляется, еще предстоит выяснить   -  person arthur    schedule 28.04.2015


Ответы (2)


Непонятно, как таблица может быть пустой после того, как вы видите sig_id. Единственные правдоподобные объяснения, которые приходят на ум:

  • Вы случайно проверяете другую таблицу (в другой схеме или другой БД).
  • Вы работаете с auto_commit = off и забыли COMMIT свою транзакцию. Результаты не видны другим сеансам до COMMIT.

В любом случае, ваша функция излишне запутана, вам не нужен динамический SQL с EXECUTE. Поскольку вы объединяете неэкранированные текстовые параметры в код, вы сильно подвержены случайным синтаксическим ошибкам и инъекциям SQL.
Вы также опасно близки к конфликтам имен между параметрами. имя sig и имя столбца sig. Вы избавляетесь от этой последней пули с помощью своего динамического SQL, но это все еще заряженное оружие. Прочтите главу Подстановка переменных для PL/pgSQL в руководстве и учитывайте уникальные имена.

Наконец, также крайне неэффективно иметь один вызов функции на строку. Всю процедуру можно заменить этим одним оператором 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;

Это вставит все sig из encode_sig в sig2encodings, которых там еще не было. Он возвращает результирующие sig_id, sig и insert_new = true, добавленные к sig_id, sig и insert_new = false из encode_sig, которые не были вставлены.

Если вам нужна функция для одной строки INSERT-or-SELECT, которая безопасна для одновременного использования:

Или вы надеетесь, что INSERT .. ON CONFLICT IGNORE войдет в следующий релиз, чтобы все упростить:

Обновление: оно было для версии 9.5. В /руководстве по разработке уже есть инструкции.

person Erwin Brandstetter    schedule 15.04.2015
comment
UDF должен быть интерфейсом для генерации строк для идентификаторов, который предназначен для выполнения из разных сред, включая java, perl, python и C++. Вызов UDF выглядел с точки зрения кодирования лучше в коде JAVA и Python, по сравнению с довольно длинным SQL-запросом... Комментарии? - person arthur; 22.04.2015

После долгих проб и ошибок мы выяснили, что это произошло из-за отсутствия оператора "commit" для соединения. python (и, альтернативно, комбинация драйверов java + postgres + postgres) пропускала оператор «commit» при выходе из скрипта, что приводило к несогласованному состоянию БД (последовательность была обновлена, а таблицы — нет). Таким образом, решение состоит в том, чтобы добавить следующую строку в скрипт Python:

  conn.commit()
person arthur    schedule 12.05.2015