Go, gorm, nested JSON and associations

Go, gorm, nested JSON and associations

gorm provides a clean way to store a nested JSON with the relation of associations among tables. The following code creates a DB with three levels of nesting.

package main

import (
	"encoding/json"
	"fmt"

	"github.com/jinzhu/gorm"
	_ "github.com/lib/pq"
)

const (
	host     = "localhost"
	port     = 5432
	user     = "postgres"
	password = ""
	dbname   = "postgres"
)

type Page struct {
	ID     int64  `sql:"auto_increment" json:"-"`
	Number int64  `json:"number"`
	Book   Book   `gorm:"foreignkey:book_id" json:"-"`
	BookID int64  `json:"book_id"`
	Text   string `json:"text"`
}

type Book struct {
	ID          int64  `sql:"auto_increment" json:"-"`
	ShelfPlace  int64  `json:"shelf_place"`
	Shelf       Shelf  `gorm:"foreignkey:shelf_id" json:"-"`
	ShelfID     int64  `json:"shelf_id"`
	Author      string `json:"author" gorm:"unique;not null"`
	Publisher   string `json:"publisher"`
	PagesAmount int64  `json:"pages_amount"`
	Pages       []Page `json:"pages"`
}

type Shelf struct {
	ID          int64  `sql:"auto_increment" json:"-"`
	Number      int64  `json:"number"`
	BooksAmount int64  `json:"books_amount"`
	Book        []Book `json:"books"`
}

func main() {
	psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
		"password=%s dbname=%s sslmode=disable",
		host, port, user, password, dbname)

	db, err := gorm.Open("postgres", psqlInfo)
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// Create
	//db.Create(&Shelf{
	record := `{
			"number": 1,
			"books": [
			  {
				"shelf_place": 5,
				"author": "Lewis Carroll",
				"publisher": "EA",
				"pages_amount": 2,
				"pages": [
				  {
					"number": 2,
					"text": "lorem ipsum"
				  },
				  {
					"number": 4,
					"text": "dolor sit amet"
				  }
				]
			  },
			  {
				"shelf_place": 7,
				"author": "Mark Twain",
				"publisher": "Activision",
				"pages_amount": 3,
				"pages": [
				  {
					"number": 1,
					"text": "this is"
				  },
				  {
					"number": 3,
					"text": "a test"
				  },
				  {
					"number": 6,
					"text": "of json"
				  }
				]
			  }
			]
		  }`
	var shelf Shelf

	err = json.Unmarshal([]byte(record), &shelf)
	fmt.Printf("err=%v\n", err)

	db.DropTableIfExists(&Shelf{})
	db.DropTableIfExists(&Page{})
	db.DropTableIfExists(&Book{})

	// Migrate the schema
	db.AutoMigrate(&Shelf{})
	db.AutoMigrate(&Page{})
	db.AutoMigrate(&Book{})

	db.Create(&shelf)
	
	// Fails because author is a unique attribute
	//db.Create(&shelf)

    // Preload is necessary to query nested structure.
	db.Preload("Book").Where("author = ?", "Mark Twain").Find(&shelf)
	fmt.Printf("shelf=%v", shelf)
}

References

Written with StackEdit.


Interface Quirks in Golang

An interface defines a set of methods. A struct type implements these methods and qualifies the object type as an interface type.

However, how the struct implements the interface?

There are two ways:

  • As a pointer handler
  • As a value handler

Implementation with a struct value handler

package main

import (
	"fmt"
)

type Dummy interface {
  Add(int, int)int
}

type Adder struct {
  magic int
}

func (s Adder) Add(a, b int) int {
  fmt.Printf("magic:%d\n", s.magic)

  s.magic = a + b + s.magic
  return s.magic
}

func GenericAdder(object Dummy) {
    object.Add(2,3)
}

func main() {
  nums := Adder{magic: 43}
  nums.Add(2,3)
  GenericAdder(nums)
}

Output

magic:43
magic:43

Implementation with a pointer handler

package main

import (
	"fmt"
)

type Dummy interface {
  Add(int, int)int
}

