Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PostgreSQL support #31

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
plugin/mysql.go
plugin/postgres.go
plugin/sqlite3.go
vendor
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ install_plugin:
go run cmd/octillery/main.go install --sqlite

uninstall_plugin:
rm -f plugin/{mysql,sqlite3}.go
rm -f plugin/{mysql,postgres,sqlite3}.go
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
`Octillery` is a Go package for sharding databases.
It can use with every OR Mapping library ( `xorm` , `gorp` , `gorm` , `dbr` ...) implementing `database/sql` interface, or raw SQL.

Currently supports `MySQL` (for product) and `SQLite3` (for testing) .
Currently supports `MySQL` (for product) and `PostgreSQL` (for testing) and `SQLite3` (for testing).

# Motivation

Expand All @@ -17,7 +17,7 @@ We need database sharding library in Go. Of course, we know some libraries like
- Supports every OR Mapping library implementing `database/sql` interface ( `xorm` , `gorp` , `gorm` , `dbr` , ... )
- Supports using `database/sql` ( raw SQL ) directly
- Pluggable sharding algorithm ( preinstalled algorithms are `modulo` and `hashmap` )
- Pluggable database adapter ( preinstalled adapters are `mysql` and `sqlite3` )
- Pluggable database adapter ( preinstalled adapters are `mysql` and `postgres` and `sqlite3` )
- Declarative describing for sharding configuration in `YAML`
- Configurable sharding algorithm, database adapter, sharding key, whether use sequencer or not.
- Supports capture read/write queries just before passing to database driver
Expand Down Expand Up @@ -196,7 +196,7 @@ Therefore, OR Mapping library call `Octillery`'s interface. and it can capture a

### How To Use New Database Adapter

`Octillery` supports `mysql` and `sqlite3` adapter by default.
`Octillery` supports `mysql` and `postgres` and `sqlite3` adapter by default.
If you want to use new database adapter, need to the following two steps.

