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.

PSQL `returning` statement returning Unit

See original GitHub issue

Build Environment SQLDelight version: 1.5.0 OS: Windows 10 Gradle version: 6.7.1 Kotlin version: 1.4.32 AGP Version (if applicable):

Describe the bug

I have a schema like

CREATE TABLE users 
(
    id SERIAL PRIMARY KEY,
    name VARCHAR NOT NULL
)

I wrote a query something like this


addUser:
INSERT INTO users(name) VALUES (?) RETURNING id

So the addUser fun generated by the compiler is still returning Unit instead of id of type Int

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:5
  • Comments:8 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
kevincianfarinicommented, Sep 15, 2021

@pabl0rg I think the issue is in SQLDelight, not sql-psi. From https://github.com/AlecStrong/sql-psi/pull/163

The RETURNING clauses in this file aren’t very useful, as we don’t read the output of INSERT in SqlDelight, but it is nevertheless valid PostgreSQL.

1reaction
kevincianfarinicommented, Sep 16, 2021

This is a first draft of what the generated code should look like for a mutator statement which has a RETURNING clause. There’s a couple of use cases here.

In an offline conversation with @AlecStrong we came to the conclusion that RETURNING shouldn’t expose a Query object. Queries allow the consumer to observe values as they change, which aren’t relevant to single shot mutations that return something. Instead, this will be a synchronous API which just returns raw values.

The below is in PostgreSQL dialect because that’s the first dialect we’ll support returning with. SQLite will come after.

RETURNING a single value from a single row

CREATE TABLE foo(id SERIAL PRIMARY KEY, bar VARCHAR NOT NULL)

insertWithReturn:
INSERT INTO foo(bar)
VALUES(?)
RETURNING id;
public interface FooQueries {
  
  public fun insertWithReturn(bar: kotlin.String): kotlin.Long
}

private class FooQueriesImpl(
  private val database: DatabaseImpl,
  private val driver: SqlDriver,
) : TransacterImpl(driver), FooQueries {

  private val insertWithReturn: MutableList<Query<*>> = copyOnWriteList()

  public override fun insertWithReturn(bar: kotlin.String): kotlin.Long = InsertWthReturnQuery(bar) { cursor ->
    cursor.getLong(0)!! 
  }.executeAsOne()
 
  private inner class InsertWithReturnQuery<out T : Any>(
    public val bar: kotlin.String,
    mapper: (SqlCursor) -> T,
  ) : Query<T>(insertWithReturn, mapper) {
    
    public override fun execute(): SqlCursor = driver.execute(8675309, 
      """
      |INSERT INTO foo(bar)
      |VALUES(?)
      |RETURNING id
      """.trimMargin(),
      parameters = 1,
    ) { bindString(1, bar) }

    public override fun toString(): kotlin.String = "Foo.sq:insertWithReturn"
  }
}

RETURNING multiple values from a single row

CREATE TABLE foo(id SERIAL PRIMARY KEY, bar VARCHAR NOT NULL, baz SERIAL NOT NULL)

insertWithReturn:
INSERT INTO foo(bar)
VALUES(?)
RETURNING id, baz;
public interface FooQueries {
  
  public fun insertWithReturn(bar: kotlin.String): InsertWithReturn
  public fun <T : Any> insertWithReturn(bar: kotlin.String, mapper: (id: Long, baz: Long) -> T): T
}

private class FooQueriesImpl(
  private val database: DatabaseImpl,
  private val driver: SqlDriver,
) : TransacterImpl(driver), FooQueries {

  private val insertWithReturn: MutableList<Query<*>> = copyOnWriteList()

  public override fun <T : Any> insertWithReturn(bar: kotlin.String, mapper: (id: Long, baz: Long) -> T): T {
    return InsertWithReturnQuery(bar) { cursor-> 
      mapper(id = cursor.getLong(0)!!, baz = cursor.getLong(1)!!)
    }.executeAsOne()
  }

  public override fun insertWithReturn(bar: kotlin.String): InsertWithReturn {
    return insertWithReturn(bar) { id, baz -> IssurWithReturn(id, baz) }
  }
 
  private inner class InsertWithReturnQuery<out T : Any>(
    public val bar: kotlin.String,
    mapper: (SqlCursor) -> T,
  ) : Query<T>(insertWithReturn, mapper) {
    
    public override fun execute(): SqlCursor = driver.execute(8675309, 
      """
      |INSERT INTO foo(bar)
      |VALUES(?)
      |RETURNING id, baz
      """.trimMargin(),
      parameters = 1,
    ) { bindString(1, bar) }

    public override fun toString(): kotlin.String = "Foo.sq:insertWithReturn"
  }
}

Mutating multiple rows

I think this is where things begin to breakdown when not returning some sort of Query object. We don’t try to infer what kind of data is returned from a query in other places, and instead leave that up to the consumer of the API with tools like Query.executeAsOne() and Query.executeAsList(). It’s possible we do need some type of query inteface which doesn’t allow async interaction.

CREATE TABLE foo(id SERIAL PRIMARY KEY, bar VARCHAR NOT NULL, baz SERIAL NOT NULL)

deleteAll:
DELETE FROM foo
RETURNING id;

Does the above deleteAll return a single value, or many values? It’s hard to say, and from what I understand of SQLDelight, we don’t want to be the entity making that choice.

Instead, we could potentially pull some of the interface of Query upwards. Query’s specialization allows observing, and separate implementations of AbstractQuery wouldn’t offer observation.

abstract class AbstractQuery<RowType : Any>(mapper: (SqlCursor) -> RowType) {
  abstract fun execute(): SqlCursor
  
  fun executeAsList(): List<RowType>  = ...
  fun executeAsOne(): RowType = ...
  fun executeAsOneOrNull() RowType? = ...
}

// subclasses of this have listeners
abstract class Query<RowType : Any>(
  private val queries: MutableList<Query<*>>,
  mapper: (SqlCursor) -> RowType),
) : AbstractQuery(mapper) { ... }
Read more comments on GitHub >

github_iconTop Results From Across the Web

6.4. Returning Data from Modified Rows - PostgreSQL
In an INSERT , the data available to RETURNING is the row as it was inserted. This is not so useful in trivial...
Read more >
Use PostgreSQL `RETURNING` and `WITH` to return updated ...
In the last statement the updated rows are added to the FROM clause, making their columns available for the SELECT and WHERE clauses....
Read more >
Postgres INSERT .. RETURNING clause and how this can be ...
A recent request made me think about Postgres' INSERT . ... RETURNING clause, then an additional SELECT statement has to be issued right ......
Read more >
How to make INSERT ... RETURNING statement work when ...
I am using Postgres v11. In the code I have units , which is a top level object. units have subunits in a...
Read more >
Use RETURNING Clause to Avoid Unnecessary SQL ...
The RETURNING clause allows you to retrieve values of columns (and expressions based on columns) that were modified by an insert, ...
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