Почему session.merge выбрасывает IntegrityError, когда строка содержит значение NULL, если заданы значения по умолчанию и server_default - PullRequest
1 голос
/ 25 октября 2019

Я пытаюсь обновить запись с помощью session.merge(object), когда поле установлено как необнуляемое с sqlalchemy и сервером по умолчанию. Выдается сообщение об ошибке, что ненулевое ограничение было нарушено при попытке обновить существующую запись. Я смог обойти это, отбросив нулевые значения из записей в таблице инициализации, но это кажется странным. Это ожидать или я не понимаю, как работает сервер по умолчанию или session.merge работает?

Версия Python: 3.6.8

Версия sqlalchemy: 1.3.4

Примеркод:

import sqlalchemy as sa
from sqlalchemy.ext.declarative import declarative_base 

Base = declarative_base()       

class TestTable(Base):

    __tablename__ = 'test_table'

    id = sa.Column(sa.Integer, primary_key=True)
    field = sa.Column(sa.String, default='Unknown', server_default=sa.text("'Unknown'"), nullable=False)
    field2 = sa.Column(sa.Integer)

engine = sa.create_engine('postgresql://postgres@localhost/test')

Base.metadata.create_all(bind=engine)

session = sa.orm.sessionmaker(engine)() 

record = {'id': 1, 'field': None, 'field2': 3}
session.merge(TestTable(**record))

session.commit()

session.merge(TestTable(**record))

session.commit()

выдана ошибка:

IntegrityError: (psycopg2.errors.NotNullViolation) null value in column "field" violates not-null constraint
DETAIL:  Failing row contains (1, null, 3).

[SQL: UPDATE test_table SET field=%(field)s WHERE test_table.id = %(test_table_id)s]
[parameters: {'field': None, 'test_table_id': 1}]
(Background on this error at: http://sqlalche.me/e/gkpj)

Ответы [ 2 ]

1 голос
/ 26 октября 2019

Во время первого слияния строка не существует, поэтому она создается:

record = {'id': 1, 'field': None, 'field2': 3}
session.merge(TestTable(**record))

... испускается:

INSERT INTO test_table (id, field, field2) VALUES (%(id)s, %(field)s, %(field2)s)
{'id': 1, 'field': 'Unknown', 'field2': 3}

Per документация , параметр default для Column применяется к insert , поэтому вы можете видеть, что None был заменен на 'Unknown' в наборе параметров, отправленном вместе свставить заявление. Состояние документа:

Скалярное выражение, вызываемое по Python или ColumnElement выражение, представляющее значение по умолчанию для этого столбца, которое будет вызываться при вставке, если этот столбец в противном случае не указан в предложении VALUESвставка.

... поэтому в этом случае кажется, что «не указано» следует интерпретировать как «не None», поскольку вы явно «указали» значение в вашем record дикт. Относительно этого является то, что когда вы создаете экземпляр без предоставления каких-либо значений для каких-либо атрибутов, эти атрибуты уже неявно содержат значение None:

print(TestTable().field is None)  # True

Если вы удалите default=... kwarg из вашего столбцаопределение, даже если вы предоставляете field=None в свой конструктор TestTable, со значением по умолчанию, удаленным, значение для этого параметра вообще не отправляется в базу данных:

INSERT INTO test_table (id, field2) VALUES (%(id)s, %(field2)s)
{'id': 1, 'field2': 3}

В этом случаепо умолчанию на стороне сервера будет срабатывать. Поэтому перед сбросом оператора вставки SQLAlchemy будет заменять любые None значения столбцов, для которых на стороне клиента по умолчанию задано значение.

При втором слиянии строка уже существует в базе данных, поэтомузначения объединенного экземпляра заполняются в экземпляре, который представляет строку, которая уже существует в базе данных. Во время слияния значение столбца field в этом экземпляре изменяется с 'Unknown' на None. Это изменение значения означает, что новое значение этого поля будет отправлено в базу данных как часть запроса update . Обратите внимание, что ни операторы по умолчанию на стороне клиента, ни на стороне сервера не применяются к оператору обновления.

UPDATE test_table SET field=%(field)s WHERE test_table.id = %(test_table_id)s
{'field': None, 'test_table_id': 1}

Поскольку мы пытаемся явно установить для столбца NOT NULL значение NULL, значение IntegrityError увеличивается.

0 голосов
/ 25 октября 2019

Я не очень знаком с sqlalchemy, но мне кажется, проблема в том, что вы неправильно поняли функцию значений по умолчанию в определениях таблиц. Указанное значение по умолчанию не является заменой нуля. Значение по умолчанию подставляется при вставке, когда столбец отсутствует в списке значений, или при вставке / обновлении, когда используется ключевое слово DEFAULT. В качестве примера попробуйте следующее в psql (извините, я не могу преобразовать в sqlalchemy):

 create table txx (id serial, col1 text, col2 text default 'xxx'); 
 insert into txx(col1) values ('abcd');
 insert into txx(col1,col2) values ('defg',null), ('hijk', DEFAULT);
 select *  from txx;

 update txx set col2 = DEFAULT where col2 is null;
 select *  from txx;

С другой стороны, всегда применяется ограничение NOT NULL. Дальнейшее ограничение NON NULL и значение DEFAULT полностью независимы друг от друга. Надеюсь это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...