Skip to content

Commit

Permalink
Merge pull request #14 from Paperchain/overhaul
Browse files Browse the repository at this point in the history
Overhaul
  • Loading branch information
shashankgroovy committed Sep 8, 2021
2 parents e68115e + f658ccc commit 81449ee
Show file tree
Hide file tree
Showing 11 changed files with 957 additions and 898 deletions.
62 changes: 48 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,51 @@
# 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](https://github.com/jmoiron/sqlx) by [jmoiron](https://github.com/jmoiron). The core postgres driver used is [pq](https://github.com/lib/pq).
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.

Papergres is used at [Paperchain](https://paperchain.io), built and maintained by [rahul rumalla](https://github.com/rahulrumalla). Special thanks to [Weston Dorsey](https://github.com/wdorsey), for sparking up the project.
This is a wrapper around the general purpose extensions library [sqlx](https://github.com/jmoiron/sqlx) by [jmoiron](https://github.com/jmoiron). The core postgres driver used is [pq](https://github.com/lib/pq).

Papergres is used at [Paperchain](https://paperchain.io), built and maintained
by the team.

[![GoDoc](https://godoc.org/github.com/paperchain/papergres?status.svg)](https://godoc.org/github.com/paperchain/papergres)
[![Build Status](https://travis-ci.org/Paperchain/papergres.svg?branch=master)](https://travis-ci.org/Paperchain/papergres)
[![Go Report Card](https://goreportcard.com/badge/github.com/paperchain/papergres)](https://goreportcard.com/report/github.com/paperchain/papergres)
[![Dev chat at https://gitter.im/papergres/Lobby](https://img.shields.io/badge/gitter-developer_chat-46bc99.svg)](https://gitter.im/papergres/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

## Install

`go get -u github.com/Paperchain/papergres`

## API Documentation
## API Documentation

Full API documentation can be found on [godoc](https://godoc.org/github.com/Paperchain/papergres)

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

Compatibility with the most recent two versions of Go is a requirement for any
new changes. Compatibility beyond that is not guaranteed.

Versioning is done with Go modules. Breaking changes (eg. removing deprecated
API) will get major version number bumps.

## Building & Testing
Fetch the dependencies using dep
```bash
dep ensure
```

Build using the go cmd

```bash
go build
```

Test everything!

```bash
go test -v ./...
```

## Usage

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

```go
// Set up your connection object
conn := NewConnection("postgres://postgres:postgres@localhost:5432/paperchain", "papergres_tests", SSLDisable)
Expand All @@ -48,12 +57,14 @@ res := db.Query("SELECT * FROM paper.book WHERE book_id = $1 LIMIT 1;", 777).Exe
```

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

```go
var books []book
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

```go
// Create a struct and specify database column names via the "db" struct tag
type Book struct {
Expand All @@ -77,7 +88,7 @@ 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

Expand All @@ -89,9 +100,25 @@ res := schema.Insert(book)
res, err := schema.InsertAll(books)
```

To search for records using the IN query clause (make sure to use `?` bind variable in sql query)

```go
var books []Book
var authors = []string{"Issac Asimov", "H. G. Wells", "Arther C. Clarke"}
res := db.Query("SELECT * FROM paper.book WHERE author IN (?);", authors).ExecAllIn(&books)
```

Additionally, one can always use the `ExecNonQuery` method to make any sql query - insert,
update and select.

```go
query := `UPDATE paper.book SET title=$1 WHERE book_id = $2;`
res := db.Query(query, "I, Robot", 42).ExecNonQuery()
```

## Logging
The library provides an logging interface that needs to implemented

The library provides an logging interface that needs to be implemented

```go
// Instantiate your logged in your application's bootstrap
Expand Down Expand Up @@ -130,7 +157,8 @@ func (t *testLogger) Debugf(format string, args ...interface{}) {
}
```

Example of the sql logging
Example of the sql logging

```
== POSTGRES QUERY ==
Query:
Expand Down Expand Up @@ -197,3 +225,9 @@ RowsReturned: 4
ExecutionTime: 5.549ms
Error: <nil>
```

## Contribution

Feel free to file issues and raise a PR.

Happy Programming!
141 changes: 141 additions & 0 deletions connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package papergres

import (
"fmt"
"net"
"net/url"
"sort"
"strings"
)

// SSLMode defines all possible SSL options
type SSLMode string

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"
)

// Connection holds all database connection configuration.
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
}

// NewConnection creates and returns the Connection object to the postgres server
func NewConnection(databaseURL string, appName string) Connection {
u, err := url.Parse(databaseURL)
if err != nil {
panic(err)
}

host, port, _ := net.SplitHostPort(u.Host)
p, _ := u.User.Password()
q, _ := url.ParseQuery(u.RawQuery)
path := u.Path
if strings.Index(path, "/") == 0 {
path = path[1:]
}

// Set sslmode
sslMode := setSSLMode(q)

// Build the Connection object
conn := Connection{
User: u.User.Username(),
Password: p,
Host: host,
Port: port,
Database: path,
AppName: appName,
SSLMode: sslMode,
}

return conn
}

// String method builds a DSN(Data Source Name) connection string based on the
// given database connection settings and returns it.
func (conn *Connection) String() string {
var s string
if conn.Database != "" {
s += fmt.Sprintf("dbname=%s ", conn.Database)
}
if conn.User != "" {
s += fmt.Sprintf("user=%s ", conn.User)
}
if conn.Password != "" {
s += fmt.Sprintf("password=%s ", conn.Password)
}
if conn.Host != "" {
s += fmt.Sprintf("host=%s ", conn.Host)
}
if conn.Port != "" {
s += fmt.Sprintf("port=%s ", conn.Port)
}
if conn.AppName != "" {
s += fmt.Sprintf("fallback_application_name=%s ", conn.AppName)
}
if conn.SSLMode != "" {
s += fmt.Sprintf("sslmode=%s ", conn.SSLMode)
}
if conn.SSLCert != "" {
s += fmt.Sprintf("sslcert=%s ", conn.SSLCert)
}
if conn.SSLKey != "" {
s += fmt.Sprintf("sslkey=%s ", conn.SSLKey)
}
if conn.SSLRootCert != "" {
s += fmt.Sprintf("sslrootcert=%s ", conn.SSLRootCert)
}
s += fmt.Sprintf("connect_timeout=%v ", conn.Timeout)
return s
}

// NewDatabase creates a new Database object
func (conn Connection) NewDatabase() *Database {
return &Database{
conn: &conn,
}
}

// function to set SSL mode for connection based on url.Values
func setSSLMode(q url.Values) SSLMode {
sslMode := SSLDisable

if len(q["sslmode"]) > 0 && q["sslmode"][0] != "" {
sslMode = SSLMode(q["sslmode"][0])
}

return sslMode
}

// prettifyConnString prints out all the props from connection string in a neat
// way.
func prettifyConnString(conn string) string {
var str string
props := strings.Split(conn, " ")
sort.Strings(props)
for _, s := range props {
if s != "" {
str += fmt.Sprintf("\n\t%s", s)
}
}
return str
}
104 changes: 104 additions & 0 deletions database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package papergres

import (
"database/sql"
"fmt"
)

// Database contains all required database attributes
type Database struct {
// conn is all information needed to connect to the database.
conn *Connection

// connString referes to the DSN string for the current DB.
connString string
}

// Connection returns the connection information for a database
func (db *Database) Connection() Connection {
return *db.conn
}

// ConnectionString returns the DSN(Data Source Name) connection string for the
// current DB connection.
func (db *Database) ConnectionString() string {
if db.connString != "" {
return db.connString
}
db.connString = db.conn.String()
return db.connString
}

// CreateDatabase creates a default database
// Good for use during testing and local dev
func (db *Database) CreateDatabase() *Result {
// if Ping works then DB already exists
err := db.Ping()
if err == nil {
return NewResult()
}

sql := fmt.Sprintf(`
CREATE DATABASE %s
WITH
OWNER = postgres
ENCODING = 'UTF8'
LC_COLLATE = 'English_United States.1252'
LC_CTYPE = 'English_United States.1252'
TABLESPACE = pg_default
CONNECTION LIMIT = -1;`, db.Connection().Database)

conn := db.Connection()
conn.Database = ""
db.conn = &conn
db.connString = ""
return db.Query(sql).ExecNonQuery()
}

// GenerateInsert generates an insert query for the given object
func (db *Database) GenerateInsert(obj interface{}) *Query {
return db.Schema("public").GenerateInsert(obj)
}

// Insert inserts the passed in object
func (db *Database) Insert(obj interface{}) *Result {
return db.Schema("public").Insert(obj)
}

// 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 (db *Database) InsertAll(objs interface{}) ([]*Result, error) {
return db.Schema("public").InsertAll(objs)
}

// Ping tests the database connection
func (db *Database) Ping() error {
return open(db.ConnectionString()).Ping()
}

// Query creates a base new query object that can be used for all database operations
func (db *Database) Query(sql string, args ...interface{}) *Query {
return &Query{
SQL: sql,
Database: db,
Args: args,
}
}

// Stats returns DBStats. Right now this only returns OpenConnections
func (db *Database) Stats() sql.DBStats {
return open(db.ConnectionString()).Stats()
}

// Schema allows for certain operations that require a specific schema
func (db *Database) Schema(name string) *Schema {
return &Schema{
Name: name,
Database: db,
}
}
Loading

0 comments on commit 81449ee

Please sign in to comment.