Описание API
Константы
TableName
Имя таблицы DynamoDB.
const TableName = "table-name"
Column
Имена столбцов таблицы.
const ColumnId = "id"
const ColumnEmail = "email"
const ColumnTimestamp = "timestamp"
Нейминг колонок
Названия всех описанных в таблице колонок начинаются с Column
и используют CamelCase синтаксис
Index
Имена вторичных индексов.
const IndexEmailIndex = "email-index"
Нейминг индексов
Названия всех описанных в таблице индексов начинаются с Index
и используют CamelCase синтаксис
Attribute
Cлайс строк со всеми именами атрибутов таблицы DynamoDB.
var AttributeNames = []string{"id", "timestamp", "email"}
KeyAttribute
Cлайс строк с первичными ключами таблицы DynamoDB.
var KeyAttributeNames = []string{"id", "timestamp"}
Структуры данных
SchemaItem
Структура, которая представляет одну запись в DynamoDB.
type SchemaItem struct {
Id string `dynamodbav:"id"`
Email string `dynamodbav:"email"`
Timestamp int64 `dynamodbav:"timestamp"`
}
TableSchema
Глобальная переменная типа DynamoSchema
, которая содержит всю мета-информацию о таблице.
var TableSchema = DynamoSchema{
TableName: "table-name",
HashKey: "id",
RangeKey: "timestamp",
// ...
}
Подробнее...
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 и т.д.)
ПрименяютсяДО
чтения данных из DynamoDB и определяют какие элементы будут прочитаны.Filter
(FilterEQ, FilterGT и т.д.)
ПрименяютсяПОСЛЕ
чтения данных и влияют только на то, что возвращается в результате.
NewQueryBuilder
Создает новый QueryBuilder
.
func NewQueryBuilder() *QueryBuilder
qb.Limit
Устанавливает лимит результатов.
func (qb *QueryBuilder) Limit(limit int) *QueryBuilder
Пример
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, Status: %s\n", item.UserId, item.Status)
}
qb.WithIndex
Принудительно указывает, какой secondary index
использовать для запроса вместо автоматического выбора.
func (qb *QueryBuilder) WithIndex(indexName string) *QueryBuilder
!!! Баг в версии клиента v0.0.2.
метод не будет сгенерирован в min
версии.
Пример
Схема с множеством индексов:
{
"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"
}
]
}
Примеры запросов:
query1 := userorders.NewQueryBuilder().
WithEQ("user_id", "user123")
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
Дополнительно
Без WithIndex:
- QueryBuilder автоматически выбирает оптимальный индекс
- Ищет GSI/LSI который поддерживает твои ключи
С WithIndex:
- QueryBuilder принудительно использует указанный индекс
- Игнорирует автоматический выбор
qb.StartFrom
Устанавливает стартовый ключ для пагинации.
func (qb *QueryBuilder) StartFrom(
lastEvaluatedKey map[string]types.AttributeValue,
) *QueryBuilder
Пример
var lastKey map[string]types.AttributeValue
query1 := userorders.NewQueryBuilder().
WithEQ("user_id", "user123").
FilterEQ("status", "active").
Limit(10)
result1, err := client.Query(ctx, query1Input)
lastKey = result1.LastEvaluatedKey
query2 := userorders.NewQueryBuilder().
WithEQ("user_id", "user123").
FilterEQ("status", "active").
StartFrom(lastKey).
Limit(10)
LastEvaluatedKey
может быть null
даже если есть больше данных и размер ответа превышает 1MB
.
Всегда проверяйте наличие LastEvaluatedKey для продолжения пагинации.
qb.OrderByDesc
Устанавливает сортировку по убыванию для sort key.
func (qb *QueryBuilder) OrderByDesc() *QueryBuilder
Пример
query := userorders.NewQueryBuilder().
WithEQ("user_id", "user123").
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("Order: %s, Date: %s\n", item.OrderId, item.CreatedAt)
}
OrderByDesc
влияет только на сортировку по sort key, не на результаты фильтров.
qb.OrderByAsc
Устанавливает сортировку по возравстанию для sort key.
func (qb *QueryBuilder) OrderByAsc() *QueryBuilder
Пример
query := userorders.NewQueryBuilder().
WithEQ("user_id", "user123").
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("Order: %s, Date: %s\n", item.OrderId, item.CreatedAt)
}
OrderByAsc
влияет только на сортировку по sort key, не на результаты фильтров.
qb.WithPreferredSortKey
Подсказывает алгоритму выбора индекса предпочтительный sort key.
func (qb *QueryBuilder) WithPreferredSortKey(key string) *QueryBuilder
Пример
// Есть несколько индексов с одинаковым hash key:
// - lsi_by_status (sort: status)
// - lsi_by_created_at (sort: created_at)
// - lsi_by_priority (sort: priority)
query1 := userorders.NewQueryBuilder().
WithEQ("user_id", "user123").
WithEQ("status", "active")
// Может выбрать любой из подходящих индексов
query2 := userorders.NewQueryBuilder().
WithEQ("user_id", "user123").
WithEQ("status", "active").
WithPreferredSortKey("created_at")
// Выберет lsi_by_created_at если возможно
items, err := query2.Execute(ctx, dynamoClient)
Когда использовать
Используй WithPreferredSortKey:
- Есть несколько индексов, подходящих для запроса
- Хочешь получить результаты в определенном порядке сортировки
- Знаешь, какой индекс работает лучше для твоего случая
Важно
WithPreferredSortKey это только подсказка, не принуждение!
✅ Алгоритм предпочтет индекс с указанным sort key
❌ Но может выбрать другой, если подходящего нет
🎯 Для принудительного выбора используй WithIndex(indexName)
qb.With
Добавляет условие для запросов в DynamoDB.
Принимает:
field
- имя поляvalue
- значениеop
- тип операции
func (qb *QueryBuilder) With(
field string,
op OperatorType,
values ...any,
) *QueryBuilder
Влияние на запрос:
Все методы With
приминяются ДО
чтения данных из DynamoDB.
(это быстрее и дешевле чем Filter
)
Пример
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)
}
Сахар
Методы генерируются только при генерации с типом all
:
godyno -s schema.json -o ./gen -mode all
godyno -s schema.json -o ./gen
В min режиме используй универсальный метод With:
query
.With("user_id", EQ, "123")
.With("created_at", GT, timestamp)
query
.With("status", BETWEEN, "active", "pending")
.With("priority", LTE, 100)
qb.WithEQ
Добавляет условие равно
для sort key.
func (qb *QueryBuilder) WithEQ(field string, value any) *QueryBuilder
Пример
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
Добавляет условие больше
для sort key.
func (qb *QueryBuilder) WithGT(field string, value any) *QueryBuilder
Пример
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
Добавляет условие меньше
для sort key.
func (qb *QueryBuilder) WithLT(field string, value any) *QueryBuilder
Пример
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
Добавляет условие больше или равно
для sort key.
func (qb *QueryBuilder) WithGTE(field string, value any) *QueryBuilder
Пример
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
Добавляет условие меньше или равно
для sort key.
func (qb *QueryBuilder) WithLTE(field string, value any) *QueryBuilder
Пример
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
Добавляет условие диапазона
для sort key.
func (qb *QueryBuilder) WithBetween(field string, start, end any) *QueryBuilder
Пример
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
Добавляет условие начинается с
для sort key.
func (qb *QueryBuilder) WithBeginsWith(field string, value any) *QueryBuilder
Пример
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
Указывает какие конкретные поля вернуть из DynamoDB вместо всех полей записи.
func (qb *QueryBuilder) WithProjection(attributes []string) *QueryBuilder
Пример
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
Без WithProjection:
type SchemaItem struct {
Id string // ✅
Name string // ✅
Email string // ✅
Description string // ✅ (не нужно, но вернётся)
Content string // ✅ (не нужно, но вернётся)
Tags []string // ✅ (не нужно, но вернётся)
ViewCount int // ✅ (не нужно, но вернётся)
}
С WithProjection:
// Возвращает ТОЛЬКО указанные поля
WithProjection([]string{"id", "name", "email"})
// В результате будут только:
type PartialItem struct {
Id string // ✅
Name string // ✅
Email string // ✅
// Description - отсутствует
// Content - отсутствует
// Tags - отсутствует
// ViewCount - отсутствует
}
Проекция снижает потребление bandwidth
но НЕ снижает RCU
- вы платите за чтение всех атрибутов элемента.
qb.Filter
Добавляет условие для фильтрации полученныйх из DynamoDB значений.
Принимает:
field
- имя поляvalue
- значениеop
- тип операции
func (qb *QueryBuilder) Filter(
field string,
op OperatorType,
values ...any,
) *QueryBuilder
Влияние на запрос:
Все методы Filter
приминяются ПОСЛЕ
чтения данных из DynamoDB.
(используйте с умом)
Пример
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)
}
Сахар
Методы генерируются только при генерации с типом all
godyno -s schema.json -o ./gen -mode all
godyno -s schema.json -o ./gen
В min режиме используй универсальный метод Filter:
query
.Filter("status", EQ, "active")
.Filter("priority", BETWEEN, 80, 100)
qb.FilterEQ
Добавляет фильтр равенства
.
func (qb *QueryBuilder) FilterEQ(field string, value any) *QueryBuilder
Пример
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
Добавляет фильтр неравенства
.
func (qb *QueryBuilder) FilterNE(field string, value any) *QueryBuilder
Пример
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
Добавляет фильтр больше
.
func (qb *QueryBuilder) FilterGT(field string, value any) *QueryBuilder
Пример
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
Добавляет фильтр меньше
.
func (qb *QueryBuilder) FilterLT(field string, value any) *QueryBuilder
Пример
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
Добавляет фильтр больше или равно
.
func (qb *QueryBuilder) FilterGTE(field string, value any) *QueryBuilder
Пример
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
Добавляет фильтр меньше или равно
.
func (qb *QueryBuilder) FilterLTE(field string, value any) *QueryBuilder
Пример
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
Добавляет фильтр диапазона
.
func (qb *QueryBuilder) FilterBetween(field string, start, end any) *QueryBuilder
Пример
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
Добавляет фильтр содержит
.
func (qb *QueryBuilder) FilterContains(field string, value any) *QueryBuilder
Пример
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
Добавляет фильтр НЕ содержит
.
func (qb *QueryBuilder) FilterNotContains(field string, value any) *QueryBuilder
Пример
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
Добавляет фильтр начинается с
.
func (qb *QueryBuilder) FilterBeginsWith(field string, value any) *QueryBuilder
Пример
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
Добавляет фильтр входит в список
.
func (qb *QueryBuilder) FilterIn(field string, values ...any) *QueryBuilder
Пример
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
Добавляет фильтр НЕ входит в список
.
func (qb *QueryBuilder) FilterNotIn(field string, values ...any) *QueryBuilder
Пример
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
Добавляет фильтр НЕ пустое поле
.
func (qb *QueryBuilder) FilterExists(field string) *QueryBuilder
Пример
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
Добавляет фильтр пустое поле
.
func (qb *QueryBuilder) FilterNotExists(field string) *QueryBuilder
Пример
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
Строит DynamoDB QueryInput.
func (qb *QueryBuilder) BuildQuery() (*dynamodb.QueryInput, error)
qb.Execute
Выполняет запрос.
func (qb *QueryBuilder) Execute(
ctx context.Context,
client *dynamodb.Client,
) (
[]SchemaItem,
error,
)
ScanBuilder
Scan читает всю таблицу.
NewScanBuilder
Создает новый ScanBuilder
.
func NewScanBuilder() *ScanBuilder
sb.WithIndex
Принудительно указывает, какой secondary index
использовать для запроса вместо автоматического выбора.
func (sb *ScanBuilder) WithIndex(indexName string) *ScanBuilder
Выполняем сканирование по конкретному индексу
- GSI (Global Secondary Index) имеют отдельные RCU/WCU настройки.
- LSI (Local Secondary Index) используют RCU/WCU основной таблицы.
Пример
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
func (sb *ScanBuilder) Limit(limit int) *ScanBuilder
Пример
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
func (sb *ScanBuilder) StartFrom(
lastEvaluatedKey map[string]types.AttributeValue,
) *ScanBuilder
Пагинация
LastEvaluatedKey
может быть null
даже если есть больше данных и размер ответа превышает 1MB
.
Всегда проверяйте наличие LastEvaluatedKey для продолжения пагинации.
Пример
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
Указывает какие конкретные поля вернуть из DynamoDB вместо всех полей записи.
func (sb *ScanBuilder) WithProjection(attributes []string) *ScanBuilder
INFO
Без WithProjection:
type SchemaItem struct {
Id string // ✅
Name string // ✅
Email string // ✅
Description string // ✅ (не нужно, но вернётся)
Content string // ✅ (не нужно, но вернётся)
Tags []string // ✅ (не нужно, но вернётся)
ViewCount int // ✅ (не нужно, но вернётся)
}
С WithProjection:
// Возвращает ТОЛЬКО указанные поля
WithProjection([]string{"id", "name", "email"})
// В результате будут только:
type PartialItem struct {
Id string // ✅
Name string // ✅
Email string // ✅
// Description - отсутствует
// Content - отсутствует
// Tags - отсутствует
// ViewCount - отсутствует
}
Проекция снижает потребление bandwidth
но НЕ снижает RCU
- вы платите за чтение всех атрибутов элемента.
Пример
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
Параллельное сканирование
Увеличивает потребление RCU пропорционально количеству сегментов.
Используйте осторожно в production среде.
sb.Filter
Добавляет условие для фильтрации полученныйх из DynamoDB значений.
Принимает:
field
- имя поляvalue
- значениеop
- тип операции
func (sb *ScanBuilder) Filter(
field string,
op OperatorType,
values ...any,
) *ScanBuilder
Пример
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)
}
Сахар
Методы генерируются только при генерации с типом all
godyno -s schema.json -o ./gen -mode all
godyno -s schema.json -o ./gen
В min
режиме используй универсальный метод Filter
:
scan
.Filter("status", EQ, "active")
.Filter("priority", BETWEEN, 80, 100)
sb.FilterEQ
Добавляет фильтр равенства
.
func (sb *ScanBuilder) FilterEQ(field string, value any) *ScanBuilder
Пример
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
Добавляет фильтр неравенства
.
func (sb *ScanBuilder) FilterNE(field string, value any) *ScanBuilder
Пример
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
Добавляет фильтр больше
.
func (sb *ScanBuilder) FilterGT(field string, value any) *ScanBuilder
Пример
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
Добавляет фильтр меньше
.
func (sb *ScanBuilder) FilterLT(field string, value any) *ScanBuilder
Пример
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
Добавляет фильтр больше или равно
.
func (sb *ScanBuilder) FilterGTE(field string, value any) *ScanBuilder
Пример
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
Добавляет фильтр меньше или равно
.
func (sb *ScanBuilder) FilterLTE(field string, value any) *ScanBuilder
Пример
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
Добавляет фильтр диапазона
.
func (sb *ScanBuilder) FilterBetween(
field string,
start,
end any,
) *ScanBuilder
Пример
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
Добавляет фильтр содержит
.
func (sb *ScanBuilder) FilterContains(field string, value any) *ScanBuilder
Пример
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
Добавляет фильтр НЕ содержит
.
func (sb *ScanBuilder) FilterNotContains(
field string,
value any,
) *ScanBuilder
Пример
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
Добавляет фильтр начинается С
.
func (sb *ScanBuilder) FilterBeginsWith(field string, value any) *ScanBuilder
Пример
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
Добавляет фильтр входит в список
.
func (sb *ScanBuilder) FilterIn(field string, values ...any) *ScanBuilder
Пример
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
Добавляет фильтр НЕ входит в список
.
func (sb *ScanBuilder) FilterNotIn(field string, values ...any) *ScanBuilder
Пример
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
Добавляет фильтр НЕ пустое поле
.
func (sb *ScanBuilder) FilterExists(field string) *ScanBuilder
Пример
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
Добавляет фильтр пустое поле
.
func (sb *ScanBuilder) FilterNotExists(field string) *ScanBuilder
Пример
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
info Строит DynamoDB ScanInput.
func (sb *ScanBuilder) BuildScan() (*dynamodb.ScanInput, error)
sb.Execute
Выполняет сканирование всей таблице.
func (sb *ScanBuilder) Execute(
ctx context.Context,
client *dynamodb.Client,
) (
[]SchemaItem,
error,
)
Input Functions
ItemInput
Преобразует SchemaItem в DynamoDB AttributeValue map.
func ItemInput(item SchemaItem) (map[string]types.AttributeValue, error)
KeyInput
Создает ключ из значений hash и range ключей.
func KeyInput(
hashKeyValue,
rangeKeyValue any,
) (
map[string]types.AttributeValue,
error,
)
rangeKeyValue
может быть nil
если таблица использует только hash key.
KeyInputFromRaw
Создает ключ из сырых значений с валидацией.
func KeyInputFromRaw(
hashKeyValue,
rangeKeyValue any,
) (
map[string]types.AttributeValue,
error,
)
KeyInputFromItem
Извлекает ключ из SchemaItem.
func KeyInputFromItem(
item SchemaItem,
) (
map[string]types.AttributeValue,
error,
)
UpdateItemInput
Преобразует SchemaItem в DynamoDB UpdateItemInput.
UpdateItemInput(item SchemaItem) (*dynamodb.DeleteItemInput, error)
UpdateItemInputFromRaw
Создает UpdateItemInput из сырых значений.
func UpdateItemInputFromRaw(
hashKeyValue,
rangeKeyValue any,
updates map[string]any,
) (
*dynamodb.UpdateItemInput,
error,
)
UpdateItemInputWithCondition
Создает UpdateItemInput с условным выражением.
func UpdateItemInputWithCondition(
hashKeyValue,
rangeKeyValue any,
updates map[string]any,
conditionExpression string,
conditionAttributeNames map[string]string,
conditionAttributeValues map[string]types.AttributeValue,
) (
*dynamodb.UpdateItemInput,
error,
)
UpdateItemInputWithExpression
Создает UpdateItemInput с expression builders.
func UpdateItemInputWithExpression(
hashKeyValue,
rangeKeyValue any,
updateBuilder expression.UpdateBuilder,
conditionBuilder *expression.ConditionBuilder,
) (
*dynamodb.UpdateItemInput,
error,
)
DeleteItemInput
Преобразует SchemaItem в DynamoDB DeleteItemInput.
DeleteItemInput(item SchemaItem) (*dynamodb.DeleteItemInput, error)
DeleteItemInputFromRaw
Создает DeleteItemInput из значений ключей.
func DeleteItemInputFromRaw(
hashKeyValue,
rangeKeyValue any,
) (
*dynamodb.DeleteItemInput,
error,
)
DeleteItemInputWithCondition
Создает DeleteItemInput с условным выражением.
func DeleteItemInputWithCondition(
hashKeyValue,
rangeKeyValue any,
conditionExpression string,
expressionAttributeNames map[string]string,
expressionAttributeValues map[string]types.AttributeValue,
) (
*dynamodb.DeleteItemInput,
error,
)
BatchItemsInput
Преобразует массив SchemaItem в массив AttributeValue maps.
func BatchItemsInput(
items []SchemaItem,
) (
[]map[string]types.AttributeValue,
error,
)
Максимум 25
элементов в одной batch операции.
Превышение лимита вернет ошибку.
BatchDeleteItemsInput
Создает BatchWriteItemInput для удаления элементов.
func BatchDeleteItemsInput(
keys []map[string]types.AttributeValue,
) (
*dynamodb.BatchWriteItemInput,
error,
)
Максимум 25
элементов в одной batch операции.
Превышение лимита вернет ошибку.
BatchDeleteItemsInputFromRaw
Создает BatchWriteItemInput из SchemaItems.
func BatchDeleteItemsInputFromRaw(
items []SchemaItem,
) (
*dynamodb.BatchWriteItemInput,
error,
)
Максимум 25
элементов в одной batch операции.
Превышение лимита вернет ошибку.
Stream Functions
Методы генерируются только при генерации с типом all
godyno -s schema.json -o ./gen -mode all
godyno -s schema.json -o ./gen
ExtractFromDynamoDBStreamEvent
Извлекает новое состояние элемента из stream record.
func ExtractFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*SchemaItem, error)
ExtractOldFromDynamoDBStreamEvent
Извлекает старое состояние элемента из stream record.
func ExtractOldFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*SchemaItem, error)
ExtractBothFromDynamoDBStreamEvent
Извлекает новое и старое состояние элемента из stream record.
func ExtractBothFromDynamoDBStreamEvent(
dbEvent events.DynamoDBEventRecord,
) (
*SchemaItem,
*SchemaItem,
error,
)
CreateTriggerHandler
Создает обработчик для DynamoDB Stream событий.
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 — вызывается для INSERT, получает новый SchemaItem
- onModify — вызывается для MODIFY, получает старый и новый SchemaItem
- onDelete — вызывается для REMOVE, получает ключи удаленного элемента
IsFieldModified
Проверяет, действительно ли указанный атрибут был изменён в событии потока DynamoDB типа MODIFY
. Сравниваются старое и новое представления записи, и функция возвращает true только если поле было добавлено, удалено или его значение изменилось.
func IsFieldModified(
record events.DynamoDBEventRecord,
fieldName string,
) bool
Возвращает true
, если:
- событие - MODIFY, и поле ранее отсутствовало, но теперь присутствует
- событие - MODIFY, и поле ранее присутствовало, но теперь отсутствует
- событие - MODIFY, и поле присутствует в обеих версиях, но его сериализованное значение отличается
Operators
Ключевые условия VS Фильтры
Ключевые условия (Key Conditions) - применяются ДО
чтения:
- Определяют какие элементы читать из DynamoDB
- Влияют на стоимость операции (RCU)
- Поддерживают только: [
EQ
,GT
,LT
,GTE
,LTE
,BETWEEN
,BEGINS_WITH
] EQ
обязателен для partition key- Остальные операторы только для sort key
Фильтры (Filter Expressions) - применяются ПОСЛЕ
чтения:
- Фильтруют уже прочитанные данные
- НЕ влияют на стоимость операции (платите за все прочитанное)
- Поддерживают ВСЕ операторы
- Операторы только для фильтров: [
CONTAINS
,NOT_CONTAINS
,IN
,NOT_IN
,EXISTS
,NOT_EXISTS
,NE
]
Рекомендация:
Используйте ключевые условия максимально, а фильтры - только для дополнительной фильтрации.
OperatorType
type OperatorType string
Константы операторов
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
Проверяет количество значений для оператора.
func ValidateValues(op OperatorType, values []any) bool
IsKeyConditionOperator
Проверяет, может ли оператор использоваться в key conditions.
func IsKeyConditionOperator(op OperatorType) bool
ValidateOperator
Проверяет совместимость оператора с полем.
func ValidateOperator(fieldName string, op OperatorType) bool
BuildConditionExpression
Создает условие фильтрации.
func BuildConditionExpression(
field string,
op OperatorType,
values []any,
) (
expression.ConditionBuilder,
error,
)
BuildKeyConditionExpression
Создает ключевое условие.
func BuildKeyConditionExpression(
field string,
op OperatorType,
values []any,
) (
expression.KeyConditionBuilder,
error,
)