Estou ciente de que, no PostgreSQL, o valor de retorno de NOW()
é o registro de data e hora do início da transação . Portanto, se você usar NOW()
várias vezes na mesma transação, sempre retornará o mesmo valor.
E isso é bom no meu livro.
Mas eu tenho um pequeno problema com testes de unidade em um aplicativo cliente com isso e adoraria poder dizer ao PostgreSQL para desabilitar (temporariamente) isso, se possível.
A razão pela qual eu não quero (não posso) usar outra função timestamp como clock_timestamp()
é porque a chamada de função NOW()
fica dentro de um gatilho e, no código de produção, quero o comportamento "início da transação".
Mas em meus testes de unidade estou corrigindo a função "commit" no nível da API para não comprometer dados reais acidentalmente no banco de dados durante o teste (não se preocupe, não uso o banco de dados de produção durante o teste). Portanto, durante os testes de unidade, commit
nunca atinge o banco de dados, então não recebo novos registros de data e hora de transação.
O banco de dados usa tabelas temporais e novas entradas são anexadas apenas às tabelas de histórico se o carimbo de data/hora for alterado para garantir que consolidamos apenas uma entrada na tabela de histórico por transação.
Mas ao testar o comportamento da tabela temporal, isso agora faz com que nenhuma entrada apareça nas tabelas de histórico. O fragmento de chave do gatilho da tabela temporal é este:
new_validity_period = tstzrange(
lower(OLD.validity_period),
NOW(),
'[)'
);
IF isempty(new_validity_period) THEN
RAISE DEBUG 'New entry % will not introduce a new history item', OLD;
RETURN OLD;
END IF;
Portanto, quando eu fizer uma operação de "inserção" no meu teste de unidade e o tempo de transação for '2020-01-01 01:02:03', o período de validade dessa entrada será [2020-01-01 01:02:02,)
. Se, ainda no mesmo teste unitário, eu excluir a entrada (e para testar se ela aparece na tabela de histórico), a operação acontece no mesmo TX, e o código acima ficará assim:
new_validity_perion = tstzrange(
'2020-01-01 01:02:03', -- the lower-bound of the 'OLD' row
'2020-01-01 01:02:03', -- the result of 'NOW()'
'[)'
)
-- resulting in an empty range because the two timestamps are identical
IF isempty(new_validity_periond) THEN -- <- Resulting to TRUE
...
RETURN OLD; -- returning here, not continuing to store the history entry
END IF;
-- code below here is skipped
Existe uma maneira de configurar o PG para sempre retornar o tempo de parede ao ligar NOW()
? Os testes são executados em um contêiner docker para que eu tenha controle total sobre o server-config. Mas seria ainda melhor se eu pudesse alterar o comportamento definindo a configuração por meio de um comando SQL, para que eu pudesse alterar apenas o comportamento dos testes da tabela temporal.
Se isso não for possível, eu precisaria usar uma configuração de sessão/transação de banco de dados diferente para esses testes. Isso também é bom, mas eu queria saber se eu poderia controlar esse comportamento do PG.
Apêndice: Isolamento de transação de teste de unidade
Este é o código que uso para isolar as chamadas de confirmação no código do usuário:
@fixture
def rb_session():
"""
Returns a session which will always be rolled back.
"""
engine = create_engine(Configurations.getenv("IPBASE_DATABASE_DSN"))
# Get a *specific* connection from the pool
connection = engine.connect()
# Explicitly start a new connection on the connection we got
# This makes the normal "session.begin()" and "session.commit()"
# calls in SQLAlchemy no-ops as there is already a transaction
# in progress.
transaction = connection.begin()
# Ensure we use the "primed" connection for all our SQLAlchemy
# session needs in our unit-tests.
session = Session(bind=connection)
try:
yield session
finally:
transaction.rollback()
session.close()
connection.close()
Acho que essa é uma prática questionável. Qualquer maneira pela qual seus testes diferem do ambiente produtivo aumenta o perigo de testar a coisa errada.
De qualquer forma, você não poderia usar
now()
, mas uma função diferente, digamosmytimestamp()
. Em seu sistema de teste, a função é definida comoNo banco de dados de produção, você usa
Então você consegue o que quer. A única diferença é que
current_timestamp
(ounow()
, que é a mesma coisa) éSTABLE
, notVOLATILE
, o que pode fazer com que as consultas se comportem de forma diferente (ou não, se a função estiver embutida). Mas esse é exatamente o tipo de coisa que eu avisei no começo.