2

I am looking for a way to store information which struct a function should use. Each struct corresponds to certain database table.

type Record struct {
   TableName string
   PrimaryKey string
   //XormStruct // how can I store User or Post here?
   XormStruct2 interface{} // see I have tried below
   XormStruct3 reflect.Type // see I have tried below
}

var posts []Post

var ListOfTables [...]Record {
   {"User", "id", User},
   //{"Post", "post_id", Post},
   {"Post", "post_id", posts, reflect.TypeOf([]Post{})},
}

// User is xorm struct
type User struct {
   Id int64
   Name string
}

// Post is xorm struct
type Post struct {
   Post_id int64
   Name string
   Other string
}

I want to be able to dynamically choose struct for table.

for _, rec := range ListOfTables {
    //var entries []rec.XormStruct // this does not work, of course
    //var entries []reflect.TypeOf(rec.XormStruct) // this does not work either
    // xorm is *xorm.Engine
    xorm.Find(&entries)
    //xorm.Find(&rec.XormStruct2) //with interface{}, does not work - produces an empty &[]
    posts3 := reflect.New(rec.XormStruct3).Interface()
    //xorm.Find(&posts3) // same as above, produces empty &[]
    var posts []Post
    xorm.Find(&posts) // produces desired data

    // afterwards I will do same to any table entries, e.g.
    xml.Marshal(entries)
    // so there is really no need to create identical functions for each table
}

Goal DRY (I have about 30 tables, function is the same)

I have tried:

  • to use reflect.TypeOf(), but I do not understand if/how I can use it (reflect.Type) to define a new variable

  • Define Record with XormStruct interface{} and for each ListOfTables entry create a slice e.g. var posts []Post and {"Post", "post_id", posts},

  • Searching SO and godocs

It seems to me, that xorm.Find() is not "happy" about getting an interface{} instead of []Posts even if it does not say so.

UPDATE: I believe the breaking difference is this:

spew.Dump(posts3) //posts3 := reflect.New(rec.XormStruct3).Interface()
// (*[]main.Post)<0x2312312>(<nil>)
spew.Dump(posts) //var posts []Post
// ([]main.Post)<nil>

SOLUTION

posts3 := reflect.New(rec.XormStruct3).Interface()
xorm.Find(posts3) // not &posts3
icza
  • 389,944
  • 63
  • 907
  • 827
liepumartins
  • 464
  • 2
  • 6
  • 21

1 Answers1

0

You may use reflect.Type to represent / describe a Go type. And at runtime, you may use reflect.New() to obtain a pointer to the zeroed value of this type wrapped in a reflect.Value. And if you need a slice instead of a single value, you may use reflect.SliceOf(), or obtain the type descriptor of the slice value in the first place.

If you store refect.Type values of your tables, this is how you can use it:

type Record struct {
   TableName  string
   PrimaryKey string
   XormStruct reflect.Type
}

var ListOfTables [...]Record {
   {"User", "id", reflect.TypeOf((*User)(nil)).Elem()},
   {"Post", "post_id", reflect.TypeOf((*Post)(nil)).Elem()},
}

// User is xorm struct
type User struct {
   Id   int64
   Name string
}

// Post is xorm struct
type Post struct {
   Post_id int64
   Name    string
   Other   string
}

Note you must use exported fields!

And then handling the tables:

for _, rec := range ListOfTables {
    entries := reflect.New(reflect.SliceOf(t.XormStruct)).Interface()
    err := xorm.Find(entries)
    // Handle error

    err := xml.Marshal(entries)
    // Handle error
}

You can see a working example (proof of concept) of this (without xorm as that is not available on the Go Playground) using JSON: Go Playground.

If you were to store reflect.Type values of slices in the first place:

var ListOfTables [...]Record {
   {"User", "id", reflect.TypeOf([]User{})},
   {"Post", "post_id", reflect.TypeOf([]Post{})},
}

And using it is also simpler:

for _, rec := range ListOfTables {
    entries := reflect.New(t.XormStruct).Interface()
    err := xorm.Find(entries)
    // Handle error

    err := xml.Marshal(entries)
    // Handle error
}

See proof of concept of this: Go Playground.

Note that if Record holds slice types (in the field XormStruct), should you ever need to access the type of the struct (the element type of the struct), you may use Type.Elem() for that.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Either of these approaches does not produce any errors, however result it is the same as my attempt to store `XormStruct interface{}`. `xorm.Find(&entries)` return an empty `&[]` Maybe the problem lies with xorm itself? – liepumartins Oct 24 '18 at 10:38
  • @liepumartins Make sure your mapping is correct. For example as I noted in the answer, you cannot use unexported fields (you must start field names with uppercase letters). – icza Oct 24 '18 at 10:41
  • I do have exported fields, sorry example was incorrect on that part. I updated question to illustrate what I mean with my previous comment. – liepumartins Oct 24 '18 at 10:49
  • Please post the exact code that uses my solution and does not work for you. The code you included in the question is a "hybrid" of my and your solution, and wouldn't even compile. Also show if and how you handle errors. – icza Oct 24 '18 at 11:09
  • Problem was `xorm.Find(&entries)` should be `xorm.Find(entries)` if your solution is used, thanks! – liepumartins Oct 24 '18 at 11:15
  • @liepumartins Yup, sorry about that. My working playground links used `entries`, but left the `&` sign in them when merged my playground solution with yours. Yes, it must be simply `entries` as that is already a pointer. – icza Oct 24 '18 at 11:19