papergres

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: May 17, 2021 License: MIT Imports: 15 Imported by: 0

README

Papergres

Papergres is an ultra lightweight micro-ORM written in golang for the postgres database server. The library provides easy ways to execute queries, insert data and sql logging.

This is a wrapper around the general purpose extensions library sqlx by jmoiron. The core postgres driver used is pq.

Papergres is used at Paperchain, built and maintained by rahul rumalla. Special thanks to Weston Dorsey, for sparking up the project.

GoDoc Build Status Go Report Card

Install

go get -u github.com/Paperchain/papergres

API Documentation

Full API documentation can be found on godoc

Contributors

We're open to and looking for open-source contributors to help make this library more robust

Building & Testing

Fetch the dependencies using dep

dep ensure

Build using the go cmd

go build

Test everything!

go test -v ./...

Usage

The simplest way to execute a query returning a single object is

// Set up your connection object
conn := NewConnection("postgres://postgres:postgres@localhost:5432/paperchain", "papergres_tests", SSLDisable)
db := conn.NewDatabase()

// Write a query in a single line
var book Book
res := db.Query("SELECT * FROM paper.book WHERE book_id = $1 LIMIT 1;", 777).ExecSingle(&book)

To retrieve a list of rows and hydrate it into a list of object

var books []book
res := db.Query("SELECT * FROM paper.book WHERE book_id > 10 LIMIT 1;", 777).ExecAll(&books)

To insert a record into a table

// Create a struct and specify database column names via the "db" struct tag
type Book struct {
	BookId    PrimaryKey `db:"book_id"`
	Title     string     `db:"title"`
	Author    string     `db:"author"`
	CreatedAt time.Time  `db:"created_at"`
	CreatedBy string     `db:"created_by"`
}

// Instantiate your struct
book := &Book{
	Title:     "The Martian",
	Author:    "Andy Weir",
	CreatedAt: time.Now(),
	CreatedBy: "TestInsert",
}

// Perform an insert using papergres
res := db.Insert(book)
if res.Err != nil {
		log.Fatalln(res.Err.Error())
}
    
// Retrieve the inserted ID from the primary key
bookid := res.LastInsertId.ID

// To insert into a specific schema other than the public
schema := db.Schema("my_schema")
res := schema.Insert(book)

// To insert multiple
res, err := schema.InsertAll(books)

Logging

The library provides an logging interface that needs to implemented

// Instantiate your logged in your application's bootstrap
Log = &testLogger{}

// The logger interface that needs to be implemented. This is available in papergres.go
type Logger interface {
	Info(args ...interface{})
	Infof(format string, args ...interface{})
	Debug(args ...interface{})
	Debugf(format string, args ...interface{})
}

// For example, you can set your logger with something like this
type testLogger struct{}

func (t *testLogger) Info(args ...interface{}) {
	if debug {
		fmt.Println(args...)
	}
}
func (t *testLogger) Infof(format string, args ...interface{}) {
	if debug {
		fmt.Printf(format, args...)
	}
}
func (t *testLogger) Debug(args ...interface{}) {
	if debug {
		fmt.Println(args...)
	}
}
func (t *testLogger) Debugf(format string, args ...interface{}) {
	if debug {
		fmt.Printf(format, args...)
	}
}

Example of the sql logging

== POSTGRES QUERY ==
        Query:
        INSERT INTO paper.character (
        book_id,
        name,
        description,
        created_at,
        created_by
)
VALUES (
        $1,
        $2,
        $3,
        $4,
        $5
)
RETURNING character_id as LastInsertId;
        Args:
        $1: 6
        $2: Mitch Henderson
        $3: Sean Bean doesn't die in this movie
        $4: 2017-12-14 11:06:46.6077695 +0000 GMT
        $5: TestInsert
        Connection:
        connect_timeout=0
        dbname=paperchain
        fallback_application_name=papergres_tests
        host=localhost
        password=postgres
        port=5432
        sslmode=disable
        user=postgres

== RESULT ==
LastInsertId:  21
RowsAffected:  No RowsAffected returned
RowsReturned:  0
ExecutionTime: 496.2µs
Error: <nil>
== ADDITIONAL INFO ==
Repeat Index: 4 / 4


== POSTGRES QUERY ==
        Query:
        SELECT * FROM paper.character WHERE book_id = $1;
        Args:
        $1: 6
        Connection:
        connect_timeout=0
        dbname=paperchain
        fallback_application_name=papergres_tests
        host=localhost
        password=postgres
        port=5432
        sslmode=disable
        user=postgres

