API Reference
Constants
TableName
DynamoDB table name.
const TableName = "table-name"
Column
Table column names.
const ColumnId = "id"
const ColumnEmail = "email"
const ColumnTimestamp = "timestamp"
Column naming convention:
All defined columns start with Column
and follow CamelCase syntax.
Index
Names of secondary indexes.
const IndexEmailIndex = "email-index"
Index naming convention:
All defined indexes start with Index
and follow CamelCase syntax.
Attribute
Slice of strings with all attribute names from the DynamoDB table.
var AttributeNames = []string{"id", "timestamp", "email"}
KeyAttribute
Slice of strings containing the primary key attributes of the DynamoDB table.
var KeyAttributeNames = []string{"id", "timestamp"}
Data Structs
SchemaItem
Structure representing a single record in DynamoDB.
type SchemaItem struct {
Id string `dynamodbav:"id"`
Email string `dynamodbav:"email"`
Timestamp int64 `dynamodbav:"timestamp"`
}
TableSchema
Global variable of type DynamoSchema
that contains all table metadata.
var TableSchema = DynamoSchema{
TableName: "table-name",
HashKey: "id",
RangeKey: "timestamp",
// ...
}
More...
var TableSchema = DynamoSchema{
TableName: "user-profiles",
HashKey: "user_id",
RangeKey: "profile_type",
Attributes: []Attribute{
{Name: "user_id", Type: "S"},
{Name: "profile_type", Type: "S"},
{Name: "created_at", Type: "N"},
{Name: "status", Type: "S"},
},
CommonAttributes: []Attribute{
{Name: "email", Type: "S"},
{Name: "is_active", Type: "BOOL"},
{Name: "tags", Type: "SS"},
{Name: "scores", Type: "NS"},
},
SecondaryIndexes: []SecondaryIndex{
{
Name: "status-created-index",
HashKey: "status",
RangeKey: "created_at",
ProjectionType: "ALL",
},
{
Name: "category-profile-index",
HashKey: "category_id",
RangeKey: "profile_type",
ProjectionType: "INCLUDE",
NonKeyAttributes: []string{"email", "is_active"},
},
{
Name: "user-created-lsi",
HashKey: "user_id",
RangeKey: "created_at",
ProjectionType: "KEYS_ONLY",
HashKeyParts: []CompositeKeyPart{
{IsConstant: false, Value: "user_id"},
{IsConstant: true, Value: "PROFILE"},
},
RangeKeyParts: []CompositeKeyPart{
{IsConstant: false, Value: "created_at"},
{IsConstant: true, Value: "2024"},
},
},
},
FieldsMap: map[string]FieldInfo{
"user_id": {
DynamoType: "S",
IsKey: true,
IsHashKey: true,
IsRangeKey: false,
AllowedOperators: buildAllowedOperators("S"),
},
"profile_type": {
DynamoType: "S",
IsKey: true,
IsHashKey: false,
IsRangeKey: true,
AllowedOperators: buildAllowedOperators("S"),
},
"created_at": {
DynamoType: "N",
IsKey: false,
IsHashKey: false,
IsRangeKey: false,
AllowedOperators: buildAllowedOperators("N"),
},
"status": {
DynamoType: "S",
IsKey: false,
IsHashKey: false,
IsRangeKey: false,
AllowedOperators: buildAllowedOperators("S"),
},
"email": {
DynamoType: "S",
IsKey: false,
IsHashKey: false,
IsRangeKey: false,
AllowedOperators: buildAllowedOperators("S"),
},
"is_active": {
DynamoType: "BOOL",
IsKey: false,
IsHashKey: false,
IsRangeKey: false,
AllowedOperators: buildAllowedOperators("BOOL"),
},
"tags": {
DynamoType: "SS",
IsKey: false,
IsHashKey: false,
IsRangeKey: false,
AllowedOperators: buildAllowedOperators("SS"),
},
"scores": {
DynamoType: "NS",
IsKey: false,
IsHashKey: false,
IsRangeKey: false,
AllowedOperators: buildAllowedOperators("NS"),
},
},
}
QueryBuilder
With
/ Filter
With
(WithEQ, WithGT, etc.)
Applied BEFORE reading data from DynamoDB and determine which items will be read.Filter
(FilterEQ, FilterGT, etc.)
Applied AFTER reading data and affect only the returned result set.
NewQueryBuilder
Create new QueryBuilder
object.
func NewQueryBuilder() *QueryBuilder
qb.Limit
Sets a limit on the number of results.
func (qb *QueryBuilder) Limit(limit int) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
Limit(10)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.WithIndex
Explicitly specifies which secondary index
to use for the query instead of automatic selection.
func (qb *QueryBuilder) WithIndex(indexName string) *QueryBuilder
!!! Bug in v0.0.2 version.
method will not generate with min
mode.
Example
Multiple indexes schema:
{
"table_name": "user-orders",
"hash_key": "user_id",
"range_key": "order_id",
"attributes": [
{"name": "user_id", "type": "S"},
{"name": "order_id", "type": "S"},
{"name": "status", "type": "S"}
],
"secondary_indexes": [
{
"name": "lsi_by_status",
"type": "LSI",
"hash_key": "user_id",
"range_key": "status"
},
{
"name": "gsi_by_status",
"type": "GSI",
"hash_key": "status"
}
]
}
Query examples:
query1 := userorders.NewQueryBuilder().
WithEQ("user_id", "user123").
WithEQ("status", "active")
input1, _ := query1.BuildQuery()
fmt.Printf("Auto: %s\n", *input1.IndexName)
// Output: Auto: lsi_by_status
query2 := userorders.NewQueryBuilder().
WithEQ("user_id", "user123").
WithEQ("status", "active").
WithIndex("gsi_by_status")
input2, _ := query2.BuildQuery()
fmt.Printf("Forced: %s\n", *input2.IndexName)
// Output: Forced: gsi_by_status
Additional Notes
Without WithIndex:
- QueryBuilder automatically selects the optimal index
- Searches for a GSI/LSI that supports your keys
With WithIndex:
- QueryBuilder forcibly uses the specified index
- Ignores automatic selection
qb.StartFrom
Sets the starting key for pagination.
func (qb *QueryBuilder) StartFrom(
lastEvaluatedKey map[string]types.AttributeValue,
) *QueryBuilder
Example
var lastKey map[string]types.AttributeValue
query1 := NewQueryBuilder().
WithEQ("user_id", "123").
Limit(10)
result1, err := dynamoClient.Query(ctx, query1Input)
lastKey = result1.LastEvaluatedKey
query2 := NewQueryBuilder().
WithEQ("user_id", "123").
StartFrom(lastKey).
Limit(10)
LastEvaluatedKey
can be null
even if more data exists and the response size exceeds 1MB
.
Always check for LastEvaluatedKey to continue pagination.
qb.OrderByDesc
Sets descending order.
func (qb *QueryBuilder) OrderByDesc() *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
OrderByDesc()
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
OrderByDesc
only affects sorting by the sort key, not the filter results.
qb.OrderByAsc
Sets ascending order.
func (qb *QueryBuilder) OrderByAsc() *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
OrderByAsc()
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
OrderByAsc
only affects sorting by the sort key, not the filter results.
qb.WithPreferredSortKey
Indicates to the index selection algorithm a preferred sort key.
func (qb *QueryBuilder) WithPreferredSortKey(key string) *QueryBuilder
Example
// There are multiple indexes with the same hash key:
// - lsi_by_status (sort key: status)
// - lsi_by_created_at (sort key: created_at)
// - lsi_by_priority (sort key: priority)
query1 := userorders.NewQueryBuilder().
WithEQ("user_id", "user123").
WithEQ("status", "active")
// Can choose any matching index (e.g., lsi_by_status or lsi_by_created_at)
query2 := userorders.NewQueryBuilder().
WithEQ("user_id", "user123").
WithEQ("status", "active").
WithPreferredSortKey("created_at")
// Hints to prefer lsi_by_created_at if applicable
items, err := query2.Execute(ctx, dynamoClient)
When to Use WithPreferredSortKey
Use WithPreferredSortKey when:
- There are multiple indexes matching the query's partition key
- You want the results to be sorted by a specific sort key
- You know which index is more efficient or relevant for your use case
Important
WithPreferredSortKey Is a Hint, Not a Requirement
✅ The query planner prefers an index with the specified sort key
❌ But it may choose a different one if no suitable index is found
🎯 To force a specific index, use WithIndex(indexName)
instead
qb.With
Adds a condition for DynamoDB queries.
Accepts:
field
- field namevalue
- valueop
- operator type
func (qb *QueryBuilder) With(
field string,
op OperatorType,
values ...any,
) *QueryBuilder
Query impact:
All With
methods are applied BEFORE reading data from DynamoDB.
(This is faster and cheaper than using Filter
)
Example
query := NewQueryBuilder().With("user_id", EQ, "123")
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
Sugar
Methods are only generated when using the all
generation type:
godyno -s schema.json -o ./gen -mode all
godyno -s schema.json -o ./gen
In min
mode, use the generic With
method instead:
query
.With("user_id", EQ, "123")
.With("created_at", GT, timestamp)
query
.With("status", BETWEEN, "active", "pending")
.With("priority", LTE, 100)
qb.WithEQ
Adds an equal
condition for keys.
func (qb *QueryBuilder) WithEQ(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
WithEQ("created_at", timestamp).
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
qb.WithGT
Adds a greater than
condition for the range key.
func (qb *QueryBuilder) WithGT(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().WithGT("created_at", yesterdayTimestamp)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.WithLT
Adds a less than
condition for the range key.
func (qb *QueryBuilder) WithLT(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().WithLT("created_at", yesterdayTimestamp)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.WithGTE
Adds a greater than or equal to
condition for the range key.
func (qb *QueryBuilder) WithGTE(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().WithGTE("created_at", yesterdayTimestamp)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.WithLTE
Adds a less than or equal to
condition for the range key.
func (qb *QueryBuilder) WithLTE(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().WithLTE("created_at", yesterdayTimestamp)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.WithBetween
Adds a range condition
for the range key.
func (qb *QueryBuilder) WithBetween(field string, start, end any) *QueryBuilder
Example
query := NewQueryBuilder().WithBetween("created_at", yesterdayTimestamp, todayTimestamp)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.WithBeginsWith
Adds a begins with
condition for the range key.
func (qb *QueryBuilder) WithBeginsWith(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().WithBeginsWith("created_at", yesterdayTimestamp)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.WithProjection
Specifies which exact fields to return from DynamoDB instead of retrieving the entire item.
func (qb *QueryBuilder) WithProjection(attributes []string) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
WithProjection([]string{"id", "email", "created_at"})
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("ID: %s, Email: %s, Created: %s\n",
item.Id, item.Email, item.CreatedAt)
}
TIP
Without WithProjection:
type SchemaItem struct {
Id string // ✅
Name string // ✅
Email string // ✅
Description string // ✅ (not needed, but will be returned)
Content string // ✅ (not needed, but will be returned)
Tags []string // ✅ (not needed, but will be returned)
ViewCount int // ✅ (not needed, but will be returned)
}
With WithProjection:
// Only the specified fields will be returned
WithProjection([]string{"id", "name", "email"})
// Resulting struct:
type PartialItem struct {
Id string // ✅
Name string // ✅
Email string // ✅
// Description - omitted
// Content - omitted
// Tags - omitted
// ViewCount - omitted
}
Projection reduces bandwidth
usage but does NOT reduce RCU
cost — you're still billed for reading the full item.
qb.Filter
Adds a condition for filtering values retrieved from DynamoDB.
Accepts:
field
- field namevalue
- valueop
- operator type
func (qb *QueryBuilder) Filter(
field string,
op OperatorType,
values ...any,
) *QueryBuilder
Query impact:
All Filter
methods are applied AFTER reading data from DynamoDB.
(use with caution)
Example
query := NewQueryBuilder().
With("user_id", EQ, "123").
Filter("status", CONTAINS, "active")
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
Sugar
Methods are only generated when using the all
generation type:
godyno -s schema.json -o ./gen -mode all
godyno -s schema.json -o ./gen
In min
mode, use the generic Filter
method instead:
query
.Filter("status", EQ, "active")
.Filter("priority", BETWEEN, 80, 100)
qb.FilterEQ
Adds an equality
filter.
func (qb *QueryBuilder) FilterEQ(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterEQ("age", 18)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterNE
Adds an inequality
filter.
func (qb *QueryBuilder) FilterNE(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterNE("age", 18)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterGT
Adds a greater than
filter.
func (qb *QueryBuilder) FilterGT(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterGT("age", 18)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterLT
Adds a less than
filter.
func (qb *QueryBuilder) FilterLT(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterLT("age", 18)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterGTE
Adds a greater than or equal to
filter.
func (qb *QueryBuilder) FilterGTE(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterGTE("age", 18)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterLTE
Adds a less than or equal to
filter.
func (qb *QueryBuilder) FilterLTE(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterLTE("age", 18)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterBetween
Adds a range
filter.
func (qb *QueryBuilder) FilterBetween(field string, start, end any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterBetween("age", 18, 35)
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterContains
Adds a contains
filter.
func (qb *QueryBuilder) FilterContains(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterContains("email", "@gmail.com")
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterNotContains
Adds a not contains
filter.
func (qb *QueryBuilder) FilterNotContains(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterNotContains("email", "@gmail.com")
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterBeginsWith
Adds a begins with
filter.
func (qb *QueryBuilder) FilterBeginsWith(field string, value any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterBeginsWith("email", "alex")
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterIn
Adds an in list
filter.
func (qb *QueryBuilder) FilterIn(field string, values ...any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterIn("email", []string{"alex@gmail.com", "john@gmail.com"})
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterNotIn
Adds a not in list
filter.
func (qb *QueryBuilder) FilterNotIn(field string, values ...any) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterNotIn("email", []string{"alex@gmail.com", "john@gmail.com"})
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterExists
Adds a field exists
filter.
func (qb *QueryBuilder) FilterExists(field string) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterExists("email")
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.FilterNotExists
Adds a field does not exist
filter.
func (qb *QueryBuilder) FilterNotExists(field string) *QueryBuilder
Example
query := NewQueryBuilder().
WithEQ("user_id", "123").
FilterNotExists("email")
queryInput, err := query.BuildQuery()
if err != nil {
return err
}
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
qb.BuildQuery
Builds a DynamoDB QueryInput
.
func (qb *QueryBuilder) BuildQuery() (*dynamodb.QueryInput, error)
qb.Execute
Executes the query.
func (qb *QueryBuilder) Execute(
ctx context.Context,
client *dynamodb.Client,
) (
[]SchemaItem,
error,
)
ScanBuilder
Scan reads the entire table.
NewScanBuilder
Create new ScanBuilder
.
func NewScanBuilder() *ScanBuilder
sb.WithIndex
Explicitly specifies which secondary index to use for the query, overriding automatic index selection.
func (sb *ScanBuilder) WithIndex(indexName string) *ScanBuilder
Performs a scan on a specific index
- GSI (Global Secondary Index) has its own RCU/WCU configuration
- LSI (Local Secondary Index) shares RCU/WCU with the base table
Example
scan := NewScanBuilder().
WithIndex("status-index").
FilterEQ("status", "active")
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.Limit
Sets the maximum number of items.
func (sb *ScanBuilder) Limit(limit int) *ScanBuilder
Example
scan := NewScanBuilder().
FilterEQ("status", "active").
Limit(10)
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.StartFrom
Sets the starting key for pagination.
func (sb *ScanBuilder) StartFrom(
lastEvaluatedKey map[string]types.AttributeValue,
) *ScanBuilder
Pagination
LastEvaluatedKey
can be null
even if there is more data and the response size exceeds 1MB
.
Always check for LastEvaluatedKey to continue pagination.
Example
var lastKey map[string]types.AttributeValue
scan1 := NewScanBuilder().
FilterEQ("status", "active").
Limit(10)
result1, err := dynamoClient.Scan(ctx, scan1Input)
lastKey = result1.LastEvaluatedKey
scan2 := NewScanBuilder().
FilterEQ("status", "active").
StartFrom(lastKey).
Limit(10)
sb.WithProjection
Specifies which exact fields to return from DynamoDB instead of all item attributes.
func (sb *ScanBuilder) WithProjection(attributes []string) *ScanBuilder
INFO
Without WithProjection:
type SchemaItem struct {
Id string // ✅
Name string // ✅
Email string // ✅
Description string // ✅ (not needed, but will be returned)
Content string // ✅ (not needed, but will be returned)
Tags []string // ✅ (not needed, but will be returned)
ViewCount int // ✅ (not needed, but will be returned)
}
With WithProjection:
// Returns ONLY the specified fields
WithProjection([]string{"id", "name", "email"})
// As a result, only:
type PartialItem struct {
Id string // ✅
Name string // ✅
Email string // ✅
// Description - missing
// Content - missing
// Tags - missing
// ViewCount - missing
}
Projection reduces bandwidth
usage but does NOT reduce RCU
— you are billed for reading all item attributes.
Example
scan := NewScanBuilder().
FilterEQ("status", "active").
WithProjection([]string{"id", "email", "created_at"})
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("ID: %s, Email: %s, Created: %s\n",
item.Id, item.Email, item.CreatedAt)
}
sb.WithParallelScan
func (sb *ScanBuilder) WithParallelScan(
totalSegments,
segment int,
) *ScanBuilder
Parallel Scan
Increases RCU consumption proportionally to the number of segments.
Use with caution in production environments.
sb.Filter
Adds a condition to filter the values retrieved from DynamoDB.
Accept:
field
- field namevalue
- valueop
- operator type
func (sb *ScanBuilder) Filter(
field string,
op OperatorType,
values ...any,
) *ScanBuilder
Example
scan := NewScanBuilder().
Filter("user_id", EQ, "123").
Filter("status", CONTAINS, "active")
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
Sugar
Methods are only generated when using the all
generation type:
godyno -s schema.json -o ./gen -mode all
godyno -s schema.json -o ./gen
In min
mode, use the generic Filter
method instead:
scan
.Filter("status", EQ, "active")
.Filter("priority", BETWEEN, 80, 100)
sb.FilterEQ
Adds an equality
filter.
func (sb *ScanBuilder) FilterEQ(field string, value any) *ScanBuilder
Example
scan := NewScanBuilder().
FilterEQ("user_id", "123").
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.FilterNE
Adds a not equal
filter.
func (sb *ScanBuilder) FilterNE(field string, value any) *ScanBuilder
Example
scan := NewScanBuilder().
FilterNE("user_id", "123").
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.FilterGT
Adds a greater than
filter.
func (sb *ScanBuilder) FilterGT(field string, value any) *ScanBuilder
Example
scan := NewScanBuilder().
FilterGT("age", 18).
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.FilterLT
Adds a less than
filter.
func (sb *ScanBuilder) FilterLT(field string, value any) *ScanBuilder
Example
scan := NewScanBuilder().
FilterLT("age", 18).
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.FilterGTE
Adds a greater than or equal
filter.
func (sb *ScanBuilder) FilterGTE(field string, value any) *ScanBuilder
Example
scan := NewScanBuilder().
FilterGTE("age", 18).
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.FilterLTE
Adds a less than or equal
filter.
func (sb *ScanBuilder) FilterLTE(field string, value any) *ScanBuilder
Example
scan := NewScanBuilder().
FilterLTE("age", 18).
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.FilterBetween
Adds a between
filter.
func (sb *ScanBuilder) FilterBetween(
field string,
start,
end any,
) *ScanBuilder
Example
scan := NewScanBuilder().
FilterBetween("age", 18, 35).
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.FilterContains
Adds a contains
filter.
func (sb *ScanBuilder) FilterContains(field string, value any) *ScanBuilder
Example
scan := NewScanBuilder().
FilterContains("email", "@gmail.com").
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.FilterNotContains
Adds a not contains
filter.
func (sb *ScanBuilder) FilterNotContains(
field string,
value any,
) *ScanBuilder
Example
scan := NewScanBuilder().
FilterContains("email", "@gmail.com").
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.FilterBeginsWith
Adds a begins with
filter.
func (sb *ScanBuilder) FilterBeginsWith(field string, value any) *ScanBuilder
Example
scan := NewScanBuilder().
FilterBeginsWith("email", "alex").
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Status: %s\n", item.UserId, item.Status)
}
sb.FilterIn
Adds an IN list
filter.
func (sb *ScanBuilder) FilterIn(field string, values ...any) *ScanBuilder
Example
scan := NewScanBuilder().
FilterIn("email", []string{"alex@gmail.com", "john@gmail.com"})
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
sb.FilterNotIn
Adds a NOT IN list
filter.
func (sb *ScanBuilder) FilterNotIn(field string, values ...any) *ScanBuilder
Example
scan := NewScanBuilder().
FilterNotIn("email", []string{"alex@gmail.com", "john@gmail.com"})
items, err := query.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
sb.FilterExists
Adds a NOT NULL
filter.
func (sb *ScanBuilder) FilterExists(field string) *ScanBuilder
Example
scan := NewScanBuilder().
FilterExists("email")
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
sb.FilterNotExists
Adds a Empty
filter.
func (sb *ScanBuilder) FilterNotExists(field string) *ScanBuilder
Example
scan := NewScanBuilder().
FilterNotExists("email")
items, err := scan.Execute(ctx, dynamoClient)
if err != nil {
return err
}
for _, item := range items {
fmt.Printf("User: %s, Created: %s\n", item.UserId, item.CreatedAt)
}
sb.BuildScan
Builds a DynamoDB ScanInput.
func (sb *ScanBuilder) BuildScan() (*dynamodb.ScanInput, error)
sb.Execute
Executes the scan operation.
func (sb *ScanBuilder) Execute(
ctx context.Context,
client *dynamodb.Client,
) (
[]SchemaItem,
error,
)
Input Functions
ItemInput
Converts a SchemaItem
into a DynamoDB AttributeValue
map.
func ItemInput(item SchemaItem) (map[string]types.AttributeValue, error)
KeyInput
Creates a key from hash and range key values.
func KeyInput(
hashKeyValue,
rangeKeyValue any,
) (
map[string]types.AttributeValue,
error,
)
rangeKeyValue
can be nil
if the table uses only a hash key_
KeyInputFromRaw
Creates a key from raw values with validation.
func KeyInputFromRaw(
hashKeyValue,
rangeKeyValue any,
) (
map[string]types.AttributeValue,
error,
)
KeyInputFromItem
Extracts the key from a SchemaItem.
func KeyInputFromItem(
item SchemaItem
) (
map[string]types.AttributeValue,
error,
)
UpdateItemInput
Converts a SchemaItem into a DynamoDB UpdateItemInput.
UpdateItemInput(item SchemaItem) (*dynamodb.DeleteItemInput, error)
UpdateItemInputFromRaw
Creates an UpdateItemInput from raw values.
func UpdateItemInputFromRaw(
hashKeyValue,
rangeKeyValue any,
updates map[string]any,
) (
*dynamodb.UpdateItemInput,
error,
)
UpdateItemInputWithCondition
Creates an UpdateItemInput with a condition expression.
func UpdateItemInputWithCondition(
hashKeyValue,
rangeKeyValue any,
updates map[string]any,
conditionExpression string,
conditionAttributeNames map[string]string,
conditionAttributeValues map[string]types.AttributeValue,
) (
*dynamodb.UpdateItemInput,
error,
)
UpdateItemInputWithExpression
Creates an UpdateItemInput using expression builders.
func UpdateItemInputWithExpression(
hashKeyValue,
rangeKeyValue any,
updateBuilder expression.UpdateBuilder,
conditionBuilder *expression.ConditionBuilder,
) (
*dynamodb.UpdateItemInput,
error,
)
DeleteItemInput
Converts a SchemaItem into a DynamoDB DeleteItemInput.
DeleteItemInput(item SchemaItem) (*dynamodb.DeleteItemInput, error)
DeleteItemInputFromRaw
Creates a DeleteItemInput from key values.
func DeleteItemInputFromRaw(
hashKeyValue,
rangeKeyValue any,
) (
*dynamodb.DeleteItemInput,
error,
)
DeleteItemInputWithCondition
Creates a DeleteItemInput with a condition expression.
func DeleteItemInputWithCondition(
hashKeyValue,
rangeKeyValue any,
conditionExpression string,
expressionAttributeNames map[string]string,
expressionAttributeValues map[string]types.AttributeValue,
) (
*dynamodb.DeleteItemInput,
error,
)
BatchItemsInput
Converts a slice of SchemaItem
into a slice of AttributeValue
maps.
func BatchItemsInput(
items []SchemaItem,
) (
[]map[string]types.AttributeValue,
error,
)
Maximum 25
items per batch operation.
Exceeding the limit will result in an error.
BatchDeleteItemsInput
Creates BatchWriteItemInput
for deleting items.
func BatchDeleteItemsInput(
keys []map[string]types.AttributeValue,
) (
*dynamodb.BatchWriteItemInput,
error,
)
Maximum of 25
items per batch operation.
Exceeding the limit will result in an error.
BatchDeleteItemsInputFromRaw
Creates BatchWriteItemInput
from SchemaItems.
func BatchDeleteItemsInputFromRaw(
items []SchemaItem,
) (
*dynamodb.BatchWriteItemInput,
error,
)
Maximum of 25
items per batch operation.
Exceeding the limit will result in an error.
Stream Functions
Methods are generated only when using the all
generation type.
godyno -s schema.json -o ./gen -mode all
godyno -s schema.json -o ./gen
ExtractFromDynamoDBStreamEvent
Extracts the new state of the item from a stream record.
func ExtractFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*SchemaItem, error)
ExtractOldFromDynamoDBStreamEvent
Extracts the old state of the item from a stream record.
func ExtractOldFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*SchemaItem, error)
ExtractBothFromDynamoDBStreamEvent
Extracts the old and new state of the item from a stream record.
func ExtractBothFromDynamoDBStreamEvent(
dbEvent events.DynamoDBEventRecord,
) (
*SchemaItem,
*SchemaItem,
error,
)
CreateTriggerHandler
Create DynamoDB Event Handler.
func CreateTriggerHandler(
onInsert func(context.Context, *SchemaItem) error,
onModify func(context.Context, *SchemaItem, *SchemaItem) error,
onDelete func(context.Context, map[string]events.DynamoDBAttributeValue) error,
) func(ctx context.Context, event events.DynamoDBEvent) error
TIP
- onInsert — called for INSERT events, receives the new SchemaItem
- onModify — called for MODIFY events, receives the old and new SchemaItem
- onDelete — called for REMOVE events, receives the keys of the deleted item
IsFieldModified
Checks whether a given attribute was actually changed in a DynamoDB MODIFY stream event. It compares the old and new images and returns true only if the field was added, removed, or its value differs.
func IsFieldModified(
record events.DynamoDBEventRecord,
fieldName string,
) bool
return true
if:
- the event is MODIFY and the field previously did not exist but now does
- the event is MODIFY and the field previously existed but now does not
- the event is MODIFY and the field existed in both images and its serialized value differs
Operators
Key Conditions VS Filters
Key Conditions - applied BEFORE
reading:
- Define which items to read from DynamoDB
- Affect the cost of the operation (RCU)
- Only support: [
EQ
,GT
,LT
,GTE
,LTE
,BETWEEN
,BEGINS_WITH
] EQ
is required for the partition key- Other operators apply only to the sort key
Filter Expressions - applied AFTER
reading:
- Filter the data after it has been read
- Do NOT affect the cost (you pay for all read items)
- Support ALL operators
- Filter-only operators: [
CONTAINS
,NOT_CONTAINS
,IN
,NOT_IN
,EXISTS
,NOT_EXISTS
,NE
]
Recommendation:
Use key conditions as much as possible, and filters only for additional refinement.
OperatorType
type OperatorType string
Constants
const (
EQ OperatorType = "="
NE OperatorType = "<>"
GT OperatorType = ">"
LT OperatorType = "<"
GTE OperatorType = ">="
LTE OperatorType = "<="
BETWEEN OperatorType = "BETWEEN"
CONTAINS OperatorType = "CONTAINS"
NOT_CONTAINS OperatorType = "NOT_CONTAINS"
BEGINS_WITH OperatorType = "BEGINS_WITH"
IN OperatorType = "IN"
NOT_IN OperatorType = "NOT_IN"
EXISTS OperatorType = "EXISTS"
NOT_EXISTS OperatorType = "NOT_EXISTS"
)
ValidateValues
Validates the number of values for an operator.
func ValidateValues(op OperatorType, values []any) bool
IsKeyConditionOperator
Checks if the operator can be used in key conditions.
func IsKeyConditionOperator(op OperatorType) bool
ValidateOperator
Checks if the operator is compatible with the field.
func ValidateOperator(fieldName string, op OperatorType) bool
BuildConditionExpression
Creates a filter condition.
func BuildConditionExpression(
field string,
op OperatorType,
values []any,
) (
expression.ConditionBuilder,
error,
)
BuildKeyConditionExpression
Creates a key condition.
func BuildKeyConditionExpression(
field string,
op OperatorType,
values []any,
) (
expression.KeyConditionBuilder,
error,
)