From 588e354d9c61779820519befb17841c36d5a042d Mon Sep 17 00:00:00 2001 From: AnD00 <19258109+AnD00@users.noreply.github.com> Date: Fri, 26 Feb 2021 15:31:34 +0900 Subject: [PATCH 1/6] Update README. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fd57a29..36bc1d2 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 ) From d6a09a571ee442c0e29a376be8224e2c7ab5c0bd Mon Sep 17 00:00:00 2001 From: AnD00 <19258109+AnD00@users.noreply.github.com> Date: Fri, 26 Feb 2021 16:59:34 +0900 Subject: [PATCH 2/6] Update go.mod and go.sum. --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 80b26b2..7c7d595 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/fatih/color v0.0.0-20160317093153-533cd7fd8a85 github.com/go-sql-driver/mysql v1.5.0 github.com/jessevdk/go-flags v0.0.0-20170212220246-460c7bb0abd6 + github.com/lib/pq v1.9.0 github.com/mattn/go-colorable v0.0.0-20160220075935-9cbef7c35391 // indirect github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7 // indirect github.com/mattn/go-sqlite3 v0.0.0-20170407154627-cf7286f069c3 diff --git a/go.sum b/go.sum index 90c18e9..1d1d0e3 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.0.0-20160220075935-9cbef7c35391 h1:x4vT4RoTH2BNkPx0LgrBKeFuPQPviK1aSAIWG6ruc+Y= github.com/mattn/go-colorable v0.0.0-20160220075935-9cbef7c35391/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.0-20151211000621-56b76bdf51f7 h1:owMyzMR4QR+jSdlfkX9jPU3rsby4++j99BfbtgVr6ZY= From 6d201aea330539a72afa2829b30691101dd1957d Mon Sep 17 00:00:00 2001 From: AnD00 <19258109+AnD00@users.noreply.github.com> Date: Fri, 26 Feb 2021 16:59:51 +0900 Subject: [PATCH 3/6] Add lib/pq to glide.yaml. --- glide.lock | 29 +++++++++++++++++------------ glide.yaml | 1 + 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/glide.lock b/glide.lock index 742465e..9856842 100644 --- a/glide.lock +++ b/glide.lock @@ -1,16 +1,6 @@ -hash: a53ba18529b4efb8804dfa0ea9c635971afa262a64a68650a1795b70f8c9bcc3 -updated: 2018-11-26T12:18:29.992047+09:00 +hash: 434d5eadc5255573660bd11ee4665a91d946842bd375653340b74979afe25874 +updated: 2021-02-22T23:25:04.176645+09:00 imports: -- name: github.com/deckarep/golang-set - version: 699df6a3acf6867538e50931511e9dc403da108a -- name: github.com/fatih/color - version: 533cd7fd8a85905f67a1753afb4deddc85ea174f -- name: github.com/go-sql-driver/mysql - version: 17ef3dd9d98b69acec3e85878995ada9533a9370 -- name: github.com/jessevdk/go-flags - version: 460c7bb0abd6e927f2767cadc91aa6ef776a98b4 -- name: github.com/juju/errors - version: c7d06af17c68cd34c835053720b21f6549d9b0ee - name: github.com/blastrain/vitess-sqlparser version: a139afbb1abad0805ea6a4e2c0fd4e513a3e0939 subpackages: @@ -33,6 +23,21 @@ imports: - tidbparser/dependency/util/memory - tidbparser/parser - tidbparser/parser/opcode +- name: github.com/deckarep/golang-set + version: 699df6a3acf6867538e50931511e9dc403da108a +- name: github.com/fatih/color + version: 533cd7fd8a85905f67a1753afb4deddc85ea174f +- name: github.com/go-sql-driver/mysql + version: 17ef3dd9d98b69acec3e85878995ada9533a9370 +- name: github.com/jessevdk/go-flags + version: 460c7bb0abd6e927f2767cadc91aa6ef776a98b4 +- name: github.com/juju/errors + version: c7d06af17c68cd34c835053720b21f6549d9b0ee +- name: github.com/lib/pq + version: 4604d39ddc9f62e2ca152114c73aec99b77a3468 + subpackages: + - oid + - scram - name: github.com/mattn/go-colorable version: 9cbef7c35391cca05f15f8181dc0b18bc9736dbb repo: https://github.com/mattn/go-colorable diff --git a/glide.yaml b/glide.yaml index 9704c7a..84e966d 100644 --- a/glide.yaml +++ b/glide.yaml @@ -10,6 +10,7 @@ import: - package: github.com/mattn/go-sqlite3 - package: github.com/go-sql-driver/mysql - package: github.com/schemalex/schemalex +- package: github.com/lib/pq testImport: - package: gopkg.in/gorp.v1 - package: github.com/gocraft/dbr From ae5c57f9a3abdba14a2af45212681ec4e8076255 Mon Sep 17 00:00:00 2001 From: AnD00 <19258109+AnD00@users.noreply.github.com> Date: Fri, 26 Feb 2021 17:01:04 +0900 Subject: [PATCH 4/6] Add install command for postgres. --- cmd/octillery/main.go | 9 ++++++--- config/config.go | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/octillery/main.go b/cmd/octillery/main.go index bc05dfb..93ce8e0 100644 --- a/cmd/octillery/main.go +++ b/cmd/octillery/main.go @@ -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 @@ -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 { diff --git a/config/config.go b/config/config.go index 5332160..8e1b775 100644 --- a/config/config.go +++ b/config/config.go @@ -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 From 4191e95f36c8c4ef58173abfba9aca5dff86819d Mon Sep 17 00:00:00 2001 From: AnD00 <19258109+AnD00@users.noreply.github.com> Date: Fri, 26 Feb 2021 17:01:42 +0900 Subject: [PATCH 5/6] Add postgresql adapter. --- .gitignore | 1 + Makefile | 2 +- connection/adapter/adapter.go | 2 +- connection/adapter/plugin/postgres.go | 106 ++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 connection/adapter/plugin/postgres.go diff --git a/.gitignore b/.gitignore index 13292d1..3c40d45 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ plugin/mysql.go +plugin/postgres.go plugin/sqlite3.go vendor diff --git a/Makefile b/Makefile index 3502b78..7327d85 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/connection/adapter/adapter.go b/connection/adapter/adapter.go index 4a7bdd1..863d48d 100644 --- a/connection/adapter/adapter.go +++ b/connection/adapter/adapter.go @@ -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 { diff --git a/connection/adapter/plugin/postgres.go b/connection/adapter/plugin/postgres.go new file mode 100644 index 0000000..ecde5d4 --- /dev/null +++ b/connection/adapter/plugin/postgres.go @@ -0,0 +1,106 @@ +package plugin + +import ( + "database/sql" + "fmt" + "strings" + + postgres "github.com/lib/pq" + "github.com/pkg/errors" + "go.knocknote.io/octillery/config" + "go.knocknote.io/octillery/connection/adapter" + osql "go.knocknote.io/octillery/database/sql" + osqldriver "go.knocknote.io/octillery/database/sql/driver" + "go.knocknote.io/octillery/debug" + "go.knocknote.io/octillery/internal" +) + +// PostgreSQLAdapter implements DBAdapter interface. +type PostgreSQLAdapter struct { +} + +const ( + pluginName = "postgres" +) + +func init() { + if internal.IsLoadedPlugin(pluginName) { + return + } + var driver interface{} + driver = postgres.Driver{} + if drv, ok := driver.(osqldriver.Driver); ok { + // postgres package's import statement is already replaced to "go.knocknote.io/octillery/database/sql" + osql.RegisterByOctillery(pluginName, drv) + } else { + // In this case, postgres package already call `sql.Register("postgres", &Driver{})`. + // So, octillery skip driver registration + } + adapter.Register(pluginName, &PostgreSQLAdapter{}) + internal.SetLoadedPlugin(pluginName) +} + +// CurrentSequenceID get current unique id for all shards by sequencer +func (adapter *PostgreSQLAdapter) CurrentSequenceID(conn *sql.DB, tableName string) (int64, error) { + var seqID int64 + if err := conn.QueryRow(fmt.Sprintf("select last_value from %s", sequenceName(tableName))).Scan(&seqID); err != nil { + return 0, errors.Wrap(err, "cannot select last_value") + } + return seqID, nil +} + +// NextSequenceID get next unique id for all shards by sequencer +func (adapter *PostgreSQLAdapter) NextSequenceID(conn *sql.DB, tableName string) (int64, error) { + var seqID int64 + if err := conn.QueryRow(fmt.Sprintf("select nextval('%s')", sequenceName(tableName))).Scan(&seqID); err != nil { + return 0, errors.Wrapf(err, "cannot select nextval('%s')", sequenceName(tableName)) + } + return seqID, nil +} + +// ExecDDL do nothing +func (adapter *PostgreSQLAdapter) ExecDDL(config *config.DatabaseConfig) error { + return nil +} + +// OpenConnection open connection by database configuration file +func (adapter *PostgreSQLAdapter) OpenConnection(config *config.DatabaseConfig, queryString string) (*sql.DB, error) { + if len(config.Masters) > 1 { + return nil, errors.New("Sorry, currently supports single master database only") + } + dbname := config.NameOrPath + for _, master := range config.Masters { + dsn := fmt.Sprintf("%s://%s:%s@%s/%s?sslmode=disable&%s", pluginName, config.Username, config.Password, master, dbname, queryString) + debug.Printf("dsn = %s", strings.Replace(dsn, "%", "%%", -1)) + conn, err := sql.Open(config.Adapter, dsn) + if err != nil { + return nil, errors.WithStack(err) + } + return conn, nil + } + for _, slave := range config.Slaves { + dsn := fmt.Sprintf("%s://%s:%s@%s/%s?sslmode=disable&%s", pluginName, config.Username, config.Password, slave, dbname, queryString) + debug.Printf("TODO: not support slave. dsn = %s", dsn) + break + } + for _, backup := range config.Backups { + dsn := fmt.Sprintf("%s://%s:%s@%s/%s?sslmode=disable&%s", pluginName, config.Username, config.Password, backup, dbname, queryString) + debug.Printf("TODO: not support backup. dsn = %s", dsn) + } + return nil, errors.New("must define 'master' server") +} + +// CreateSequencerTableIfNotExists create table for sequencer if not exists +func (adapter *PostgreSQLAdapter) CreateSequencerTableIfNotExists(conn *sql.DB, tableName string) error { + _, err := conn.Exec(fmt.Sprintf(`CREATE SEQUENCE IF NOT EXISTS %s AS integer;`, sequenceName(tableName))) + return errors.Wrap(err, "cannot create table for sequencer") +} + +// InsertRowToSequencerIfNotExists do nothing +func (adapter *PostgreSQLAdapter) InsertRowToSequencerIfNotExists(conn *sql.DB, tableName string) error { + return nil +} + +func sequenceName(tableName string) string { + return fmt.Sprintf("%s_id_seq", tableName) +} From b82d5ff86b890be2ab11610e9318a27d064c0a08 Mon Sep 17 00:00:00 2001 From: AnD00 <19258109+AnD00@users.noreply.github.com> Date: Fri, 26 Feb 2021 17:02:19 +0900 Subject: [PATCH 6/6] Add examples for postgres. --- _examples/dbr/postgres/databases.yml | 23 +++++ _examples/dbr/postgres/main.go | 92 ++++++++++++++++++++ _examples/gorp/postgres/databases.yml | 26 ++++++ _examples/gorp/postgres/main.go | 121 ++++++++++++++++++++++++++ 4 files changed, 262 insertions(+) create mode 100644 _examples/dbr/postgres/databases.yml create mode 100644 _examples/dbr/postgres/main.go create mode 100644 _examples/gorp/postgres/databases.yml create mode 100644 _examples/gorp/postgres/main.go diff --git a/_examples/dbr/postgres/databases.yml b/_examples/dbr/postgres/databases.yml new file mode 100644 index 0000000..510318c --- /dev/null +++ b/_examples/dbr/postgres/databases.yml @@ -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 diff --git a/_examples/dbr/postgres/main.go b/_examples/dbr/postgres/main.go new file mode 100644 index 0000000..39b4e6c --- /dev/null +++ b/_examples/dbr/postgres/main.go @@ -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")) + } +} diff --git a/_examples/gorp/postgres/databases.yml b/_examples/gorp/postgres/databases.yml new file mode 100644 index 0000000..3d141a9 --- /dev/null +++ b/_examples/gorp/postgres/databases.yml @@ -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 diff --git a/_examples/gorp/postgres/main.go b/_examples/gorp/postgres/main.go new file mode 100644 index 0000000..fe71fa3 --- /dev/null +++ b/_examples/gorp/postgres/main.go @@ -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) + } +}