== RESULT ==
LastInsertId:  0
RowsAffected:  0
RowsReturned:  4
ExecutionTime: 5.549ms
Error: <nil>

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Reset

func Reset()

Reset resets all global variables to start-up values. All cached values will be created again when needed. Can be used to reset DB instances if one were to unexpectedly fail which I'm not sure is possible.

func Shutdown

func Shutdown()

Shutdown performs a graceful shutdown of all DBs

Types

type Connection

type Connection struct {
	Database    string
	User        string
	Password    string
	Host        string
	Port        string
	AppName     string
	Timeout     int
	SSLMode     SSLMode
	SSLCert     string
	SSLKey      string
	SSLRootCert string
}

Connection holds all database connection configuration.

func NewConnection

func NewConnection(databaseURL string, appName string) Connection

NewConnection creates and returns the Connection object to the postgres server

func (Connection) NewDatabase

func (conn Connection) NewDatabase() *Database

NewDatabase creates a new Database object

func (*Connection) String

func (conn *Connection) String() string

String will build a connection string given database Connection settings

type Database

type Database struct {
	// contains filtered or unexported fields
}

Database contains all required database attributes Use papergres.New() to create a Database

func (*Database) Connection

func (db *Database) Connection() Connection

Connection returns the connection information for a database

func (*Database) ConnectionString

func (db *Database) ConnectionString() string

ConnectionString will build a connection string given database Connection settings

func (*Database) CreateDatabase

func (db *Database) CreateDatabase() *Result

CreateDatabase creates a default database Good for use during testing and local dev

func (*Database) GenerateInsert

func (db *Database) GenerateInsert(obj interface{}) *Query

GenerateInsert generates an insert query for the given object

func (*Database) Insert

func (db *Database) Insert(obj interface{}) *Result

Insert inserts the passed in object

func (*Database) InsertAll

func (db *Database) InsertAll(objs interface{}) ([]*Result, error)

InsertAll inserts a slice of objects concurrently. objs must be a slice with items in it. the Result slice will be in the same order as objs so a simple loop will set all the primary keys if needed:

for i, r := range results {
	objs[i].Id = r.LastInsertId.ID
}

func (*Database) Ping

func (db *Database) Ping() error

Ping tests the database connection

func (*Database) Query

func (db *Database) Query(sql string, args ...interface{}) *Query

Query creates a base new query object that can be used for all database operations

func (*Database) Schema

func (db *Database) Schema(name string) *Schema

Schema allows for certain operations that require a specific schema

func (*Database) Stats

func (db *Database) Stats() sql.DBStats

Stats returns DBStats. Right now this only returns OpenConnections

type Domain

type Domain struct {
	// contains filtered or unexported fields
}

Domain a database schema and the package that uses it. It provides the default DomainOwner implementation.

type DomainOwner

type DomainOwner interface {
	// Database returns back the Database of the domain
	Database() *Database

	// Schema returns back schema of the domain
	Schema() []string

	// Package returns back the package that owns the domain
	Package() string

	// Name returns the name of the domain
	Name() string

	// Namespace returns the full namespace of the domain
	Namespace() string

	// String returns the string representation of the DomainOwner
	String() string
}

DomainOwner is an application that is in charge of a database domain

type Field

type Field struct {
	Typeof string
	Name   string
	Tag    string
	Value  interface{}
}

Field is a struct field

type Generator

type Generator interface {
	// DomainOwner a generator must be a DomainOwner
	DomainOwner

	// DropSchema will drop all schema
	DropSchema() *Result

	// CreateSchema will create all schema
	CreateSchema() *Result

	// CreateMockData generates and inserts test data
	CreateMockData() *Result
}

Generator defines behavior needed to create a database

type LastInsertId

type LastInsertId struct {
	ID  PrimaryKey
	Err error
}

LastInsertId is the last inserted ID from a script

type Logger

type Logger interface {
	Info(args ...interface{})
	Infof(format string, args ...interface{})
	Debug(args ...interface{})
	Debugf(format string, args ...interface{})
}

Logger is the required interface for the papergres logger

var (
	// Log is the way to log the scripting activity happening from the library to the database
	Log Logger
)

type PrimaryKey

type PrimaryKey interface{}

PrimaryKey is the type used for primary keys

type Query

type Query struct {
	SQL      string
	Database *Database
	Args     []interface{}
	// contains filtered or unexported fields
}

Query holds the SQL to execute and the connection string

func (*Query) Exec

func (q *Query) Exec() *Result

Exec runs a sql command given a connection and expects LastInsertId or RowsAffected to be returned by the script. Use this for INSERTs

