Hi Ian ,

I've modified the example towards a more specific use case. The main idea 
in the example below  is to make code related to database operations(i.e 
SELECT queries) safer and  easier to read. A  kind of 
json.Unmarshal/Marshal for databases, with validation (type checking, param 
numbers etc) to avoid a class of bugs/errors such invalid param 
types/numbers passed, invalid queries, invalid resource type to scan into 
etc. 
 
Currently the function returned by *Select*  throws the validation errors 
at runtime (i.e. invalid param type passed etc). It would be great to have 
that class of errors checked at compile type.
 
The only way I could achieve that kind of  type checking was  through code 
generation. I already built a tool to generate functions with the proper 
param types but it seems that code generation introduces a lot of friction 
to the point that I stopped using it.

My hope is that one day a Go feature (i.e. a version of Generics) could 
help the function returned by *func* *Select* be type checked at compile 
time.

https://play.golang.org/p/-Th7aHDGORL

package main

import (
"database/sql"
"errors"
"fmt"
"reflect"
)

var sqlDB *sql.DB

type Flower struct {
Color  string
Size   int
Weight int
}

type T struct{}

var FlowerByColor = Select(" * FROM tablex WHERE Color=$ LIMIT 1", 
reflect.TypeOf(Flower{}))

func main() {
// Select a flower based on its color from database

// invalid resource type; resource of type Flower is expected
       // I would like this to not compile as I pass an invalid resource 
type T instead of type Flower
var color string
if err := FlowerByColor(T{}, &color); err != nil {
fmt.Printf("err %v\n", err)
}
// invalid param; type string (Folower.Color) type is expected
var colorInvalid int
// I would like this to not compile as I pass an invalid color type
if err := FlowerByColor(Flower{}, colorInvalid); err != nil {
fmt.Printf("err %v\n", err)
}

// correct query
color = "red"
if err := FlowerByColor(Flower{}, color); err != nil {
fmt.Printf("err %v\n", err)
}
// Note: a proper SelectFunc would actually accept only pointers to Flower 
so that
// it can unmarshal data into the resource as below. For brevity I omitted 
handling
// pointers in SelectFunc
resource := new(Flower)
if err := FlowerByColor(resource, color); err != nil {
fmt.Printf("err %v\n", err)
}
// so something with the data from realVal
fmt.Printf("our  flower of color %v has a size of %v", color, realVal.Size)

}

// SelectFunc receives a resource and the query params
type SelectFunc func(resource interface{}, params ...interface{}) error

// select receives a sql query and the type that represents
//the sql table from database.
// Returns a function that executes the sql query with the matching params 
from tv.
func Select(q string, tv reflect.Type) SelectFunc {
paramTypes, err := parseQuery(q, tv)
if err != nil {
panic("invalid query")
}
return  func(resource interface{}, param ...interface{}) error {
// validate input
// resource must match the resource type
if reflect.TypeOf(resource) != tv {
return errors.New("invalid resource type")
}
if len(param) != len(paramTypes) {
return errors.New("invalid number of params passed")
}
for k, v := range param {
if reflect.TypeOf(v) != paramTypes[k] {
return errors.New("invalid argv type passed")
}
}
// do a select database query
resourceFields := fieldsFromResource(reflect.ValueOf(resource))
if err := sqlDB.QueryRow("SELECT "+q, param...).Scan(resourceFields...); 
err != nil {
return err
}
return nil
}
        
 
}

// parseQuery parses query and the resource t.
// returns the types selected in the query
func parseQuery(query string, t reflect.Type) ([]reflect.Type, error) {
// skip parsing for brevity
return []reflect.Type{t.Field(0).Type}, nil

}

func fieldsFromResource(v reflect.Value) []interface{} {
// skip type fields looping for brevity
return []interface{}{
v.Field(0).Addr().Interface(),
v.Field(1).Addr().Interface(),
v.Field(2).Addr().Interface(),
}
}


On Wednesday, October 6, 2021 at 2:14:20 AM UTC+3 Ian Lance Taylor wrote:

> On Sun, Oct 3, 2021 at 8:41 AM mi...@ubo.ro <mi...@ubo.ro> wrote:
> >
> > I have developed a library that depends very much on reflect package. It 
> caches a specific type and return a function that encodes(does something 
> with) with that kind /type of data. Think of defining database schema using 
> types and generating functions to validate/update/insert data.
> >
> > I reduced it to a basic example below. Can the generics feature from Go 
> help me make it any safer? Ideally I would like to make the program below 
> not to compile due the errors of invalid params passed to PrintT instead to 
> throw dynamically at runtime.
>
> I don't understand what your program is trying to do, but the current
> generics proposal does not support variadic generic parameters, so I
> don't think it's going to help you.
>
> Ian
>

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/28bc1402-c789-43ca-ae24-791ca93d541bn%40googlegroups.com.

Reply via email to