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() *QueryBuilderqb.Limit
Sets a limit on the number of results.
func (qb *QueryBuilder) Limit(limit int) *QueryBuilderExample
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_statusAdditional 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,
) *QueryBuilderExample
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() *QueryBuilderExample
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() *QueryBuilderExample
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) *QueryBuilderExample
// 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,
) *QueryBuilderQuery 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 ./genIn 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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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,
) *QueryBuilderQuery 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 ./genIn 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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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) *QueryBuilderExample
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() *ScanBuildersb.WithIndex
Explicitly specifies which secondary index to use for the query, overriding automatic index selection.
func (sb *ScanBuilder) WithIndex(indexName string) *ScanBuilderPerforms 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) *ScanBuilderExample
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,
) *ScanBuilderPagination
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) *ScanBuilderINFO
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,
) *ScanBuilderParallel 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,
) *ScanBuilderExample
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 ./genIn 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) *ScanBuilderExample
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) *ScanBuilderExample
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) *ScanBuilderExample
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) *ScanBuilderExample
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) *ScanBuilderExample
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) *ScanBuilderExample
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,
) *ScanBuilderExample
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) *ScanBuilderExample
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,
) *ScanBuilderExample
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) *ScanBuilderExample
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) *ScanBuilderExample
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) *ScanBuilderExample
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) *ScanBuilderExample
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) *ScanBuilderExample
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 ./genExtractFromDynamoDBStreamEvent
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) errorTIP
- 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,
) boolreturn 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] EQis 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 stringConstants
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) boolIsKeyConditionOperator
Checks if the operator can be used in key conditions.
func IsKeyConditionOperator(op OperatorType) boolValidateOperator
Checks if the operator is compatible with the field.
func ValidateOperator(fieldName string, op OperatorType) boolBuildConditionExpression
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,
)