type Adder struct {
  magic int
}

func (s *Adder) Add(a, b int) int {
  fmt.Printf("magic:%d\n", s.magic)

  s.magic = a + b + s.magic
  return s.magic
}

func GenericAdder(object Dummy) {
    object.Add(2,3)
}

func main() {
  nums := &Adder{magic: 43}
  nums.Add(2,3)
  GenericAdder(nums)
}

Output

magic:43
magic:48

Conclusion

  • A pointer handler is useful if the struct variable is updated by multiple handlers and the variable state needs to persist.
  • When the method is called with a value of struct, Go uses a copy of the struct variable.
  • The golang function signature stays the same for interface argument. It does not care for the passed argument type (pointer to struct or value of struct)

References

Written with StackEdit.



go-kit: Sample Code for Function to Log

go-kit: Sample Code for Function to Log

func getLogger() log.Logger {
    var logger log.Logger
    logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
    logger = log.With(logger, "instance_id", 123)
    return logger
}

The above function is easily usable in a go-kit project to log JSON style data.

func myfun() {
    mylogger := getLogger()
    mylogger.Log("a key", "a value")
}

Reference

Written with StackEdit.


How to Find GeoIP from an IP?

There are two components of getting detailed info about an IP.

  • A GeoIP database
  • A library to look up the DB

These DBs are not free and have a proprietary format. So a library is required to perform lookups.
One such format is MaxMind and used by GeoIP2 & GeoLite2 DBs.

More @ https://github.com/oschwald/maxminddb-golang

A test program to get detailed IP information

$ cat mytest.go                                                                                                                                          [18:46:01]
//package maxminddb_test
package main

import (
        "fmt"
        "log"
        "net"

        "github.com/oschwald/maxminddb-golang"
)

// This example shows how to decode to a struct
func ExampleReader_Lookup_struct() {
        db, err := maxminddb.Open("./tests/static/GeoIP2-City.mmdb")
        if err != nil {
                log.Fatal(err)
        }
        defer db.Close()

        ip := net.ParseIP("81.2.69.142")

        var record interface{}
        err = db.Lookup(ip, &record)
        if err != nil {
                log.Fatal(err)
        }
        fmt.Print(record)
}

func main() {
        ExampleReader_Lookup_struct()
}

Note that the lookup assumes an interface type. The reason is that the API allows the application to specify what fields to lookup.

You can decide on the record structure after dumping the complete info and picking what you need.

References

Written with StackEdit.


go tool: no such tool “compile”

I started facing go tool: no such tool "compile" error after installing a local build of go.

The golang source was in /usr/local

$ cd /usr/local    
$ ls  
total 41240
drwxr-xr-x@  19 root             wheel   608B Oct 18 19:35 go
drwxr-xr-x   16 root             wheel   512B Oct 18 19:35 .
-rw-r--r--@   1 root             wheel    20M Oct 18 19:35 go1.11.13.src.tar.gz

Next was go installation as following:

cd src; ./all.bash

The final build output was

Installed Go for darwin/amd64 in /usr/local/go
Installed commands in /usr/local/go/bin

Now the PATH & GOROOT must have the following values:

$ export GOROOT=/usr/local/go
$export PATH=/usr/local/go/bin:$PATH

The correct GOPATH value fixed te problem.

What didn’t work

  • Setting GOTOOLDIR
  • Reinstalling GO

Reference


Untyped Constants in Golang

Golang is extremely explicit with types. It supports constants of a type. But how about a const 2 or hello. What type would they assume!?
Go solved this problem with untyped constants. Untyped means:

  1. A constant’s type depends on the type of assignee variable.
  2. The assignee’s type must have compatibility to hold constant’s value.

Example

package main

import (
    "fmt"
)

const (
    myconst      = `a untyped constant`
    anotherConst = "a typed const"
)

type myStrType string

func main() {
    fmt.Println("vim-go")

    var name string
    var number int

    // Since name is of type string, both untype & typed string
    // constants work fine.
    name = myconst
    name = anotherConst

    // Doesn't work because a untyped string still is a string.
    // We can't assign it to an integer.
    // number = myconst

    fmt.Println(name, number)

    // This is the use case of untyped consts.
    // A compatible type can hold an untyped constant.
    var newStr myStrType // <---------
    newStr = myconst
    fmt.Println(newStr)
}

