pgxmap

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 24, 2022 License: Apache-2.0 Imports: 7 Imported by: 0

README

pgxmap

test Go Report Card GitHub go.mod Go version codecov GoDoc

Custom map to use with pgx help with map[string]interface{} decoding and encoding correctly map items types (e.g. int64) when saved and retrieved from Postgres DB.

Why

pgx stores JSON-marshalled objects in DB as JSONB type. For Go structs with priory know types, pgx driver encodes Go types to JSON and back automatically. However, when unmarshalling into a Go interface (e.g. map[string]interface{}) Go decoding has to do some guessing to map certain types when scanning DB selection results (e.g. int64 vs float64). There isn't really much pgx can actually do here since JavaScript itself does not distinguish between integers and floats (see this for details). There are similar issues with time precision as well.

The full demo of the error resulting from an idiomatic implementation is available here.

There are 4 diff ways you can deal with this issue:

  1. Avoid using map with interface value altogether
  2. Use map with all values as strings and parse on return
  3. Use the driver.Valuer interface to do your own encoding and decoding
  4. Use the "wrapper struct" (assuming you have the luxury of using generics)

The pgxmap uses the 3rd approach and ensures that the int, float derivative types (int64, int32, uint16, float64 etc.) and time in your map are automatically correctly encoded and decoded to ensure exact same type/precision while still providing that basic Go map-like functionality.

Usage

The basic usage of the pgxmap library falls into 3 patterns:

  • Creation of data (config map or state struct)
  • Saving it to DB
  • Retrieving it from DB
Creation
Config Map

For illustration purposes, this example creates a config map with only 3 items with the different types. See map_test.go for broader example.

	bigInt := time.Now().UnixNano()

	m := pgxmap.ConfigMap{
		"int64":   bigInt,
		"uint32":  uint32(bigInt),
		"float64": float64(bigInt) / float64(3),
	}
State Struct

Assuming the data you want to persist into DB looks something like this:

type MyThing struct {
	String string    `json:"s"`
	Number int64     `json:"n"`
	Float  float64   `json:"f"`
	Time   time.Time `json:"t"`
	Bool   bool      `json:"b"`
}

You wrap it into the State struct like this:

s := pgxmap.State[MyThing]{
	Data: MyThing{
		String: "hello",
		Number: 1653398649063529000,
		Float:  1653398731.740085,
		Time:   time.Now(),
		Bool:   true,
	},
}
Saving

Assuming the DB table schema looks something like this (key is the JSONB column type):

CREATE TABLE IF NOT EXISTS demo (
    id varchar NOT NULL PRIMARY KEY,
    cf JSONB
);

Saving the map or state object into DB then is as simple as using it as yet another parameter in your Exec method:

Assuming the sql variable holding your SELECT statement is already defined.

func save(ctx context.Context, p *pgxpool.Pool, id string, m pgxmap.ConfigMap) error {
	// input validation skipped for brevity 
	if _, err := pool.Exec(ctx, sql, id, m); err != nil {
		return errors.Wrap(err, "error inserting row")
	}
	return nil
}
Retrieval

Similarly, during retrieval you simply provide a pointer to the ConfigMap or State and use the row.Scan method:

Assuming the sql variable holding your INSERT statement is already defined.

func get(ctx context.Context, p *pgxpool.Pool, id string) (pgxmap.State, error) {
    // input validation skipped for brevity 
	var s pgxmap.State
	row := pool.QueryRow(ctx, sql, id)
	if err := row.Scan(&s); err != nil && err != pgx.ErrNoRows {
		return nil, errors.Wrap(err, "error scanning row")
	}

	return s, nil
}

Disclaimer

This is my personal project and it does not represent my employer. I take no responsibility for issues caused by this code. I do my best to ensure that everything works, but if something goes wrong, my apologies is all you will get.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ConfigMap

type ConfigMap map[string]interface{}

ConfigMap represents a map of key-value pairs that's used as input/output.

func (*ConfigMap) Scan

func (m *ConfigMap) Scan(value interface{}) error

Scan implements the sql.Scanner interface.

func (ConfigMap) Value

func (m ConfigMap) Value() (driver.Value, error)

Value implements the driver.Valuer interface.

type State added in v0.2.1

type State[T any] struct {
	Data T `json:"data"`
}

func (*State[T]) Scan added in v0.2.1

func (s *State[T]) Scan(value interface{}) error

Scan implements the sql.Scanner interface.

func (State[T]) Value added in v0.2.1

func (s State[T]) Value() (driver.Value, error)

Value implements the driver.Valuer interface.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL