I encountered a very strange behaviour and am hoping the experts here can help me explain why the phenomenon occurs.
I have the following table and function definitions in PostgreSQL:
CREATE TABLE test_table (
"Id" text PRIMARY KEY,
"Counter" int NOT NULL
);
CREATE UNIQUE INDEX idx_test_table_id ON test_table("Id");
CREATE OR REPLACE FUNCTION public.test_func(id text)
RETURNS int
AS $$
DECLARE counter int;
BEGIN
INSERT INTO public.test_table
VALUES (id, 2)
ON CONFLICT ("Id")
DO UPDATE SET "Counter" = public.test_table."Counter" + 1
RETURNING "Counter" - 1
INTO counter;
RETURN counter;
END
$$
LANGUAGE plpgsql;
I have a test client that calls the function in a loop, asynchronously, and using the same ID.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Npgsql;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
namespace Sandbox
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public async Task TestMethod1()
{
int id = new Random().Next();
IList<Task> tasks = new List<Task>();
for (int i = 0; i < 80; i++)
{
tasks.Add(ExecutePgFunctionAsync(id.ToString()));
}
await Task.WhenAll(tasks.ToArray());
}
private async Task ExecutePgFunctionAsync(string id)
{
NpgsqlConnection conn = new NpgsqlConnection("Database=sandbox;Host=localhost;Password=runsmarter;Pooling=True;Port=12000;Timeout=15;Username=postgres;Include Error Detail=True");
await conn.OpenAsync();
using (NpgsqlCommand command = new NpgsqlCommand("test_func", conn))
{
try
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("id", id);
await command.ExecuteNonQueryAsync();
}
finally
{
await command.Connection.CloseAsync();
}
}
}
}
}
Using the above definitions, everything is fine. However, if I change the unique index to:
CREATE UNIQUE INDEX idx_test_table_id ON test_table(LOWER("Id"));
I will begin to get the following error sporadically:
Npgsql.PostgresException: 23505: duplicate key value violates unique constraint "idx_test_table_id"
If I further add LOWER()
to the conflict condition, i.e.:
ON CONFLICT (LOWER("Id"))
The error changes to:
Npgsql.PostgresException: 23505: duplicate key value violates unique constraint "test_table_pkey"
Why do these errors occur?
Addendum
I re-ran the original code almost as-is on a fresh installation of Visual Studio and PostgreSQL. I added a catch clause to ExecutePgFunctionAsync() in the hope of providing more diagnostic data.
With:
I get the following exception:
And the table is in the following state at the time of exception (please note the value of Counter will vary across runs):
With:
I get the following exception:
And the table is in the following state at the time of exception: