<@U01SP2GJYAU> I have my JasyncDatabase implementa...
# komapper
d
@Toshihiro Nakamura I have my JasyncDatabase implementation for running Commands, but I see I need to implement another method for projections... what's the simplest way to do it (I posted the current code in the thread)?
Copy code
internal class JasyncQueryVisitor(
    val conn: SuspendingConnection,
    val config: DatabaseConfig = DryRunDatabaseConfig
) : QueryVisitor<JasyncRunner<*>> {
    override fun <T, R> templateSelectQuery(
        context: TemplateSelectContext,
        transform: (Row) -> T,
        collect: suspend (Flow<T>) -> R
    ): JasyncRunner<*> = JasyncRunner {
        val runner = TemplateSelectRunner(context)
        val stmt = runner.dryRun(config)

        val result = try {
            if (stmt.args.isEmpty())
                conn.sendQuery(stmt.sql)
            else
                conn.sendPreparedStatement(
                    stmt.sql,
                    stmt.args.map { it.any }
                )
        } catch (e: Exception) {
            println("Error running: ${context.sql} with params: ${context.valueMap}")

            throw e
        }

        val flow = result.rows.asFlow()
            .map { transform(RowDataWrapper(it)) }

        collect(flow)
    }

    override fun <T : Any, R> templateEntityProjectionSelectQuery(
        context: TemplateSelectContext,
        metamodel: EntityMetamodel<T, *, *>,
        strategy: ProjectionType,
        collect: suspend (Flow<T>) -> R
    ): JasyncRunner<*> {
        TODO("Not yet implemented")
    }

    override fun templateExecuteQuery(context: TemplateExecuteContext): JasyncRunner<Long> =
        JasyncRunner { conn.sendQuery(context.sql).rowsAffected }
templateEntityProjectionSelectQuery
needs to be implemented here...
Copy code
@JvmInline
internal value class RowDataWrapper(private val rowData: RowData) : Row {
    @Suppress("UNCHECKED_CAST")
    override fun <T : Any> get(columnLabel: String, type: KType): T? {
        return rowData[columnLabel] as? T
    }

    @Suppress("UNCHECKED_CAST")
    override fun <T : Any> get(index: Int, type: KType): T? {
        return rowData[index] as? T
    }
}
I guess I came up with this, I hope this is right @Toshihiro Nakamura :
Copy code
override fun <T : Any, R> templateEntityProjectionSelectQuery(
        context: TemplateSelectContext,
        metamodel: EntityMetamodel<T, *, *>,
        strategy: ProjectionType,
        collect: suspend (Flow<T>) -> R
    ): JasyncRunner<*> = JasyncRunner {
        val result = queryResult(context)
        
        val transform: (RowData) -> T = when (strategy) {
            ProjectionType.INDEX -> { row ->
                val props = metamodel.properties().take(row.size).mapIndexed { index, propertyMetamodel -> 
                    propertyMetamodel to row[index] 
                }
                metamodel.newEntity(props.toMap())
            }
            ProjectionType.NAME -> { row ->
                val props = metamodel.properties().mapNotNull { propertyMetamodel ->
                    propertyMetamodel to row[propertyMetamodel.columnName]
                }
                metamodel.newEntity(props.toMap())
            }
        }

        val flow = result.rows.asFlow().map { transform(it) }

        collect(flow)
    }
I wish the basic mappers (like for enums) was in core or some kind of common module... 😵‍💫
newEntity seems to just cast what it gets, and Jasync doesn't try to put it into the Enum...
This is what I did in the end, does it look ok?
Copy code
internal class JasyncQueryVisitor(
    val conn: SuspendingConnection,
    val config: DatabaseConfig = DryRunDatabaseConfig
) : QueryVisitor<JasyncRunner<*>> {
    override fun <T, R> templateSelectQuery(
        context: TemplateSelectContext,
        transform: (Row) -> T,
        collect: suspend (Flow<T>) -> R
    ): JasyncRunner<*> = JasyncRunner {
        val result = queryResult(context)

        val flow = result.rows.asFlow()
            .map { transform(RowDataWrapper(it)) }

        collect(flow)
    }

    private suspend fun queryResult(context: TemplateSelectContext): QueryResult {
        val runner = TemplateSelectRunner(context)
        val stmt = runner.dryRun(config)

        val result = try {
            if (stmt.args.isEmpty())
                conn.sendQuery(stmt.sql)
            else
                conn.sendPreparedStatement(
                    stmt.sql,
                    stmt.args.map { it.any }
                )
        } catch (e: Exception) {
            println("Error running: ${context.sql} with params: ${context.valueMap}")

            throw e
        }
        return result
    }

    override fun <T : Any, R> templateEntityProjectionSelectQuery(
        context: TemplateSelectContext,
        metamodel: EntityMetamodel<T, *, *>,
        strategy: ProjectionType,
        collect: suspend (Flow<T>) -> R
    ): JasyncRunner<*> = JasyncRunner {
        val result = queryResult(context)

        val transform: (RowData) -> T = when (strategy) {
            ProjectionType.INDEX -> { row ->
                val props = metamodel.properties().take(row.size).mapIndexed { index, propertyMetamodel ->
                    propertyMetamodel to execute(propertyMetamodel, row, index)
                }
                metamodel.newEntity(props.toMap())
            }
            ProjectionType.NAME -> { row ->
                val props = metamodel.properties().mapNotNull { propertyMetamodel ->
                    propertyMetamodel to execute(propertyMetamodel, row)
                }
                metamodel.newEntity(props.toMap())
            }
        }

        val flow = result.rows.asFlow().map { transform(it) }

        collect(flow)
    }

    fun <EXTERIOR : Any, INTERIOR : Any> execute(expression: ColumnExpression<EXTERIOR, INTERIOR>, row: RowData, index: Int): EXTERIOR? {
        return ValueExtractor.getByIndex(expression, index) {
            row[index] as INTERIOR?
        }
    }

    fun <EXTERIOR : Any, INTERIOR : Any> execute(expression: ColumnExpression<EXTERIOR, INTERIOR>, row: RowData): EXTERIOR? {
        return ValueExtractor.getByName(expression) {
            row[expression.columnName] as INTERIOR?
        }
    }
t
LGTM!