question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Postgres. "Update from source" returns error "Query can't be translated to UPDATE Statement." in some cases

See original GitHub issue

As far as Postgres is concerned, there is some strange case when two queries that are logically exactly the same and processed successfully by Postgres with the same results but in one of the cases, the part of Linq2Db called “LinqToDB.SqlProvider.BasicSqlOptimizer.GetAlternativeUpdatePostgreSqlite” (which I assume by its name somehow specialized in working with Postgres) fails with the error:

LinqToDB.LinqToDBException : Query can't be translated to UPDATE Statement.
   at LinqToDB.SqlProvider.BasicSqlOptimizer.GetAlternativeUpdatePostgreSqlite(SqlUpdateStatement statement)
   at LinqToDB.DataProvider.PostgreSQL.PostgreSQLSqlOptimizer.TransformStatement(SqlStatement statement)
   at LinqToDB.SqlProvider.BasicSqlOptimizer.FinalizeStatement(SqlStatement statement, EvaluationContext context)
   at LinqToDB.SqlProvider.BasicSqlOptimizer.Finalize(SqlStatement statement)
   at LinqToDB.DataProvider.PostgreSQL.PostgreSQLSqlOptimizer.Finalize(SqlStatement statement)
   at LinqToDB.Linq.QueryRunner.FinalizeQuery(Query query)
   at LinqToDB.Linq.QueryRunner.SetNonQueryQuery(Query query)
   at LinqToDB.Linq.Builder.UpdateBuilder.UpdateContext.BuildQuery[T](Query`1 query, ParameterExpression queryParameter)
   at LinqToDB.Linq.Builder.ExpressionBuilder.Build[T]()
   at LinqToDB.Linq.Query`1.CreateQuery(ExpressionTreeOptimizationContext optimizationContext, ParametersContext parametersContext, IDataContext dataContext, Expression expr)
   at LinqToDB.Linq.Query`1.GetQuery(IDataContext dataContext, Expression& expr, Boolean& dependsOnParameters)
   at LinqToDB.Linq.ExpressionQuery`1.GetQuery(Expression& expression, Boolean cache, Boolean& dependsOnParameters)
   at LinqToDB.Linq.ExpressionQuery`1.LinqToDB.Async.IQueryProviderAsync.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at LinqToDB.LinqExtensions.UpdateAsync[TSource,TTarget](IQueryable`1 source, ITable`1 target, Expression`1 setter, CancellationToken token)

I prepared the reproducing example (both successful and failed): Database definition:

create table if not exists totals
(
    id    serial
        constraint totals_pk
            primary key,
    sum   integer     not null,
    label varchar(10) not null
);

create table if not exists entry
(
    int      serial,
    total_id integer not null,
    sum      integer not null
);

C# tests:

[Test]
public async Task Test_Successful()
{
        await 
        dataConnection.Totals.InnerJoin(
            dataConnection.Entries
                .GroupBy(e => e.TotalId, (totalId, en) => new { TotalId = totalId, SumAggr = en.Sum(i => i.Sum) }),
            (t, eg) => t.Id == eg.TotalId, (t, eg) => new { OldSum = t.Sum,  eg.SumAggr, t.Label }
        )
            .Where(r => r.Label == "spendings")
            .UpdateAsync(dataConnection.Totals, g => 
                new Total
                {
                    Sum = g.OldSum + g.SumAggr
                });

        var lastQuery = dataConnection.LastQuery;
        
        TestContext.WriteLine(lastQuery);         
}

[Test]
public async Task Test_Failed()
{
            await
            dataConnection.Entries
                .GroupBy(e => e.TotalId, (totalId, en) => new { TotalId = totalId, SumAggr = en.Sum(i => i.Sum) })
                .InnerJoin(dataConnection.Totals, (eg, t) =>
                    t.Id == eg.TotalId, (eg, t) => new {  OldSum = t.Sum, eg.SumAggr, t.Label })
                .Where(r => r.Label == "spendings")
                .UpdateAsync(dataConnection.Totals, g =>
                    new Total
                    {
                        Sum = g.OldSum + g.SumAggr
                    });

        });
}

As you can see above the only thing that changed in Test_Failed is the order of inner join participants. Both queries are run successfully in Postgres. Below I have provided them as well:

Query 1:

UPDATE
	"public".totals
SET
	"sum" = r."sum" + eg."SumAggr"
FROM
	"public".totals r
		INNER JOIN (
			SELECT
				t1.total_id as "TotalId",
				Sum(t1."sum") as "SumAggr"
			FROM
				"public".entry t1
			GROUP BY
				t1.total_id
		) eg ON r.id = eg."TotalId"
WHERE
	r.label = 'spendings' AND "public".totals.id = r.id

Query 2:

UPDATE
	"public".totals
SET
	"sum" = r."sum" + eg."SumAggr"
FROM
(
			SELECT
				t1.total_id as "TotalId",
				Sum(t1."sum") as "SumAggr"
			FROM
				"public".entry t1
			GROUP BY
				t1.total_id
) eg
INNER JOIN 	"public".totals r ON r.id = eg."TotalId"
WHERE
	r.label = 'spendings' AND "public".totals.id = r.id

Environment details

linq2db version: 3.7 Database Server: Postgresql 12 Database Provider: Npgsql

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
sdanylivcommented, Jul 6, 2022

Your version will simplify things a lot, I know. Will check maybe it do not needs too much efforts.

1reaction
sdanylivcommented, Jul 6, 2022

Well, translating UPDATE FROM for PostgreSQL is a kind of magic. Table to update is placed by the Select Query in JOIN operator and we are trying to find a way how to move it to the to the top of the query. In simple words - make it effective and with desired result. Will check, probably I can fix such case.

Read more comments on GitHub >

github_iconTop Results From Across the Web

PostgreSQL update not working as expected
The from / update in Postgres works a bit differently from SQL Server/MS Access. This should do what you want: UPDATE chgman.tc_data d...
Read more >
PostgreSQL UPDATE doesn't seem to update some rows
If you don't see results of a seemingly sound data-modifying query, the first question to ask is: Did you commit your transaction?
Read more >
Documentation: 15: COPY
For INSERT , UPDATE and DELETE queries a RETURNING clause must be provided, ... to pass any arguments to shell command that come...
Read more >
Documentation: 15: 43.5. Basic Statements
If the command does return rows (for example SELECT , or INSERT / UPDATE / DELETE ... write the command as the data...
Read more >
Documentation: 15: 43.11. PL/pgSQL under the Hood
By default, PL/pgSQL will report an error if a name in an SQL statement could refer to either a variable or a table...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found