func (*Query) ExecAll

func (q *Query) ExecAll(dest interface{}) *Result

ExecAll gets many rows and populates the given slice dest should be a pointer to a slice

func (*Query) ExecNonQuery

func (q *Query) ExecNonQuery() *Result

ExecNonQuery runs the SQL and doesn't look for any results

func (*Query) ExecSingle

func (q *Query) ExecSingle(dest interface{}) *Result

ExecSingle fetches a single row from the database and puts it into dest. If more than 1 row is returned it takes the first one. Expects at least 1 row or it will error.

func (*Query) Repeat

func (q *Query) Repeat(times int, pSelectorFn SelectParamsFn) *Repeat

Repeat will execute a query N times. The param selector function will pass in the current iteration and expect back the destination obj and args for that index. Make sure to use pointers to ensure the sql results fill your structs. Use this when you want to run the same query for many different parameters, like getting data for child entities for a collection of parents. This function executes the iterations concurrently so each loop should not rely on state from a previous loops execution. The function should be extremely fast and efficient with DB resources. Returned error will contain all errors that occurred in any iterations.

params := func(i int) (dest interface{}, args []interface{}) {
	p := &parents[i] 						// get parent at i to derive parameters
	args := MakeArgs(p.Id, true, "current") // create arg list, variadic
	return &p.Child, args 					// &p.Child will be filled with returned data
}
// len(parents) is parent slice and determines how many times to execute query
results, err := db.Query(sql).Repeat(len(parents), params).Exec()

func (*Query) String

func (q *Query) String() string

type Repeat

type Repeat struct {
	Query    *Query
	ParamsFn SelectParamsFn
	N        int
}

Repeat holds the iteration count and params function for executing a query N times.

func (*Repeat) Exec

func (r *Repeat) Exec() ([]*Result, error)

Exec executes the repeat query command. Internally this will prepare the statement with the database and then create go routines to execute each statement.

type Result

type Result struct {
	LastInsertId  LastInsertId
	RowsAffected  RowsAffected
	RowsReturned  int
	ExecutionTime time.Duration
	Err           error
}

Result holds the results of an executed query

func Exec

func Exec(sql string, conn Connection, args ...interface{}) *Result

Exec executes an ad-hoc query against a connection. This is only recommended for use if you have a weird case where you need to modify the connection string just for this query, like when creating a new database. Otherwise just new New() and save the DAtabase instance.

func ExecNonQuery

func ExecNonQuery(sql string, conn Connection, args ...interface{}) *Result

ExecNonQuery executes an ad-hoc query against a connection. This is only recommended for use if you have a weird case where you need to modify the connection string just for this query, like when creating a new database. Otherwise just new New() and save the Database instance.

func NewResult

func NewResult() *Result

NewResult returns an empty Result

func (*Result) String

func (r *Result) String() string

type RowsAffected

type RowsAffected struct {
	Count int64
	Err   error
}

RowsAffected is the returned rows affected from a script

type SSLMode

type SSLMode string

SSLMode defines all possible SSL options

const (
	// SSLDisable - No SSL
	SSLDisable SSLMode = "disable"
	// SSLRequire - Always SSL, no verification
	SSLRequire SSLMode = "require"
	// SSLVerifyCA - Always SSL, verifies that certificate was signed by trusted CA
	SSLVerifyCA SSLMode = "verify-ca"
	// SSLVerifyFull - Always SSL, verifies that certificate was signed by trusted CA
	// and server host name matches the one in the certificate
	SSLVerifyFull SSLMode = "verify-full"
)

type Schema

type Schema struct {
	Name     string
	Database *Database
}

Schema holds the schema to query along with the Database

func (*Schema) GenerateInsert

func (s *Schema) GenerateInsert(obj interface{}) *Query

GenerateInsert generates the insert query for the given object

func (*Schema) Insert

func (s *Schema) Insert(obj interface{}) *Result

Insert inserts the passed in object

func (*Schema) InsertAll

func (s *Schema) InsertAll(objs interface{}) ([]*Result, error)

InsertAll inserts a slice of objects concurrently. objs must be a slice with items in it. the Result slice will be in the same order as objs so a simple loop will set all the primary keys if needed:

for i, r := range results {
	objs[i].Id = r.LastInsertId.ID
}

type SelectParamsFn

type SelectParamsFn func(i int) (dest interface{}, args []interface{})

SelectParamsFn is a function that takes in the iteration and returns the destination and args for a SQL execution.

Jump to

Keyboard shortcuts

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