Skip to content

Commit

Permalink
Merge pull request #2 from Kansuler/v2
Browse files Browse the repository at this point in the history
initial draft for octobe v2
  • Loading branch information
Kansuler authored Dec 22, 2023
2 parents 40f19f9 + 68240aa commit dc965bd
Show file tree
Hide file tree
Showing 21 changed files with 891 additions and 1,382 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/testing.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.18
go-version: 1.21

- name: Vendor deps
run: go mod vendor
Expand Down
201 changes: 77 additions & 124 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,159 +2,112 @@

![License](https://img.shields.io/github/license/Kansuler/octobe) ![Tag](https://img.shields.io/github/v/tag/Kansuler/octobe) ![Version](https://img.shields.io/github/go-mod/go-version/Kansuler/octobe) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/492e6729782b471788994a72f2359f39)](https://www.codacy.com/gh/Kansuler/octobe/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Kansuler/octobe&utm_campaign=Badge_Grade) [![Go Reference](https://pkg.go.dev/badge/github.com/Kansuler/octobe.svg)](https://pkg.go.dev/github.com/Kansuler/octobe)

A slim golang package for programmers that love to write raw SQL, but has a problem with boilerplate code. This package will help you structure and unify the way you work with your database.
A slim golang package for programmers that love to write raw SQL, but has a problem with boilerplate code. This package
will help you structure and the way you work with your database.

The main advantage with this library is to enable developers to build a predictable and consistent database layer without losing the feeling of freedom. The octobe library draws inspiration from http handlers, but where handlers interface with the database instead.
The main advantage with this library is to enable developers to build a predictable and consistent database layer
without losing the feeling of freedom. The octobe library draws inspiration from http handlers, but where handlers
interface with the database instead.

Read package documentation at
[https://pkg.go.dev/github.com/Kansuler/octobe](https://pkg.go.dev/github.com/Kansuler/octobe)

## Usage

### WatchTransaction

This is a method that can watch the whole transaction, and where you don't have to define rollback or commit.

WatchTransaction will rollback in case error is returned, otherwise it will proceed to commit.
### Postgres Example

```go
func Method(db *sql.DB, ctx context.Context) error {
ob := octobe.New(db)

// Example of chaining multiple handlers in a transaction
return ob.WatchTransaction(ctx, func(scheme *octobe.Scheme) error {
p1 := Product{Name: "home made baguette"}
err := scheme.Handle(InsertProduct(&p1))
if err != nil {
return err
package main

import (
"context"
"github.com/Kansuler/octobe/v2"
"github.com/Kansuler/octobe/v2/driver/postgres"
"github.com/google/uuid"
"os"
)

func main() {
ctx := context.Background()
dsn := os.Getenv("DSN")
if dsn == "" {
panic("DSN is not set")
}

// Execute other non-database logic that can return an error and rollback the transaction
err = anotherFunctionWithLogic()
// Create a new octobe instance with a postgres driver, insert optional options for configuration that applies to
// every session.
o, err := octobe.New(postgres.Open(ctx, dsn, postgres.WithTransaction(postgres.TxOptions{})))
if err != nil {
return err
panic(err)
}

p2 := Product{Name: "another home made baguette"}
// For illustration, you can also suppress specific errors in the scheme handler
err = scheme.Handle(InsertProduct(&p2), octobe.SuppressError(sql.ErrNoRows), octobe.SuppressError(sql.ErrTxDone))
// Begin a new session, since `postgres.WithTransaction` is set, this will start a postgres transaction.
session, err := o.Begin(context.Background())
if err != nil {
return err
panic(err)
}

return nil
})
}
// WatchRollback will rollback the transaction if var err is not nil when the function returns.
defer session.WatchRollback(func() error {
return err
})

// InsertProduct will take a pointer of a product, and insert it
// This method could be in a separate package.
func InsertProduct(p *Product) octobe.Handler {
return func(scheme *octobe.Scheme) error {
seg := scheme.Segment(`
INSERT INTO
products(name)
VALUES($1)
RETURNING id
`)

seg.Arguments(p.Name)

// QueryRow and scan of RETURNING from query.
return seg.QueryRow(&p.ID)
}
}
```
name := uuid.New().String()

### Transaction
// Insert a new product into the database, and return a Product struct.
product1, err := postgres.Execute(session, AddProduct(name))
if err != nil {
panic(err)
}

Run a query with transaction, and chain handlers.
// Select the product from the database by name, and return a Product struct.
product2, err := postgres.Execute(session, ProductByName(name))
if err != nil {
panic(err)
}

```go
func Method(db *sql.DB, ctx context.Context) error {
ob := octobe.New(db)
scheme, err := ob.BeginTx(ctx)
if err != nil {
return err
}

// WatchRollback returns error that is defined in the scope of this Method.
// if err is not nil, octobe will perform a rollback.
defer scheme.WatchRollback(func() error {
return err
})

p := Product{Name: "home made baguette"}
err = scheme.Handle(InsertProduct(&p))
if err != nil {
return err
}

// Finish with a commit
return scheme.Commit()
// Commit the transaction, if err is not nil, the transaction will be rolled back via WatchRollback.
err = session.Commit()
if err != nil {
panic(err)
}
}

// InsertProduct will take a pointer of a product, and insert it
// This method could be in a separate package.
func InsertProduct(p *Product) octobe.Handler {
return func(scheme *octobe.Scheme) error {
seg := scheme.Segment(`
INSERT INTO
products(name)
VALUES($1)
RETURNING id
`)

seg.Arguments(p.Name)

// QueryRow and scan of RETURNING from query.
return seg.QueryRow(&p.ID)
}
// Product is a model that represents a product in the database
type Product struct {
ID int
Name string
}
```

### Basic Handler

Run a simple query where you chain database handlers.

```go
func Method(db *sql.DB, ctx context.Context) error {
// New creates an octobe context around an *sql.DB instance
// SuppressError is option and can be used to ignore specific errors, like sql.ErrNoRows"
ob := octobe.New(db, octobe.SuppressError(sql.ErrNoRows))

// Begin is used to start without a database transaction
scheme := ob.Begin(ctx)

var p1 Product
// Handle a database query in another method, perfect for separating out queries to a database package
err := scheme.Handle(SelectNameHandler(1, &p1))
if err != nil {
return err
}

var p2 Product
err := scheme.Handle(SelectNameHandler(2, &p2))
if err != nil {
return err
}

return
// AddProduct is an octobe handler that will insert a product into the database, and return a product model.
// In the octobe.Handler signature the first generic is the type of driver builder, and the second is the returned type.
func AddProduct(name string) postgres.Handler[Product] {
return func(builder postgres.Builder) (Product, error) {
var product Product
query := builder(`
INSERT INTO products (name) VALUES ($1) RETURNING id, name;
`)

query.Arguments(name)
err := query.QueryRow(&product.ID, &product.Name)
return product, err
}
}

// Handler func that implements the octobe.Handler
func SelectNameHandler(id string, p *Product) octobe.Handler {
return func(scheme *octobe.Scheme) error {
// A segment is a specific query, you can chain many queries in here, or split chained logic into multiple handler funcs if you'd like.
seg := scheme.Segment(`
SELECT name FROM products WHERE id = $1;
`)

// Arguments takes any input to the query
seg.Arguments(id)
// ProductByName is an octobe handler that will select a product from the database by name, and return a product model.
// In the octobe.Handler signature the first generic is the type of driver builder, and the second is the returned type.
func ProductByName(name string) postgres.Handler[Product] {
return func(builder postgres.Builder) (Product, error) {
var product Product
query := builder(`
SELECT id, name FROM products WHERE name = $1;
`)

// Segment has all the normal methods you expect such as QueryRow, Query and Exec.
return seg.QueryRow(&p.Name)
}
query.Arguments(name)
err := query.QueryRow(&product.ID, &product.Name)
return product, err
}
}
```

Expand Down
27 changes: 27 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: '3.8'

services:
postgres:
image: postgres:latest
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: testdb
ports:
- "5432:5432"
test:
image: golang:1.21
environment:
DSN: postgresql://user:password@postgres:5432/testdb?sslmode=disable&connect_timeout=10
depends_on:
- postgres
volumes:
- type: bind
source: ./
target: /workspace
working_dir: /workspace
command: >
bash -c "
go install gotest.tools/gotestsum@latest &&
gotestsum -- -coverprofile=coverage.txt -timeout=10s -v -count=1 -coverpkg=./... -covermode=atomic ./driver/...
"
20 changes: 0 additions & 20 deletions docker-compose.yml

This file was deleted.

Loading

0 comments on commit dc965bd

Please sign in to comment.