Reputation: 38003
I've got a bit of complex sql code that I'm converting from MSSql to Postgres (using Entity Framework Core 2.1), to deal with potential race conditions when inserting to a table with a unique index. Here's the dumbed-down version:
const string QUERY = @"
DO
$$
BEGIN
insert into Foo (Field1,Field2,Field3)
values (@value1,@value2,@value3);
EXCEPTION WHEN others THEN
-- do nothing; it's a race condition
END;
$$ LANGUAGE plpgsql;
select *
from Foo
where Field1 = @value1
and Field2 = @value2;
";
return DbContext.Foos
.FromSql(QUERY,
new NpgsqlParameter("value1", value1),
new NpgsqlParameter("value2", value2),
new NpgsqlParameter("value3", value3))
.First();
In other words, try to insert the record, but don't throw an exception if the attempt to insert it results in a unique index violation (index is on Field1+Field2), and return the record, whether it was created by me or another thread.
This concept worked fine in MSSql, using a TRY..CATCH
block. As far as I can tell, the way to handle Postgres exceptions is as I've done, in a plpgsql block.
BUT...
It appears that variable substitution in plpgsql blocks doesn't work. The code above fails on the .First()
(no elements in sequence), and when I comment out the EXCEPTION
line, I see the real problem, which is:
Npgsql.PostgresException : 42703: column "value1" does not exist
When I test using regular Sql, i.e. doing the insert
without using a plpgsql block, this works fine.
So, what is the correct way to do variable substitution in a plpgsql block?
Upvotes: 1
Views: 2450
Reputation: 14618
The reason this doesn't work is that the body of the DO
statement is actually a string, a text. See reference
$$
is just another way to delimit text in postgresql. It can be just as well be replaced with '
or $somestuff$
.
As it is a string, Npgsql and Postgresql have no reason to mess with @value1
in it.
Solutions? Only a very ugly one, so not using this construction, as you're not able to pass it any values. And messing with string concatenation is no different than doing concatenation in C# in the first place.
Alternatives? Yes!
You don't need to handle exceptions in plpgsql blocks. Simply insert, use the ON CONFLICT DO NOTHING
, and be on your way.
INSERT INTO Foo (Field1,Field2,Field3)
VALUES (@value1,@value2,@value3)
ON CONFLICT DO NOTHING;
select *
from Foo
where Field1 = @value1
and Field2 = @value2;
Or if you really want to keep using plpgsql, you can simply create a temporary table, using the ON COMMIT DROP
option, fill it up with these parameters as one row, then use it in the DO
statement. For that to work all your code must execute as part of one transaction. You can use one explicitly just in case.
The only ways to pass parameters to plpgsql code is via these 2 methods:
EXECUTE $$ INSERT ... VALUES ($1, $2, $3); $$ USING 3, 'text value', 5.234;
End notes:
As a fellow T-SQL developer who loved its freedom, but transitioned to Postgresql, I have to say that the BIG difference is that on one side there's T-SQL which gives the power, and on the other side it's a very powerful Postgresql-flavored SQL. plpgsql is very rarely warranted. In fact, in a code base of megabytes of complex SQL stuff, I can rewrite pretty much every plpgsql code in SQL. That's how powerful it really is compared to MSSQL-flavored SQL. It just takes some getting used to, and befriending the very ample documentation. Good luck!
Upvotes: 2