model

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Jan 5, 2022 License: MIT Imports: 11 Imported by: 2

Documentation

Overview

Package model allows Go structs to behave as database models.

While this package exports several types the only one you currently need to be concerned with is type Models. All of the examples in this package use a global instance of Models defined in the examples subpackage; you may refer to that global instance for an instantiation example.

Note that in the examples for this package when you see examples.Models or examples.Connect() it is referring the examples subdirectory for this package and NOT the subdirectory for sqlh (i.e. both sqlh and sqlh/model have an examples subdirectory.)

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrUnsupported error = errors.New("unsupported")

Functions

This section is empty.

Types

type Model

type Model struct {
	// Table is the related database table.
	Table schema.Table
	// Statements are the SQL database statements.
	Statements statements.Table

	// V is a *set.Value of a model instance M.
	V *set.Value
	// VSlice is a *set.Value of a model slice []M.
	VSlice *set.Value

	// Mapping is the column to struct field mapping.
	Mapping *set.Mapping

	// BoundMapping is a cached BoundMapping of a zero value for
	// the model.  Gathering query arguments and scan targets will
	// be faster when executing queries by performing the following:
	//	cp := Model.BoundMapping.Copy() // Copy the cached BoundMapping.
	//	cp.Rebind( modelInstance ) 		// Bind the copy to an instance of the model.
	//	cp.Fields( ... )				// Get a slice of query arguments by column name.
	//	cp.Assignable( ... )			// Get a slice of scan targets by column name.
	BoundMapping set.BoundMapping
}

Model relates a Go type to its Table.

func (*Model) BindQuery

func (me *Model) BindQuery(query *statements.Query) QueryBinding

BindQuery returns a QueryBinding that facilitates running queries against instaces of the model.

func (*Model) NewInstance

func (me *Model) NewInstance() interface{}

NewInstance creates an instance of the model's zero value.

func (*Model) NewSlice

func (me *Model) NewSlice() interface{}

NewSlice creates a slice that can hold instances of the model's zero value.

type Models

type Models struct {
	//
	// Mapper defines how SQL column names map to fields in Go structs.
	Mapper *set.Mapper
	//
	// Grammar defines the SQL grammar to use for SQL generation.
	Grammar grammar.Grammar
	//
	// Models is a map of Go types to Model instances.  This member is automatically
	// instantiated during calls to Register().
	Models map[reflect.Type]*Model
	//
	// StructTag specifies the struct tag name to use when inspecting types
	// during register.  If not set will default to "model".
	StructTag string
}

Models is a registry of Models and methods to manipulate them.

Example (Insert)
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	model := &examples.Address{
		// Id, CreatedTime, ModifiedTime are not set and updated by the database.
		Street: "1234 The Street",
		City:   "Small City",
		State:  "ST",
		Zip:    "98765",
	}

	// Create a mock database.
	db, err := examples.Connect(examples.ExAddressInsert)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Insert our model.
	if err := examples.Models.Insert(db, model); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Check expected model fields are no longer zero values.
	if model.Id == 0 {
		fmt.Println("id not updated")
	}
	if model.CreatedTime.Equal(zero) {
		fmt.Println("created time not updated")
	}
	if model.ModifiedTime.Equal(zero) {
		fmt.Println("modified time not updated")
	}
	fmt.Printf("Model inserted.")

}
Output:

Model inserted.
Example (InsertSlice)
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	models := []*examples.Address{
		// Id, CreatedTime, ModifiedTime are not set and updated by the database.
		{
			Street: "1234 The Street",
			City:   "Small City",
			State:  "ST",
			Zip:    "98765",
		},
		{
			Street: "55 Here We Are",
			City:   "Big City",
			State:  "TS",
			Zip:    "56789",
		},
	}

	// Create a mock database.
	db, err := examples.Connect(examples.ExAddressInsertSlice)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Insert our models.
	if err := examples.Models.Insert(db, models); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Check expected model fields are no longer zero values.
	for _, model := range models {
		if model.Id == 0 {
			fmt.Println("id not updated")
			return
		}
		if model.CreatedTime.Equal(zero) {
			fmt.Println("created time not updated")
			return
		}
		if model.ModifiedTime.Equal(zero) {
			fmt.Println("modified time not updated")
			return
		}
	}
	fmt.Printf("%v model(s) inserted.", len(models))

}
Output:

2 model(s) inserted.
Example (Relationship)
package main

import (
	"fmt"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	// This single example shows the common cases of INSERT|UPDATE|UPSERT as distinct code blocks.
	// examples.Relationship has a composite key and no auto-updating fields.
	{
		// Demonstrates INSERT of a single model.
		db, err := examples.Connect(examples.ExRelationshipInsert)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		//
		relate := &examples.Relationship{
			LeftId:  1,
			RightId: 10,
			Toggle:  false,
		}
		if err = examples.Models.Insert(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Insert success.")
	}
	{
		// Demonstrates UPDATE of a single model.
		db, err := examples.Connect(examples.ExRelationshipUpdate)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		//
		relate := &examples.Relationship{
			LeftId:  1,
			RightId: 10,
			Toggle:  true,
		}
		if err = examples.Models.Update(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Update success.")
	}
	{
		// Demonstrates UPSERT of a single model.
		db, err := examples.Connect(examples.ExRelationshipUpsert)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		//
		relate := &examples.Relationship{
			LeftId:  1,
			RightId: 10,
			Toggle:  false,
		}
		if err = examples.Models.Upsert(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Upsert success.")
	}

}
Output:

Insert success.
Update success.
Upsert success.
Example (RelationshipSlice)
package main

import (
	"fmt"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	// This single example shows the common cases of INSERT|UPDATE|UPSERT as distinct code blocks.
	// examples.Relationship has a composite key and no auto-updating fields.
	{
		// Demonstrates INSERT of a slice of models.
		db, err := examples.Connect(examples.ExRelationshipInsertSlice)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		//
		relate := []*examples.Relationship{
			{
				LeftId:  1,
				RightId: 10,
				Toggle:  false,
			},
			{
				LeftId:  2,
				RightId: 20,
				Toggle:  true,
			},
			{
				LeftId:  3,
				RightId: 30,
				Toggle:  false,
			},
		}
		if err = examples.Models.Insert(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Insert success.")
	}
	{
		// Demonstrates UPDATE of a slice of models.
		db, err := examples.Connect(examples.ExRelationshipUpdateSlice)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		//
		relate := []*examples.Relationship{
			{
				LeftId:  1,
				RightId: 10,
				Toggle:  true,
			},
			{
				LeftId:  2,
				RightId: 20,
				Toggle:  false,
			},
			{
				LeftId:  3,
				RightId: 30,
				Toggle:  true,
			},
		}
		if err = examples.Models.Update(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Update success.")
	}
	{
		// Demonstrates UPSERT of a slice of models.
		db, err := examples.Connect(examples.ExRelationshipUpsertSlice)
		if err != nil {
			fmt.Println(err.Error())
			return
		}
		//
		relate := []*examples.Relationship{
			{
				LeftId:  1,
				RightId: 10,
				Toggle:  false,
			},
			{
				LeftId:  2,
				RightId: 20,
				Toggle:  true,
			},
			{
				LeftId:  3,
				RightId: 30,
				Toggle:  false,
			},
		}
		if err = examples.Models.Upsert(db, relate); err != nil {
			fmt.Println(err)
			return
		}
		fmt.Println("Upsert success.")
	}

}
Output:

Insert success.
Update success.
Upsert success.
Example (Update)
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	model := &examples.Address{
		Id:          42,
		CreatedTime: time.Now().Add(-1 * time.Hour),
		// ModifiedTime is zero value; will be updated by database.
		Street: "1234 The Street",
		City:   "Small City",
		State:  "ST",
		Zip:    "98765",
	}

	// Create a mock database.
	db, err := examples.Connect(examples.ExAddressUpdate)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Update our model.
	if err := examples.Models.Update(db, model); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Check expected model fields are no longer zero values.
	if model.ModifiedTime.Equal(zero) {
		fmt.Println("modified time not updated")
	}
	fmt.Printf("Model updated.")

}
Output:

Model updated.
Example (UpdateSlice)
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	models := []*examples.Address{
		// ModifiedTime is not set and updated by the database.
		{
			Id:          42,
			CreatedTime: time.Now().Add(-2 * time.Hour),
			Street:      "1234 The Street",
			City:        "Small City",
			State:       "ST",
			Zip:         "98765",
		},
		{
			Id:          62,
			CreatedTime: time.Now().Add(-1 * time.Hour),
			Street:      "55 Here We Are",
			City:        "Big City",
			State:       "TS",
			Zip:         "56789",
		},
	}

	// Create a mock database.
	db, err := examples.Connect(examples.ExAddressUpdateSlice)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Update our models.
	if err := examples.Models.Update(db, models); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Check expected model fields are no longer zero values.
	for _, model := range models {
		if model.ModifiedTime.Equal(zero) {
			fmt.Println("modified time not updated")
			return
		}
	}
	fmt.Printf("%v model(s) updated.", len(models))

}
Output:

2 model(s) updated.
Example (Upsert)
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	model := &examples.Upsertable{
		Id:     "some-unique-string",
		String: "Hello, World!",
		Number: 42,
	}

	// Create a mock database.
	db, err := examples.Connect(examples.ExUpsert)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Upsert our model.
	if err := examples.Models.Upsert(db, model); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Check expected model fields are no longer zero values.
	if model.CreatedTime.Equal(zero) {
		fmt.Println("created time not updated")
	}
	if model.ModifiedTime.Equal(zero) {
		fmt.Println("modified time not updated")
	}
	fmt.Printf("Model upserted.")

}
Output:

Model upserted.
Example (UpsertSlice)
package main

import (
	"fmt"
	"time"

	"github.com/nofeaturesonlybugs/sqlh/model/examples"
)

func main() {
	var zero time.Time
	models := []*examples.Upsertable{
		{
			Id:     "some-unique-string",
			String: "Hello, World!",
			Number: 42,
		},
		{
			Id:     "other-unique-string",
			String: "Goodbye, World!",
			Number: 10,
		},
	}

	// Create a mock database.
	db, err := examples.Connect(examples.ExUpsertSlice)
	if err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Upsert our models.
	if err := examples.Models.Upsert(db, models); err != nil {
		fmt.Println("err", err.Error())
		return
	}

	// Check expected model fields are no longer zero values.
	for _, model := range models {
		if model.CreatedTime.Equal(zero) {
			fmt.Println("created time not updated")
			return
		}
		if model.ModifiedTime.Equal(zero) {
			fmt.Println("modified time not updated")
			return
		}
	}
	fmt.Printf("%v model(s) upserted.", len(models))

}
Output:

2 model(s) upserted.

func (*Models) Insert

func (me *Models) Insert(Q sqlh.IQueries, value interface{}) error

Insert attempts to persist values via INSERTs.

func (*Models) Lookup

func (me *Models) Lookup(value interface{}) (m *Model, err error)

Lookup returns the model associated with the value.

func (*Models) Register

func (me *Models) Register(value interface{}, opts ...interface{})

Register adds a Go type to the Models instance. As part of initialization your application should register all types T that need to interact with the database.

Register is not goroutine safe; implement locking in the application level if required.

func (*Models) Update

func (me *Models) Update(Q sqlh.IQueries, value interface{}) error

Update attempts to persist values via UPDATESs.

func (*Models) Upsert added in v0.3.0

func (me *Models) Upsert(Q sqlh.IQueries, value interface{}) error

Upsert attempts to persist values via UPSERTs.

Upsert only works on primary keys that are defined as "key"; in other words columns tagged with "key,auto" are not used in the generated query.

Upsert only supports primary keys; currently there is no support for upsert on UNIQUE indexes that are not primary keys.

type QueryBinding

type QueryBinding interface {
	// Query accepts either a single model M or a slice of models []M.  It then
	// runs and returns the result of QueryOne or QuerySlice.
	Query(sqlh.IQueries, interface{}) error
	// QueryOne runs the query against a single instance of the model.
	QueryOne(sqlh.IQueries, interface{}) error
	// QuerySlice runs the query against a slice of model instances.
	QuerySlice(sqlh.IQueries, interface{}) error
}

QueryBinding binds a model with a query to facilitate running the query against instances of types described by the model.

type TableName

type TableName string

TableName represents a database table name. Embed the TableName type into a struct and set the approprate struct tag to configure the table name.

Directories

Path Synopsis
Package examples provides types and functions to facilitate the examples and test code in the model package.
Package examples provides types and functions to facilitate the examples and test code in the model package.
Package statements builds uses a grammar to build SQL statements scoped to entities within the database.
Package statements builds uses a grammar to build SQL statements scoped to entities within the database.

Jump to

Keyboard shortcuts

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