jet icon indicating copy to clipboard operation
jet copied to clipboard

Use alias tag to skip destination field during scan

Open gnz00 opened this issue 3 years ago • 8 comments
trafficstars

Hey y'all, I was wondering if it is possible to skip certain fields while scanning rows into a destination.

I think there could be two potential solutions that could work together:

type Model struct {
  ID string              `sql:"primary_key" alias:"model.id"`
  Complex *ComplexStruct `alias:"-"` // skip this one
}

Or alternatively as a method on the QRM that only scans fields tagged with alias or sql

stmt.QueryWithTag(db, &rows, map[string]string{ "alias": "json", "sql": "sql" }) // override alias to json
stmt.QueryWithTagOption(db, &rows, Tags.DEFAULT, TagOptions.PERMISSIVE) // default behavior
stmt.QueryWithTagOption(db, &rows, Tags.DEFAULT, TagOptions.STRICT) // only 'alias' or 'sql'

If there isn't a way currently I can take a crack at it.

gnz00 avatar Jul 19 '22 23:07 gnz00

Hi @gnz00 . The first solution is already implemented - wiki. If you set alias:"-" for a field, that field will be ignored from the scan. It doesn't have to be "-" it can be any string, but it has to be different from sql query projection alias.

go-jet avatar Jul 20 '22 10:07 go-jet

Edit: I just realized I was embedding the struct! Nevermind!

gnz00 avatar Jul 21 '22 03:07 gnz00

@go-jet Actually, I may have closed this prematurely.

Here is a full example showing a panic:

package main

import (
	"database/sql"
	"fmt"
	"net/http"

	mysql "github.com/go-jet/jet/v2/mysql"
	_ "github.com/go-sql-driver/mysql"
)

type TestObject struct {
	Header http.Header `alias:"-"`
	ID     string      `sql:"primary_key" alias:"id"`
}

func main() {
	db, err := sql.Open("mysql", "root:pass@/mysql")
	if err != nil {
		panic(err)
	}

	var rows []*TestObject
	table := mysql.NewTable("mysql", "user", "", mysql.StringColumn("id"))
	mysql.
		SELECT(
			mysql.RawString("12345").AS("test_object.id"),
		).
		FROM(table).
		Query(db, &rows)

	for _, row := range rows {
		fmt.Printf("%v\n", row)
	}
}
❯ go run main.go
panic: jet: unsupported dest type: Header http.Header

goroutine 1 [running]:
github.com/go-jet/jet/v2/qrm.mapRowToDestinationPtr(0x1024920a0?, {0x140001345c0, 0x31}, {0x10248f260?, 0x1400012c390?, 0x1?}, 0x140001b5730)
	/Users/gnz00/go/pkg/mod/github.com/go-jet/jet/[email protected]/qrm/qrm.go:336 +0x13c
github.com/go-jet/jet/v2/qrm.mapRowToDestinationValue(0x102474940?, {0x140001345c0, 0x31}, {0x1024920a0?, 0x1400012c390?, 0x102380afc?}, 0x14000132420?)
	/Users/gnz00/go/pkg/mod/github.com/go-jet/jet/[email protected]/qrm/qrm.go:308 +0x114
github.com/go-jet/jet/v2/qrm.mapRowToStruct(0x14000132420, {0x140001345c0, 0x31}, {0x10244d4a0?, 0x1400012c390?, 0x1400011fd40?}, 0x14000195938?, {0x0?, 0x0, 0x1400011b580?})
	/Users/gnz00/go/pkg/mod/github.com/go-jet/jet/[email protected]/qrm/qrm.go:241 +0x450
github.com/go-jet/jet/v2/qrm.mapRowToSlice(0x14000132420, {0x0, 0x0}, {0x102449b60?, 0x1400012c348?, 0x0?}, 0x0)
	/Users/gnz00/go/pkg/mod/github.com/go-jet/jet/[email protected]/qrm/qrm.go:168 +0x2d4
