Home

Awesome

godynamo

Go Report Card PkgGoDev Actions Status codecov Release

Go driver for AWS DynamoDB which can be used with the standard database/sql package.

Usage

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/btnguyen2k/godynamo"
)

func main() {
	driver := "godynamo"
	dsn := "Region=<aws-region>;AkId=<access-key-id>;SecretKey=<secret-key>"
	db, err := sql.Open(driver, dsn)
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// db instance is ready to use 
	dbRows, err := db.Query(`LIST TABLES`)
	if err != nil {
		panic(err)
	}
	for dbRows.Next() {
		var val interface{}
		err := dbRows.Scan(&val)
		if err != nil {
			panic(err)
		}
		fmt.Println(val)
	}
}

Data Source Name (DSN) format for AWS DynamoDB

Note: line-break is for readability only!

Region=<aws-region>
;AkId=<aws-access-key-id>
;Secret_Key=<aws-secret-key>
[;Endpoint=<aws-dynamodb-endpoint>]
[TimeoutMs=<timeout-in-milliseconds>]

Using aws.Config:

Since v1.3.0, godynamo supports using aws.Config to create the connection to DynamoDB:

package main

import (
	"database/sql"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/btnguyen2k/godynamo"
)

func main() {
	driver := "godynamo"
	awscfg := aws.Config{
        Region: "<aws-region>",
        Credentials: aws.StaticCredentialsProvider{
            Value: aws.Credentials{
                AccessKeyID:     "<access-key-id>",
                SecretAccessKey: "<secret-key>",
			},
		},
    }
	godynamo.RegisterAWSConfig(awscfg)
	
	db, err := sql.Open(driver, "dummy")
	if err != nil {
		panic(err)
	}
	defer db.Close()
	
	// db instance is ready to use
}

Supported statements:

Transaction support

godynamo supports transactions that consist of write statements (e.g. INSERT, UPDATE and DELETE) since v0.2.0. Please note the following:

Example:

tx, err := db.Begin()
if err != nil {
	panic(err)
}
defer tx.Rollback()
result1, _ := tx.Exec(`INSERT INTO "tbltest" VALUE {'app': ?, 'user': ?, 'active': ?}`, "app0", "user1", true)
result2, _ := tx.Exec(`INSERT INTO "tbltest" VALUE {'app': ?, 'user': ?, 'duration': ?}`, "app0", "user2", 1.23)
err = tx.Commit()
if err != nil {
	panic(err)
}

rowsAffected1, err1 := fmt.Println(result1.RowsAffected())
if err1 != nil {
	panic(err1)
}
fmt.Println("RowsAffected:", rowsAffected1) // output "RowsAffected: 1"

rowsAffected2, err2 := fmt.Println(result2.RowsAffected())
if err2 != nil {
	panic(err2)
}
fmt.Println("RowsAffected:", rowsAffected2) // output "RowsAffected: 1"

If a statement's condition check fails (e.g. deleting non-existing item), the whole transaction will also fail. This behaviour is different from executing statements in non-transactional mode where failed condition check results in 0 affected row without error.

You can use EXISTS function for condition checking.

Notes on transactions:

conn, _ := db.Conn(context.Background())
tx, err := conn.BeginTx(context.Background(), nil)
if err != nil {
	panic(err)
}
result1, _ := tx.Exec(`INSERT INTO "tbltest" VALUE {'app': ?, 'user': ?, 'active': ?}`, "app0", "user1", true)

// the statement is added to the existing transaction
// also, result2.RowsAffected() is not available until the transaction is committed
result2, _ := conn.ExecContext(context.Background(), `INSERT INTO "tbltest" VALUE {'app': ?, 'user': ?, 'duration': ?}`, "app0", "user2", 1.23)

err = tx.Commit()
if err != nil {
	panic(err)
}

rowsAffected1, err1 := fmt.Println(result1.RowsAffected())
if err1 != nil {
	panic(err1)
}
fmt.Println("RowsAffected:", rowsAffected1) // output "RowsAffected: 1"

rowsAffected2, err2 := fmt.Println(result2.RowsAffected())
if err2 != nil {
	panic(err2)
}
fmt.Println("RowsAffected:", rowsAffected2) // output "RowsAffected: 1"

Caveats

Numerical values are stored in DynamoDB as floating point numbers. Hence, numbers are always read back as float64. See DynamoDB document for details on DynamoDB's supported data types.

A single query can only return up to 1MB of data. In the case of SELECT query, the driver automatically issues additional queries to fetch the remaining data if needed. However, returned rows may not be in the expected order specified by ORDER BY clause. That means, rows returned from the query SELECT * FROM table_name WHERE category='Laptop' ORDER BY id may not be in the expected order if all matched rows do not fit in 1MB of data.

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

Support and Contribution

Feel free to create pull requests or issues to report bugs or suggest new features. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue.

If you find this project useful, please star it.