Output

$ go run untyped_consts.go                                                                                                                                                                                                              vim-go
a typed const 0
a untyped constant

Remember

  • An untyped constant still has the type.
  • Only a typedef of original type can hold a untyped constant.

References


Notes on Go Structure Tags

Go has a feature to create a user-defined type with struct. Go also allows the program to specify meta-information with structure field.
This meta-information is called Tags. A tag usually helps in packaging/unpacking a user-defined type structure.

  package main

  import (
      "encoding/json"
      "fmt"
  )

  type Test struct {
      // json tags indicate the key name to use in json data
      Name    string `json:"myname"`
      Country string `json:"region"`
  }

  func main() {
      // The order of json need not match the order of structure fields.
      p := map[string]string{"region": "earth", "myname": "hello"}

      marshalled, err := json.Marshal(p)
      fmt.Println(marshalled, err)

      // get the var back from marshalled data
      var m Test
      json.Unmarshal(marshalled, &m)
      fmt.Printf("name=%v\n", m.Name)
      fmt.Printf("name=%v\n", m.Country)
  }

Why should I use json tags

  • The benefit of json tags is the flexibility to marshal with either a JSON or a struct.
  • JSON input for marshaling is convenient and flexible with elements ordering.

Reference


Golang: HTTP Client & Server with Query Params

Client

func TestDelete(id string, registered bool) {
      path := "http://localhost:8085/v1/shops"

      // Create client
      client := &http.Client{}

      // Create request
      req, err := http.NewRequest("DELETE", path, nil)
      if err != nil {
          fmt.Println(err)
          return
      }

      q := req.URL.Query()
      q.Add("id", r.UID)
      q.Add("registered", strconv.FormatBool(registered))
      req.URL.RawQuery = q.Encode()

      fmt.Println(req.URL.String())

      // Fetch Request
      resp, err := client.Do(req)
      if err != nil {
          fmt.Println(err)
          return
      }
      defer resp.Body.Close()

      // Read Response Body
      respBody, err := ioutil.ReadAll(resp.Body)
      if err != nil {
          fmt.Println(err)
          return
      }
      
      fmt.Printf("response=%v body=%v", resp, respBody)
      
      // the server returns a slice in response body
      var out []string
      json.Unmarshal(respBody, &out)
      fmt.Printf("response=%s", out)
  }

Server

var ( 
    errQP = errors.New("query param error")
)

func handleDelete(r *http.Request) (*response, error) {
	uid := r.URL.Query().Get("id")
	registration := r.URL.Query().Get("registration")
	if uid == "" || legacy == "" {
		glog.Errorf("handleDeletes: Url Param missing uid=%s registration=%s", uid, registration)
		return nil, errQP
	}

	isRegistered, err := strconv.ParseBool(registered)
	if err != nil {
		glog.Errorf("handleDelete: failed to parse id=%s registration=%s", uid, registration)
		return nil, err
	}
	// deletedShops is a slice
	deletedShops, err := deleteShops(id, isRegistered)
	if err != nil {
		glog.Errorf("handleDelete: failed id=%s registration=%v err=%s", id, isRegistered, err)
		return nil, err
	}

	return &response{
		status: 200,
		body:   deletedShops,
	}, nil
}

Points to Note

  • The above code allows a client to encode query params in a request.
  • The Client gets a slice in response.

Golang: Switch-case does not fall through!

Unlike C/C++ switch-case statements, Golang switch case does not fall through.

package main
import (
	"fmt"
)

func main() {
	fmt.Print("Go runs on ")
	os := "darwin"
	switch os {
	case "darwin":
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}

The above code does not print anything!

package main
import (
	"fmt"
)

func main() {
	fmt.Print("Go runs on ")
	os := "darwin"
	switch os {
	case "darwin":
	fallthrough
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}

You have to add fallthrough statement to make it work. The above code prints Linux.