github.com/go-jet/jet/v2/qrm.queryToSlice({0x1024a5c20?, 0x14000124000?}, {0x1024a4228?, 0x14000121790?}, {0x14000134540?, 0x14000134540?}, {0x0?, 0x0?, 0x14000195b68?}, {0x102449b60?, ...})
	/Users/gnz00/go/pkg/mod/github.com/go-jet/jet/[email protected]/qrm/qrm.go:124 +0x26c
github.com/go-jet/jet/v2/qrm.Query({0x1024a5c20, 0x14000124000}, {0x1024a4228?, 0x14000121790?}, {0x14000134540, 0x35}, {0x0, 0x0, 0x0}, {0x102449b60?, ...})
	/Users/gnz00/go/pkg/mod/github.com/go-jet/jet/[email protected]/qrm/qrm.go:29 +0x130
github.com/go-jet/jet/v2/internal/jet.(*serializerStatementInterfaceImpl).QueryContext.func1()
	/Users/gnz00/go/pkg/mod/github.com/go-jet/jet/[email protected]/internal/jet/statement.go:102 +0x5c
github.com/go-jet/jet/v2/internal/jet.duration(0x14000195d88)
	/Users/gnz00/go/pkg/mod/github.com/go-jet/jet/[email protected]/internal/jet/statement.go:181 +0x3c
github.com/go-jet/jet/v2/internal/jet.(*serializerStatementInterfaceImpl).QueryContext(0x14000132310, {0x1024a5c20, 0x14000124000}, {0x1024a4228, 0x14000121790}, {0x102449b60, 0x1400012c348})
	/Users/gnz00/go/pkg/mod/github.com/go-jet/jet/[email protected]/internal/jet/statement.go:101 +0x144
github.com/go-jet/jet/v2/internal/jet.(*serializerStatementInterfaceImpl).Query(0x0?, {0x1024a4228?, 0x14000121790?}, {0x102449b60?, 0x1400012c348?})
	/Users/gnz00/go/pkg/mod/github.com/go-jet/jet/[email protected]/internal/jet/statement.go:90 +0x58
main.main()
exit status 2

gnz00 avatar Jul 21 '22 03:07 gnz00

http.Header is a map type. Scan currently supports only base types, structs, arrays and types that implements Scanner interface.
Also, I don't think http.Header is intended to be used directly as part of the response body. Header values are usually just added to ResponseWriter - https://pkg.go.dev/net/http#ResponseWriter

go-jet avatar Jul 21 '22 09:07 go-jet

It’s a contrived example but regardless of the type shouldn’t it be skipped with a tag of alias:”-“. I don’t think the expected behavior as you’ve described is a panic.

gnz00 avatar Jul 22 '22 09:07 gnz00

Setting alias to "-", it doesn't mean that field should be skipped. It just means those type fields should expect projection in form "-.". If there are no such projection then the whole complex field is skipped.
Now, regarding the destination with a map. I guess we could just ignore the map, without panic.

go-jet avatar Jul 23 '22 10:07 go-jet

I am suggesting that there be a token value that will explicitly opt out a field out of projection, as currently I am having to scan and then wrap to avoid panics on embedded 3rd-party structs. Alternatively, if there is no such projection for -, I imagine it should also avoid trying to map it (and evaluating the type, leading to a panic or error otherwise).

gnz00 avatar Jul 25 '22 19:07 gnz00

You don't have to scan into whole complex destination. For instance:

type Response struct {
     Header      map[string]string
     ThirdParty  ThirdParty
     Data         struct {
         model.Actor
         Films []model.Film
     }
}

Only Data field is set by database query, so only Data field needs to be used as a QueryContext destination. Thus effectively ignoring other fields (Header and ThirdParty).

var response Response

err := stmt.QueryContext(ctx, db, &response.Data)

go-jet avatar Jul 27 '22 08:07 go-jet