1. Write `DBAdapter` interface. ( see https://godoc.org/go.knocknote.io/octillery/connection/adapter )
Expand Down
23 changes: 23 additions & 0 deletions _examples/dbr/postgres/databases.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
default: &default
database: octillery_dbr
adapter: postgres
encoding: utf8
username: postgres
master:
- localhost:5432

tables:
members:
shard: true
shard_column: id
algorithm: hashmap
sequencer:
<<: *default
database: octillery_dbr_members_seq
shards:
- member_shard_1:
<<: *default
database: octillery_dbr_members_shard_1
- member_shard_2:
<<: *default
database: octillery_dbr_members_shard_2
92 changes: 92 additions & 0 deletions _examples/dbr/postgres/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package main

import (
"errors"
"path/filepath"

"github.com/gocraft/dbr"
"go.knocknote.io/octillery"
"go.knocknote.io/octillery/path"
)

type Member struct {
ID int64 `db:"id"`
Number int64 `db:"number"`
Name string `db:"name"`
IsValid bool `db:"is_valid"`
}

func main() {
if err := octillery.LoadConfig(filepath.Join(path.ThisDirPath(), "databases.yml")); err != nil {
panic(err)
}
conn, err := dbr.Open("postgres", "postgres://postgres:@127.0.0.1:5432/octillery_dbr?sslmode=disable", nil)
if err != nil {
panic(err)
}
sess := conn.NewSession(nil)
if conn.DB != nil {
if _, err := conn.DB.Exec(`
CREATE TABLE IF NOT EXISTS members(
id serial NOT NULL PRIMARY KEY,
number integer NOT NULL,
name varchar(255),
is_valid boolean NOT NULL
)`); err != nil {
panic(err)
}
}
if _, err := sess.DeleteFrom("members").Exec(); err != nil {
panic(err)
}

result, err := sess.InsertInto("members").
Columns("number", "name", "is_valid").
Values(10, "Bob", true).
Exec()
if err != nil {
panic(err)
}

count, err := result.RowsAffected()
if err != nil {
panic(err)
}
if count != 1 {
panic(errors.New("cannot insert row"))
}

member := &Member{Number: 9, Name: "Ken", IsValid: false}

sess.InsertInto("members").
Columns("number", "name", "is_valid").
Record(member).
Exec()

var members []Member
sess.Select("*").From("members").Load(&members)

if len(members) != 2 {
panic(errors.New("cannot get members"))
}

attrsMap := map[string]interface{}{"number": 13, "name": "John"}
if _, err := sess.Update("members").
SetMap(attrsMap).
Where("id = ?", members[0].ID).
Exec(); err != nil {
panic(err)
}

var m Member
if _, err := sess.Select("*").
From("members").
Where("id = ?", members[0].ID).
Load(&m); err != nil {
panic(err)
}

if m.Name != "John" {
panic(errors.New("cannot update row"))
}
}
26 changes: 26 additions & 0 deletions _examples/gorp/postgres/databases.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
default: &default
database: octillery_gorp
adapter: postgres
encoding: utf8
username: postgres
master:
- localhost:5432

tables:
posts:
shard: true
shard_column: post_id
algorithm: hashmap
sequencer:
<<: *default
database: octillery_gorp_posts_seq
shards:
- post_shard_1:
<<: *default
database: octillery_gorp_posts_shard_1
- post_shard_2:
<<: *default
database: octillery_gorp_posts_shard_2
tests:
<<: *default
database: octillery_gorp_tests
121 changes: 121 additions & 0 deletions _examples/gorp/postgres/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package main

import (
"log"
"path/filepath"
"time"

"go.knocknote.io/octillery"
"go.knocknote.io/octillery/database/sql"
"go.knocknote.io/octillery/path"
"gopkg.in/gorp.v1"
)

func main() {
if err := octillery.LoadConfig(filepath.Join(path.ThisDirPath(), "databases.yml")); err != nil {
panic(err)
}
// initialize the DbMap
dbmap := initDb()
defer dbmap.Db.Close()

// delete any existing rows
err := dbmap.DropTablesIfExists()
checkErr(err, "DropTables failed")

// create the table. in a production system you'd generally
// use a migration tool, or create the tables via scripts
err = dbmap.CreateTablesIfNotExists()
checkErr(err, "Create tables failed")

// create two posts
p1 := newPost("Go 1.1 released!", "Lorem ipsum lorem ipsum")
p2 := newPost("Go 1.2 released!", "Lorem ipsum lorem ipsum")

// insert rows - auto increment PKs will be set properly after the insert
err = dbmap.Insert(&p1, &p2)
checkErr(err, "Insert failed")

// use convenience SelectInt
count, err := dbmap.SelectInt("select count(*) from posts")
checkErr(err, "select count(*) failed")
log.Println("Rows after inserting:", count)

// update a row
p2.Title = "Go 1.2 is better than ever"
count, err = dbmap.Update(&p2)
checkErr(err, "Update failed")
log.Println("Rows updated:", count)

// fetch one row - note use of "post_id" instead of "Id" since column is aliased
//
// Postgres users should use $1 instead of ? placeholders
// See 'Known Issues' below
//
err = dbmap.SelectOne(&p2, "select * from posts where post_id=?", p2.Id)
checkErr(err, "SelectOne failed")
log.Println("p2 row:", p2)

// fetch all rows
var posts []Post
_, err = dbmap.Select(&posts, "select * from posts order by post_id")
checkErr(err, "Select failed")
log.Println("All rows:")
for x, p := range posts {
log.Printf(" %d: %v\n", x, p)
}

// delete row by PK
count, err = dbmap.Delete(&p1)
checkErr(err, "Delete failed")
log.Println("Rows deleted:", count)

// delete row manually via Exec
_, err = dbmap.Exec("delete from posts where post_id=?", p2.Id)
checkErr(err, "Exec failed")

// confirm count is zero
count, err = dbmap.SelectInt("select count(*) from posts")
checkErr(err, "select count(*) failed")
log.Println("Row count - should be zero:", count)

log.Println("Done!")
}

type Post struct {
// db tag lets you specify the column name if it differs from the struct field
Id int64 `db:"post_id"`
Created int64
Title string
Body string
}

func newPost(title, body string) Post {
return Post{
Created: time.Now().UnixNano(),
Title: title,
Body: body,
}
}

func initDb() *gorp.DbMap {
// connect to db using standard Go database/sql API
// use whatever database/sql driver you wish
db, err := sql.Open("postgres", "postgres://postgres:@127.0.0.1:5432/octillery_dbr?sslmode=disable")
checkErr(err, "sql.Open failed")

// construct a gorp DbMap
dbmap := &gorp.DbMap{Db: db, Dialect: gorp.PostgresDialect{}}

// add a table, setting the table name to 'posts' and
// specifying that the Id property is an auto incrementing PK
dbmap.AddTableWithName(Post{}, "posts").SetKeys(true, "Id")

return dbmap
}

func checkErr(err error, msg string) {
if err != nil {
log.Fatalln(msg, err)
}
}
9 changes: 6 additions & 3 deletions cmd/octillery/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ type ConsoleCommand struct {

// InstallCommand type for install command
type InstallCommand struct {
MySQLAdapter bool `long:"mysql" description:"install mysql adapter"`
SQLiteAdapter bool `long:"sqlite" description:"install sqlite3 adapter"`
MySQLAdapter bool `long:"mysql" description:"install mysql adapter"`
PostgreSQLAdapter bool `long:"postgres" description:"install postgres adapter"`
SQLiteAdapter bool `long:"sqlite" description:"install sqlite3 adapter"`
}

// ShardCommand type for shard command
Expand Down Expand Up @@ -560,10 +561,12 @@ func (cmd *InstallCommand) installToPath(sourcePath string) error {
var adapterPath string
if cmd.MySQLAdapter {
adapterPath = filepath.Join(adapterBasePath, "mysql.go")
} else if cmd.PostgreSQLAdapter {
adapterPath = filepath.Join(adapterBasePath, "postgres.go")
} else if cmd.SQLiteAdapter {
adapterPath = filepath.Join(adapterBasePath, "sqlite3.go")
} else {
return errors.New("unknown adapter name. currently supports '--mysql' or '--sqlite' only")
return errors.New("unknown adapter name. currently supports '--mysql' or '--postgres' or '--sqlite' only")
}
adapterData, err := ioutil.ReadFile(adapterPath)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import (

// DatabaseConfig type for database definition
type DatabaseConfig struct {
// database name of MySQL or database file path of SQLite
// database name of MySQL or database name of PostgreSQL or database file path of SQLite
NameOrPath string `yaml:"database"`

// adapter name ( 'mysql' or 'sqlite3' )
// adapter name ( 'mysql' or 'postgres' or 'sqlite3' )
Adapter string `yaml:"adapter"`

// database encoding like utf8mb4
Expand Down
2 changes: 1 addition & 1 deletion connection/adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// DBAdapter is a adapter for common sequence each database driver.
//
// octillery currently supports mysql and sqlite3.
// octillery currently supports mysql and postgres and sqlite3.
// If use the other new adapter, implement the following interface as plugin ( new_adapter.go ) and call adapter.Register("adapter_name", &NewAdapterStructure{}).
// Also, new_adapter.go file should put inside go.knocknote.io/octillery/plugin directory.
type DBAdapter interface {
Expand Down
Loading