diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 6424360..448b034 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -61,7 +61,7 @@ jobs: - name: Generate Changelog id: changelog run: | - PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "^${{ github.ref_name }}$" | head -n1) + PREVIOUS_TAG=$(git tag --sort=-creatordate | grep -E 'v[0-9]+\.[0-9]+\.[0-9]+$' | grep -v "^${{ github.ref_name }}$" | head -n1 if [ -z "$PREVIOUS_TAG" ]; then PREVIOUS_TAG=$(git rev-list --max-parents=0 HEAD) fi diff --git a/templates/v2/core/mixin_filter.go b/templates/v2/core/mixin_filter.go index 3b81800..d0154b2 100644 --- a/templates/v2/core/mixin_filter.go +++ b/templates/v2/core/mixin_filter.go @@ -6,13 +6,11 @@ const KeyConditionMixinSugarTemplate = ` // WithEQ adds equality key condition. // Required for partition key, optional for sort key. -// Example: .WithEQ("user_id", "123") func (kcm *KeyConditionMixin) WithEQ(field string, value any) { kcm.With(field, EQ, value) } // WithBetween adds range key condition for sort keys. -// Example: .WithBetween("created_at", start_time, end_time) func (kcm *KeyConditionMixin) WithBetween(field string, start, end any) { kcm.With(field, BETWEEN, start, end) } diff --git a/templates/v2/core/mixins.go b/templates/v2/core/mixins.go index 131ac72..0839036 100644 --- a/templates/v2/core/mixins.go +++ b/templates/v2/core/mixins.go @@ -54,14 +54,12 @@ func NewPaginationMixin() PaginationMixin { } // Limit sets the maximum number of items to return in one request. -// Example: .Limit(25) func (pm *PaginationMixin) Limit(limit int) { pm.LimitValue = &limit } // StartFrom sets the exclusive start key for pagination. // Use LastEvaluatedKey from previous response for next page. -// Example: .StartFrom(previousResponse.LastEvaluatedKey) func (pm *PaginationMixin) StartFrom(lastEvaluatedKey map[string]types.AttributeValue) { pm.ExclusiveStartKey = lastEvaluatedKey } @@ -87,7 +85,6 @@ func (kcm *KeyConditionMixin) With(field string, op OperatorType, values ...any) if !ValidateValues(op, values) { return } - fieldInfo, exists := TableSchema.FieldsMap[field] if !exists { return diff --git a/templates/v2/core/mixins_with.go b/templates/v2/core/mixins_with.go index bd64729..feb4320 100644 --- a/templates/v2/core/mixins_with.go +++ b/templates/v2/core/mixins_with.go @@ -5,13 +5,11 @@ const FilterMixinSugarTemplate = ` // CONVENIENCE METHODS - Only available in ALL mode // FilterEQ adds equality filter condition. -// Example: .FilterEQ("status", "active") func (fm *FilterMixin) FilterEQ(field string, value any) { fm.Filter(field, EQ, value) } // FilterContains adds contains filter for strings or sets. -// Example: .FilterContains("tags", "important") func (fm *FilterMixin) FilterContains(field string, value any) { fm.Filter(field, CONTAINS, value) } @@ -22,13 +20,11 @@ func (fm *FilterMixin) FilterNotContains(field string, value any) { } // FilterBeginsWith adds begins_with filter for strings. -// Example: .FilterBeginsWith("email", "admin@") func (fm *FilterMixin) FilterBeginsWith(field string, value any) { fm.Filter(field, BEGINS_WITH, value) } // FilterBetween adds range filter for comparable values. -// Example: .FilterBetween("price", 10, 100) func (fm *FilterMixin) FilterBetween(field string, start, end any) { fm.Filter(field, BETWEEN, start, end) } @@ -54,7 +50,6 @@ func (fm *FilterMixin) FilterLTE(field string, value any) { } // FilterExists checks if attribute exists. -// Example: .FilterExists("optional_field") func (fm *FilterMixin) FilterExists(field string) { fm.Filter(field, EXISTS) } @@ -71,7 +66,6 @@ func (fm *FilterMixin) FilterNE(field string, value any) { // FilterIn adds IN filter for scalar values. // For DynamoDB Sets (SS/NS), use FilterContains instead. -// Example: .FilterIn("category", "books", "electronics") func (fm *FilterMixin) FilterIn(field string, values ...any) { if len(values) == 0 { return diff --git a/templates/v2/core/schema.go b/templates/v2/core/schema.go index d0541a4..25f16a9 100644 --- a/templates/v2/core/schema.go +++ b/templates/v2/core/schema.go @@ -3,7 +3,6 @@ package core // SchemaTemplate with pre-computed allowed operators const SchemaTemplate = ` // FieldInfo contains metadata about a schema field with operator validation. -// Provides O(1) lookup for supported DynamoDB operations per field type. type FieldInfo struct { DynamoType string IsKey bool @@ -14,7 +13,6 @@ type FieldInfo struct { // SupportsOperator checks if this field supports the given operator. // Returns false for invalid operator/type combinations. -// Example: stringField.SupportsOperator(BEGINS_WITH) -> true func (fi FieldInfo) SupportsOperator(op OperatorType) bool { return fi.AllowedOperators[op] } diff --git a/templates/v2/generic/operators.go b/templates/v2/generic/operators.go index d3c8b2d..9b9df95 100644 --- a/templates/v2/generic/operators.go +++ b/templates/v2/generic/operators.go @@ -16,20 +16,20 @@ const ( LTE OperatorType = "<=" // Less than or equal // Range operator for between comparisons - BETWEEN OperatorType = "BETWEEN" // Between two values (inclusive) + BETWEEN OperatorType = "BETWEEN" // String operators - work with String types and Sets - CONTAINS OperatorType = "contains" // Contains substring or set member - NOT_CONTAINS OperatorType = "not_contains" // Does not contain substring or member - BEGINS_WITH OperatorType = "begins_with" // String starts with prefix + CONTAINS OperatorType = "contains" + NOT_CONTAINS OperatorType = "not_contains" + BEGINS_WITH OperatorType = "begins_with" // Set operators for scalar values only (not DynamoDB Sets SS/NS) - IN OperatorType = "IN" // Value is in list of values - NOT_IN OperatorType = "NOT_IN" // Value is not in list of values + IN OperatorType = "IN" + NOT_IN OperatorType = "NOT_IN" // Existence operators - work with all types - EXISTS OperatorType = "attribute_exists" // Attribute exists - NOT_EXISTS OperatorType = "attribute_not_exists" // Attribute does not exist + EXISTS OperatorType = "attribute_exists" + NOT_EXISTS OperatorType = "attribute_not_exists" ) // ConditionType defines whether this is a key condition or filter condition. @@ -37,8 +37,8 @@ const ( type ConditionType string const ( - KeyCondition ConditionType = "KEY" // For partition/sort key conditions - FilterCondition ConditionType = "FILTER" // For non-key attribute filtering + KeyCondition ConditionType = "KEY" + FilterCondition ConditionType = "FILTER" ) // Condition represents a single query or filter condition with validation metadata. @@ -54,7 +54,6 @@ type Condition struct { type KeyOperatorHandler func(expression.KeyBuilder, []any) expression.KeyConditionBuilder type ConditionOperatorHandler func(expression.NameBuilder, []any) expression.ConditionBuilder -// keyOperatorHandlers provides O(1) lookup for key condition operations. // Only includes operators valid for key conditions (partition/sort keys). var keyOperatorHandlers = map[OperatorType]KeyOperatorHandler{ EQ: func(field expression.KeyBuilder, values []any) expression.KeyConditionBuilder { @@ -88,10 +87,8 @@ var allowedKeyConditionOperators = map[OperatorType]bool{ BETWEEN: true, } -// conditionOperatorHandlers provides O(1) lookup for filter operations. // Includes all operators supported in filter expressions. var conditionOperatorHandlers = map[OperatorType]ConditionOperatorHandler{ - // Basic comparison operators EQ: func(field expression.NameBuilder, values []any) expression.ConditionBuilder { return field.Equal(expression.Value(values[0])) }, @@ -114,7 +111,6 @@ var conditionOperatorHandlers = map[OperatorType]ConditionOperatorHandler{ return field.Between(expression.Value(values[0]), expression.Value(values[1])) }, - // String and set operations CONTAINS: func(field expression.NameBuilder, values []any) expression.ConditionBuilder { return field.Contains(fmt.Sprintf("%v", values[0])) }, @@ -125,7 +121,6 @@ var conditionOperatorHandlers = map[OperatorType]ConditionOperatorHandler{ return field.BeginsWith(fmt.Sprintf("%v", values[0])) }, - // Scalar value list operations (not for DynamoDB Sets) IN: func(field expression.NameBuilder, values []any) expression.ConditionBuilder { if len(values) == 0 { return expression.AttributeNotExists(field) @@ -153,7 +148,6 @@ var conditionOperatorHandlers = map[OperatorType]ConditionOperatorHandler{ return expression.Not(field.In(operands[0], operands[1:]...)) }, - // Existence checks EXISTS: func(field expression.NameBuilder, values []any) expression.ConditionBuilder { return expression.AttributeExists(field) }, @@ -167,13 +161,13 @@ var conditionOperatorHandlers = map[OperatorType]ConditionOperatorHandler{ func ValidateValues(op OperatorType, values []any) bool { switch op { case EQ, NE, GT, LT, GTE, LTE, CONTAINS, NOT_CONTAINS, BEGINS_WITH: - return len(values) == 1 // Single value operators + return len(values) == 1 case BETWEEN: - return len(values) == 2 // Start and end values + return len(values) == 2 case IN, NOT_IN: - return len(values) >= 1 // At least one value required + return len(values) >= 1 case EXISTS, NOT_EXISTS: - return len(values) == 0 // No values needed + return len(values) == 0 default: return false } @@ -196,7 +190,6 @@ func ValidateOperator(fieldName string, op OperatorType) bool { // BuildConditionExpression converts operator to DynamoDB filter expression. // Creates type-safe filter conditions with full validation. -// Example: BuildConditionExpression("name", EQ, []any{"John"}) func BuildConditionExpression(field string, op OperatorType, values []any) (expression.ConditionBuilder, error) { fieldInfo, exists := TableSchema.FieldsMap[field] if !exists { @@ -217,7 +210,6 @@ func BuildConditionExpression(field string, op OperatorType, values []any) (expr // BuildKeyConditionExpression converts operator to DynamoDB key condition. // Creates type-safe key conditions for Query operations only. -// Example: BuildKeyConditionExpression("user_id", EQ, []any{"123"}) func BuildKeyConditionExpression(field string, op OperatorType, values []any) (expression.KeyConditionBuilder, error) { fieldInfo, exists := TableSchema.FieldsMap[field] if !exists { diff --git a/templates/v2/helpers/atomic.go b/templates/v2/helpers/atomic.go index e1cecde..9869585 100644 --- a/templates/v2/helpers/atomic.go +++ b/templates/v2/helpers/atomic.go @@ -5,7 +5,6 @@ const AtomicHelpersTemplate = ` // IncrementAttribute atomically increments a numeric attribute by a specified value. // Uses DynamoDB's ADD operation to ensure thread-safe increments without race conditions. // Creates the attribute with the increment value if it doesn't exist. -// Example: IncrementAttribute("user123", nil, "view_count", 1) func IncrementAttribute(hashKeyValue any, rangeKeyValue any, attributeName string, incrementValue int) (*dynamodb.UpdateItemInput, error) { if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil { return nil, err @@ -38,7 +37,6 @@ func IncrementAttribute(hashKeyValue any, rangeKeyValue any, attributeName strin // Uses DynamoDB's ADD operation for sets - duplicate values are automatically ignored. // Creates the set with provided values if the attribute doesn't exist. // Supports string sets ([]string) and numeric sets ([]int, []float64, etc.). -// Example: AddToSet("user123", nil, "tags", []string{"premium", "verified"}) func AddToSet(hashKeyValue any, rangeKeyValue any, attributeName string, values any) (*dynamodb.UpdateItemInput, error) { if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil { return nil, err @@ -91,7 +89,6 @@ func AddToSet(hashKeyValue any, rangeKeyValue any, attributeName string, values // Uses DynamoDB's DELETE operation for sets - non-existent values are ignored. // If all values are removed, the attribute is deleted from the item. // Supports string sets ([]string) and numeric sets ([]int, []float64, etc.). -// Example: RemoveFromSet("user123", nil, "tags", []string{"temporary"}) func RemoveFromSet(hashKeyValue any, rangeKeyValue any, attributeName string, values any) (*dynamodb.UpdateItemInput, error) { if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil { return nil, err diff --git a/templates/v2/helpers/marshaling.go b/templates/v2/helpers/marshaling.go index 153aab1..f90e422 100644 --- a/templates/v2/helpers/marshaling.go +++ b/templates/v2/helpers/marshaling.go @@ -18,7 +18,6 @@ type Float interface { // toIntStrings converts any signed or unsigned integer slice to string slice. // DynamoDB requires numeric sets as string arrays for the wire protocol. -// Example: toIntStrings([]int{1, 2, 3}) -> ["1", "2", "3"] func toIntStrings[T Signed | Unsigned](nums []T) []string { out := make([]string, len(nums)) for i, n := range nums { @@ -29,7 +28,6 @@ func toIntStrings[T Signed | Unsigned](nums []T) []string { // toFloatStrings converts any float slice to string slice. // Uses 'g' format for optimal precision and readability. -// Example: toFloatStrings([]float64{1.5, 2.7}) -> ["1.5", "2.7"] func toFloatStrings[F Float](nums []F) []string { out := make([]string, len(nums)) for i, f := range nums { @@ -131,7 +129,6 @@ func marshalUpdatesWithSchema(updates map[string]any) (map[string]types.Attribut // marshalValueByType marshals value according to specific DynamoDB type. // Handles special cases like String Sets (SS) and Number Sets (NS) that require // custom marshaling logic not provided by the default AWS SDK marshaler. -// Example: marshalValueByType([]int{1,2,3}, "NS") -> AttributeValueMemberNS func marshalValueByType(value any, dynamoType string) (types.AttributeValue, error) { switch dynamoType { case "SS": diff --git a/templates/v2/helpers/stream.go b/templates/v2/helpers/stream.go index 8a5cf82..021444c 100644 --- a/templates/v2/helpers/stream.go +++ b/templates/v2/helpers/stream.go @@ -5,7 +5,6 @@ const StreamHelpersTemplate = ` // ExtractFromDynamoDBStreamEvent extracts SchemaItem from DynamoDB stream event. // Converts Lambda stream AttributeValues to DynamoDB SDK types for safe unmarshaling. // Used for INSERT and MODIFY events to get the new item state. -// Example: item, err := ExtractFromDynamoDBStreamEvent(record) func ExtractFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*SchemaItem, error) { if dbEvent.Change.NewImage == nil { return nil, fmt.Errorf("new image is nil in the event") @@ -22,7 +21,6 @@ func ExtractFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*Schema // ExtractOldFromDynamoDBStreamEvent extracts old SchemaItem from DynamoDB stream event. // Converts Lambda stream AttributeValues to DynamoDB SDK types for safe unmarshaling. // Used for MODIFY and REMOVE events to get the previous item state. -// Example: oldItem, err := ExtractOldFromDynamoDBStreamEvent(record) func ExtractOldFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*SchemaItem, error) { if dbEvent.Change.OldImage == nil { return nil, fmt.Errorf("old image is nil in the event") @@ -37,7 +35,6 @@ func ExtractOldFromDynamoDBStreamEvent(dbEvent events.DynamoDBEventRecord) (*Sch } // toDynamoMap converts Lambda events.DynamoDBAttributeValue to SDK types.AttributeValue. -// Required because Lambda and DynamoDB SDK use different attribute value types. func toDynamoMap(streamAttrs map[string]events.DynamoDBAttributeValue) map[string]types.AttributeValue { dynamoAttrs := make(map[string]types.AttributeValue, len(streamAttrs)) for key, streamAttr := range streamAttrs { @@ -47,7 +44,6 @@ func toDynamoMap(streamAttrs map[string]events.DynamoDBAttributeValue) map[strin } // toDynamoAttr converts single Lambda AttributeValue to SDK AttributeValue. -// Handles all DynamoDB data types including nested Lists and Maps. func toDynamoAttr(streamAttr events.DynamoDBAttributeValue) types.AttributeValue { switch streamAttr.DataType() { case events.DataTypeString: @@ -86,7 +82,6 @@ func toDynamoAttr(streamAttr events.DynamoDBAttributeValue) types.AttributeValue // IsFieldModified checks if a specific field was modified in a MODIFY event. // Compares old and new values to detect actual changes, not just updates. // Returns false for INSERT/REMOVE events or if images are missing. -// Example: if IsFieldModified(record, "status") { ... } func IsFieldModified(dbEvent events.DynamoDBEventRecord, fieldName string) bool { if dbEvent.EventName != "MODIFY" { return false @@ -160,28 +155,6 @@ func streamAttributeValuesEqual(a, b events.DynamoDBAttributeValue) bool { } } -// GetBoolFieldChanged checks if a boolean field changed from false to true. -// Useful for detecting state transitions like activation flags. -// Example: if GetBoolFieldChanged(record, "is_verified") { sendWelcomeEmail() } -func GetBoolFieldChanged(dbEvent events.DynamoDBEventRecord, fieldName string) bool { - if dbEvent.EventName != "MODIFY" { - return false - } - if dbEvent.Change.OldImage == nil || dbEvent.Change.NewImage == nil { - return false - } - - oldValue := false - if oldVal, ok := dbEvent.Change.OldImage[fieldName]; ok { - oldValue = oldVal.Boolean() - } - newValue := false - if newVal, ok := dbEvent.Change.NewImage[fieldName]; ok { - newValue = newVal.Boolean() - } - return !oldValue && newValue -} - // ExtractBothFromDynamoDBStreamEvent extracts both old and new items from stream event. // Returns nil for missing images (e.g., oldItem is nil for INSERT events). // Useful for MODIFY events where you need to compare before/after states. diff --git a/templates/v2/helpers/validation.go b/templates/v2/helpers/validation.go index 293fc4c..a2995e2 100644 --- a/templates/v2/helpers/validation.go +++ b/templates/v2/helpers/validation.go @@ -27,22 +27,16 @@ func validateKeyPart(partName string, value any) error { } // validateHashKey checks if hash key value is valid for DynamoDB operations. -// Hash key is required for all DynamoDB operations and cannot be nil or empty. -// Example: validateHashKey("user123") -> nil, validateHashKey("") -> error func validateHashKey(value any) error { return validateKeyPart("hash", value) } // validateRangeKey checks if range key value is valid (nil is allowed). -// Range key is optional - tables can have simple (hash only) or composite keys. -// Example: validateRangeKey(nil) -> nil, validateRangeKey("timestamp") -> nil func validateRangeKey(value any) error { return validateKeyPart("range", value) } // validateAttributeName checks if attribute name meets DynamoDB requirements. -// DynamoDB limits: non-empty, max 255 characters. -// Used to prevent API errors from invalid attribute names. func validateAttributeName(name string) error { if name == "" { return fmt.Errorf("attribute name cannot be empty") @@ -54,13 +48,10 @@ func validateAttributeName(name string) error { } // validateUpdatesMap checks if updates map is valid for UpdateItem operations. -// Ensures non-empty map with valid attribute names and non-nil values. -// Prevents wasted API calls and provides clear error messages. func validateUpdatesMap(updates map[string]any) error { if len(updates) == 0 { return fmt.Errorf("updates map cannot be empty") } - for attrName, value := range updates { if err := validateAttributeName(attrName); err != nil { return fmt.Errorf("invalid attribute name '%s': %v", attrName, err) @@ -73,14 +64,10 @@ func validateUpdatesMap(updates map[string]any) error { } // validateBatchSize checks if batch size is within DynamoDB limits. -// DynamoDB batch operations (BatchGetItem, BatchWriteItem) have a 25 item limit. -// Prevents API errors and guides proper batch partitioning. -// Example: validateBatchSize(30, "write") -> error about exceeding limit func validateBatchSize(size int, operation string) error { if size == 0 { return fmt.Errorf("%s batch cannot be empty", operation) } - if size > 25 { return fmt.Errorf("%s batch size %d exceeds DynamoDB limit of 25", operation, size) } @@ -88,13 +75,10 @@ func validateBatchSize(size int, operation string) error { } // validateSetValues checks if set values are valid for AddToSet/RemoveFromSet operations. -// DynamoDB sets cannot be empty and string sets cannot contain empty strings. -// Validates both string sets (SS) and numeric sets (NS) with proper type checking. func validateSetValues(values any) error { if values == nil { return fmt.Errorf("set values cannot be nil") } - switch v := values.(type) { case []string: if len(v) == 0 { @@ -117,8 +101,6 @@ func validateSetValues(values any) error { } // validateConditionExpression checks if condition expression meets DynamoDB limits. -// DynamoDB condition expressions have a 4KB size limit. -// Helps prevent API errors from oversized expressions. func validateConditionExpression(expr string) error { if expr == "" { return fmt.Errorf("condition expression cannot be empty") @@ -130,8 +112,6 @@ func validateConditionExpression(expr string) error { } // validateIncrementValue checks if increment value is valid for atomic operations. -// DynamoDB ADD operation accepts any integer value (positive or negative). -// Function maintained for API consistency and future validation needs. func validateIncrementValue(value int) error { // DynamoDB supports any int value for ADD operation // No specific validation needed, but we keep the function for consistency @@ -139,9 +119,6 @@ func validateIncrementValue(value int) error { } // validateKeyInputs validates both hash and range key inputs for DynamoDB operations. -// Comprehensive validation for all key-based operations (GetItem, UpdateItem, etc.). -// Provides clear error context for debugging key-related issues. -// Example: validateKeyInputs("user123", "2023-01-01") -> nil func validateKeyInputs(hashKeyValue, rangeKeyValue any) error { if err := validateHashKey(hashKeyValue); err != nil { return fmt.Errorf("invalid hash key: %v", err) diff --git a/templates/v2/inputs/item.go b/templates/v2/inputs/item.go index 16bdef6..a653566 100644 --- a/templates/v2/inputs/item.go +++ b/templates/v2/inputs/item.go @@ -5,7 +5,6 @@ const ItemInputsTemplate = ` // ItemInput converts a SchemaItem to DynamoDB AttributeValue map format. // Uses AWS SDK's attributevalue package for safe and consistent marshaling. // The resulting map can be used in PutItem, UpdateItem, and other DynamoDB operations. -// Example: attrMap, err := ItemInput(userItem) func ItemInput(item SchemaItem) (map[string]types.AttributeValue, error) { attributeValues, err := attributevalue.MarshalMap(item) if err != nil { @@ -13,20 +12,4 @@ func ItemInput(item SchemaItem) (map[string]types.AttributeValue, error) { } return attributeValues, nil } - -// ItemsInput converts a slice of SchemaItems to DynamoDB AttributeValue maps. -// Efficiently marshals multiple items for batch operations like BatchWriteItem. -// Maintains order and provides detailed error context for debugging failed marshaling. -// Example: attrMaps, err := ItemsInput([]SchemaItem{item1, item2, item3}) -func ItemsInput(items []SchemaItem) ([]map[string]types.AttributeValue, error) { - result := make([]map[string]types.AttributeValue, 0, len(items)) - for i, item := range items { - av, err := ItemInput(item) - if err != nil { - return nil, fmt.Errorf("failed to marshal item at index %d: %v", i, err) - } - result = append(result, av) - } - return result, nil -} ` diff --git a/templates/v2/inputs/item_delete.go b/templates/v2/inputs/item_delete.go index fc6349e..d8bf14e 100644 --- a/templates/v2/inputs/item_delete.go +++ b/templates/v2/inputs/item_delete.go @@ -5,7 +5,6 @@ const DeleteInputsTemplate = ` // DeleteItemInput creates a DeleteItemInput from a complete SchemaItem. // Extracts the primary key from the item for the delete operation. // Use when you have the full item and want to delete it. -// Example: input, err := DeleteItemInput(userItem) func DeleteItemInput(item SchemaItem) (*dynamodb.DeleteItemInput, error) { key, err := KeyInput(item) if err != nil { @@ -20,12 +19,10 @@ func DeleteItemInput(item SchemaItem) (*dynamodb.DeleteItemInput, error) { // DeleteItemInputFromRaw creates a DeleteItemInput from raw key values. // Use when you only have the key values and want to delete the item. // More efficient than DeleteItemInput when you don't have the full item. -// Example: input, err := DeleteItemInputFromRaw("user123", "session456") func DeleteItemInputFromRaw(hashKeyValue any, rangeKeyValue any) (*dynamodb.DeleteItemInput, error) { if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil { return nil, err } - key, err := KeyInputFromRaw(hashKeyValue, rangeKeyValue) if err != nil { return nil, fmt.Errorf("failed to create key for delete: %v", err) @@ -39,15 +36,19 @@ func DeleteItemInputFromRaw(hashKeyValue any, rangeKeyValue any) (*dynamodb.Dele // DeleteItemInputWithCondition creates a conditional DeleteItemInput. // Deletes the item only if the condition expression evaluates to true. // Prevents accidental deletion and enables optimistic locking patterns. -// Example: DeleteItemInputWithCondition("user123", nil, "attribute_exists(#status)", {"#status": "status"}, nil) -func DeleteItemInputWithCondition(hashKeyValue any, rangeKeyValue any, conditionExpression string, expressionAttributeNames map[string]string, expressionAttributeValues map[string]types.AttributeValue) (*dynamodb.DeleteItemInput, error) { +func DeleteItemInputWithCondition( + hashKeyValue any, + rangeKeyValue any, + conditionExpression string, + expressionAttributeNames map[string]string, + expressionAttributeValues map[string]types.AttributeValue, +) (*dynamodb.DeleteItemInput, error) { if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil { return nil, err } if err := validateConditionExpression(conditionExpression); err != nil { return nil, err } - key, err := KeyInputFromRaw(hashKeyValue, rangeKeyValue) if err != nil { return nil, fmt.Errorf("failed to create key for conditional delete: %v", err) @@ -57,7 +58,6 @@ func DeleteItemInputWithCondition(hashKeyValue any, rangeKeyValue any, condition Key: key, ConditionExpression: aws.String(conditionExpression), } - if expressionAttributeNames != nil { input.ExpressionAttributeNames = expressionAttributeNames } @@ -70,7 +70,6 @@ func DeleteItemInputWithCondition(hashKeyValue any, rangeKeyValue any, condition // BatchDeleteItemsInput creates a BatchWriteItemInput for deleting multiple items. // Takes pre-built key maps and creates delete requests for batch operation. // Limited to 25 items per batch due to DynamoDB constraints. -// Example: BatchDeleteItemsInput([]map[string]types.AttributeValue{key1, key2}) func BatchDeleteItemsInput(keys []map[string]types.AttributeValue) (*dynamodb.BatchWriteItemInput, error) { if err := validateBatchSize(len(keys), "delete"); err != nil { return nil, err @@ -78,7 +77,6 @@ func BatchDeleteItemsInput(keys []map[string]types.AttributeValue) (*dynamodb.Ba if len(keys) == 0 { return &dynamodb.BatchWriteItemInput{}, nil } - writeRequests := make([]types.WriteRequest, 0, len(keys)) for _, key := range keys { writeRequests = append(writeRequests, types.WriteRequest{ @@ -97,7 +95,6 @@ func BatchDeleteItemsInput(keys []map[string]types.AttributeValue) (*dynamodb.Ba // BatchDeleteItemsInputFromRaw creates a BatchWriteItemInput from SchemaItems. // Extracts keys from each item and creates batch delete requests. // More convenient than BatchDeleteItemsInput when you have full items. -// Example: BatchDeleteItemsInputFromRaw([]SchemaItem{item1, item2, item3}) func BatchDeleteItemsInputFromRaw(items []SchemaItem) (*dynamodb.BatchWriteItemInput, error) { if err := validateBatchSize(len(items), "delete"); err != nil { return nil, err @@ -105,7 +102,6 @@ func BatchDeleteItemsInputFromRaw(items []SchemaItem) (*dynamodb.BatchWriteItemI if len(items) == 0 { return &dynamodb.BatchWriteItemInput{}, nil } - keys := make([]map[string]types.AttributeValue, 0, len(items)) for _, item := range items { key, err := KeyInput(item) diff --git a/templates/v2/inputs/item_update.go b/templates/v2/inputs/item_update.go index c8c97ce..5fd250d 100644 --- a/templates/v2/inputs/item_update.go +++ b/templates/v2/inputs/item_update.go @@ -5,13 +5,11 @@ const UpdateInputsTemplate = ` // UpdateItemInput creates an UpdateItemInput from a complete SchemaItem. // Automatically extracts the key and updates all non-key attributes. // Use when you want to update an entire item with new values. -// Example: input, err := UpdateItemInput(modifiedUserItem) func UpdateItemInput(item SchemaItem) (*dynamodb.UpdateItemInput, error) { key, err := KeyInput(item) if err != nil { return nil, fmt.Errorf("failed to create key from item for update: %v", err) } - allAttributes, err := marshalItemToMap(item) if err != nil { return nil, fmt.Errorf("failed to marshal item for update: %v", err) @@ -34,7 +32,6 @@ func UpdateItemInput(item SchemaItem) (*dynamodb.UpdateItemInput, error) { // UpdateItemInputFromRaw creates an UpdateItemInput from raw key values and update map. // More efficient for partial updates when you only want to modify specific attributes. // Use when you know exactly which fields to update without loading the full item. -// Example: UpdateItemInputFromRaw("user123", nil, map[string]any{"status": "active", "last_login": time.Now()}) func UpdateItemInputFromRaw(hashKeyValue any, rangeKeyValue any, updates map[string]any) (*dynamodb.UpdateItemInput, error) { if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil { return nil, err @@ -42,7 +39,6 @@ func UpdateItemInputFromRaw(hashKeyValue any, rangeKeyValue any, updates map[str if err := validateUpdatesMap(updates); err != nil { return nil, err } - key, err := KeyInputFromRaw(hashKeyValue, rangeKeyValue) if err != nil { return nil, fmt.Errorf("failed to create key for update: %v", err) @@ -64,9 +60,14 @@ func UpdateItemInputFromRaw(hashKeyValue any, rangeKeyValue any, updates map[str // UpdateItemInputWithCondition creates a conditional UpdateItemInput. // Updates the item only if the condition expression evaluates to true. -// Enables optimistic locking and prevents race conditions in concurrent updates. -// Example: UpdateItemInputWithCondition("user123", nil, updates, "version = :v", nil, map[string]types.AttributeValue{":v": &types.AttributeValueMemberN{Value: "1"}}) -func UpdateItemInputWithCondition(hashKeyValue any, rangeKeyValue any, updates map[string]any, conditionExpression string, conditionAttributeNames map[string]string, conditionAttributeValues map[string]types.AttributeValue) (*dynamodb.UpdateItemInput, error) { +func UpdateItemInputWithCondition( + hashKeyValue any, + rangeKeyValue any, + updates map[string]any, + conditionExpression string, + conditionAttributeNames map[string]string, + conditionAttributeValues map[string]types.AttributeValue, +) (*dynamodb.UpdateItemInput, error) { if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil { return nil, err } @@ -76,7 +77,6 @@ func UpdateItemInputWithCondition(hashKeyValue any, rangeKeyValue any, updates m if err := validateConditionExpression(conditionExpression); err != nil { return nil, err } - updateInput, err := UpdateItemInputFromRaw(hashKeyValue, rangeKeyValue, updates) if err != nil { return nil, err @@ -103,12 +103,10 @@ func UpdateItemInputWithExpression(hashKeyValue any, rangeKeyValue any, updateBu if err := validateKeyInputs(hashKeyValue, rangeKeyValue); err != nil { return nil, err } - key, err := KeyInputFromRaw(hashKeyValue, rangeKeyValue) if err != nil { return nil, fmt.Errorf("failed to create key for expression update: %v", err) } - var expr expression.Expression if conditionBuilder != nil { expr, err = expression.NewBuilder(). @@ -123,7 +121,6 @@ func UpdateItemInputWithExpression(hashKeyValue any, rangeKeyValue any, updateBu if err != nil { return nil, fmt.Errorf("failed to build update expression: %v", err) } - input := &dynamodb.UpdateItemInput{ TableName: aws.String(TableSchema.TableName), Key: key, diff --git a/templates/v2/inputs/key.go b/templates/v2/inputs/key.go index b0cfaf9..cd29ad3 100644 --- a/templates/v2/inputs/key.go +++ b/templates/v2/inputs/key.go @@ -6,7 +6,6 @@ const KeyInputsTemplate = ` // Extracts the primary key (hash + range) from the item and validates values. // Use when you have a complete item and need to create a key for operations. // Handles both simple (hash only) and composite (hash + range) keys automatically. -// Example: keyMap, err := KeyInput(userItem) func KeyInput(item SchemaItem) (map[string]types.AttributeValue, error) { var hashKeyValue any {{range .AllAttributes}}{{if eq .Name $.HashKey}} @@ -45,7 +44,6 @@ func KeyInput(item SchemaItem) (map[string]types.AttributeValue, error) { // More efficient than KeyInput when you already have validated key values. // Assumes validation has been done by the caller - use with caution. // Handles both simple (hash only) and composite (hash + range) keys automatically. -// Example: keyMap, err := KeyInputFromRaw("user123", "session456") func KeyInputFromRaw(hashKeyValue any, rangeKeyValue any) (map[string]types.AttributeValue, error) { key := make(map[string]types.AttributeValue) diff --git a/templates/v2/query/build.go b/templates/v2/query/build.go index 4efd458..3cda725 100644 --- a/templates/v2/query/build.go +++ b/templates/v2/query/build.go @@ -78,7 +78,6 @@ func (qb *QueryBuilder) Build() (string, expression.KeyConditionBuilder, *expres } // calculateIndexParts counts the number of composite key parts in an index. -// Used for index selection priority - more specific indexes are preferred. func (qb *QueryBuilder) calculateIndexParts(idx SecondaryIndex) int { parts := 0 if idx.HashKeyParts != nil { @@ -91,8 +90,6 @@ func (qb *QueryBuilder) calculateIndexParts(idx SecondaryIndex) int { } // buildHashKeyCondition creates the hash key condition for a given index. -// Supports both simple hash keys and composite hash keys. -// Returns the condition and whether the index hash key can be satisfied. func (qb *QueryBuilder) buildHashKeyCondition(idx SecondaryIndex) (*expression.KeyConditionBuilder, bool) { if idx.HashKeyParts != nil { if qb.hasAllKeys(idx.HashKeyParts) { @@ -107,8 +104,6 @@ func (qb *QueryBuilder) buildHashKeyCondition(idx SecondaryIndex) (*expression.K } // buildRangeKeyCondition creates the range key condition for a given index. -// Supports both simple range keys and composite range keys. -// Range keys are optional - returns true if no range key is defined. func (qb *QueryBuilder) buildRangeKeyCondition(idx SecondaryIndex) (*expression.KeyConditionBuilder, bool) { if idx.RangeKeyParts != nil { if qb.hasAllKeys(idx.RangeKeyParts) { @@ -133,7 +128,6 @@ func (qb *QueryBuilder) buildRangeKeyCondition(idx SecondaryIndex) (*expression. } // buildFilterCondition creates filter conditions for attributes not part of the index keys. -// Moves non-key conditions to filter expressions for optimal query performance. func (qb *QueryBuilder) buildFilterCondition(idx SecondaryIndex) *expression.ConditionBuilder { var filterConditions []expression.ConditionBuilder @@ -155,7 +149,6 @@ func (qb *QueryBuilder) buildFilterCondition(idx SecondaryIndex) *expression.Con } // isPartOfIndexKey checks if an attribute is part of the index's key structure. -// Used to determine whether conditions should be key conditions or filter conditions. func (qb *QueryBuilder) isPartOfIndexKey(attrName string, idx SecondaryIndex) bool { if idx.HashKeyParts != nil { for _, part := range idx.HashKeyParts { @@ -180,13 +173,11 @@ func (qb *QueryBuilder) isPartOfIndexKey(attrName string, idx SecondaryIndex) bo // BuildQuery constructs the final DynamoDB QueryInput with all expressions and parameters. // Combines key conditions, filter conditions, pagination, and sorting options. -// Example: input, err := queryBuilder.BuildQuery() func (qb *QueryBuilder) BuildQuery() (*dynamodb.QueryInput, error) { indexName, keyCond, filterCond, exclusiveStartKey, err := qb.Build() if err != nil { return nil, err } - exprBuilder := expression.NewBuilder().WithKeyCondition(keyCond) if filterCond != nil { exprBuilder = exprBuilder.WithFilter(*filterCond) @@ -219,13 +210,11 @@ func (qb *QueryBuilder) BuildQuery() (*dynamodb.QueryInput, error) { // Execute runs the query against DynamoDB and returns strongly-typed results. // Handles the complete query lifecycle: build input, execute, unmarshal results. -// Example: items, err := queryBuilder.Execute(ctx, dynamoClient) func (qb *QueryBuilder) Execute(ctx context.Context, client *dynamodb.Client) ([]SchemaItem, error) { input, err := qb.BuildQuery() if err != nil { return nil, err } - result, err := client.Query(ctx, input) if err != nil { return nil, fmt.Errorf("failed to execute query: %v", err) diff --git a/templates/v2/query/builder.go b/templates/v2/query/builder.go index 4687ba8..91856c6 100644 --- a/templates/v2/query/builder.go +++ b/templates/v2/query/builder.go @@ -14,7 +14,6 @@ type QueryBuilder struct { // NewQueryBuilder creates a new QueryBuilder instance with initialized mixins. // All mixins are properly initialized for immediate use. -// Example: query := NewQueryBuilder().WithEQ("user_id", "123").FilterEQ("status", "active") func NewQueryBuilder() *QueryBuilder { return &QueryBuilder{ FilterMixin: NewFilterMixin(), @@ -25,7 +24,6 @@ func NewQueryBuilder() *QueryBuilder { // Limit sets the maximum number of items and returns QueryBuilder for method chaining. // Controls the number of items returned in a single request. -// Example: query.Limit(25) func (qb *QueryBuilder) Limit(limit int) *QueryBuilder { qb.PaginationMixin.Limit(limit) return qb @@ -33,15 +31,21 @@ func (qb *QueryBuilder) Limit(limit int) *QueryBuilder { // StartFrom sets the exclusive start key and returns QueryBuilder for method chaining. // Use LastEvaluatedKey from previous response for pagination. -// Example: query.StartFrom(previousResponse.LastEvaluatedKey) func (qb *QueryBuilder) StartFrom(lastEvaluatedKey map[string]types.AttributeValue) *QueryBuilder { qb.PaginationMixin.StartFrom(lastEvaluatedKey) return qb } +// WithIndex sets the index name for query a secondary index. +// Allows query GSI or LSI instead of main table. +// Index must exist and be in ACTIVE state. +func (qb *QueryBuilder) WithIndex(indexName string) *QueryBuilder { + qb.IndexName = indexName + return qb +} + // OrderByDesc sets descending sort order and returns QueryBuilder for method chaining. // Only affects sort key ordering, not filter results. -// Example: query.OrderByDesc() // newest first func (qb *QueryBuilder) OrderByDesc() *QueryBuilder { qb.KeyConditionMixin.OrderByDesc() return qb @@ -49,7 +53,6 @@ func (qb *QueryBuilder) OrderByDesc() *QueryBuilder { // OrderByAsc sets ascending sort order and returns QueryBuilder for method chaining. // This is the default sort order. -// Example: query.OrderByAsc() // oldest first func (qb *QueryBuilder) OrderByAsc() *QueryBuilder { qb.KeyConditionMixin.OrderByAsc() return qb @@ -57,7 +60,6 @@ func (qb *QueryBuilder) OrderByAsc() *QueryBuilder { // WithPreferredSortKey sets the preferred sort key and returns QueryBuilder for method chaining. // Hints the index selection algorithm when multiple indexes could satisfy the query. -// Example: query.WithPreferredSortKey("created_at") func (qb *QueryBuilder) WithPreferredSortKey(key string) *QueryBuilder { qb.KeyConditionMixin.WithPreferredSortKey(key) return qb @@ -95,7 +97,6 @@ func (qb *QueryBuilder) setCompositeKey(keyName string, parts []CompositeKeyPart qb.UsedKeys[part.Value] = true } } - compositeValue := qb.buildCompositeKeyValue(parts) qb.Attributes[keyName] = compositeValue qb.UsedKeys[keyName] = true diff --git a/templates/v2/query/builder_filter.go b/templates/v2/query/builder_filter.go index 01927d8..658f736 100644 --- a/templates/v2/query/builder_filter.go +++ b/templates/v2/query/builder_filter.go @@ -15,7 +15,6 @@ const QueryBuilderFilterSugarTemplate = ` // CONVENIENCE METHODS - Only available in ALL mode // FilterEQ adds equality filter and returns QueryBuilder for method chaining. -// Example: query.FilterEQ("status", "active") func (qb *QueryBuilder) FilterEQ(field string, value any) *QueryBuilder { qb.FilterMixin.FilterEQ(field, value) return qb @@ -23,7 +22,6 @@ func (qb *QueryBuilder) FilterEQ(field string, value any) *QueryBuilder { // FilterContains adds contains filter and returns QueryBuilder for method chaining. // Works with String attributes (substring) and Set attributes (membership). -// Example: query.FilterContains("tags", "premium") func (qb *QueryBuilder) FilterContains(field string, value any) *QueryBuilder { qb.FilterMixin.FilterContains(field, value) return qb @@ -38,7 +36,6 @@ func (qb *QueryBuilder) FilterNotContains(field string, value any) *QueryBuilder // FilterBeginsWith adds begins_with filter and returns QueryBuilder for method chaining. // Only works with String attributes for prefix matching. -// Example: query.FilterBeginsWith("email", "admin@") func (qb *QueryBuilder) FilterBeginsWith(field string, value any) *QueryBuilder { qb.FilterMixin.FilterBeginsWith(field, value) return qb @@ -46,35 +43,30 @@ func (qb *QueryBuilder) FilterBeginsWith(field string, value any) *QueryBuilder // FilterBetween adds range filter and returns QueryBuilder for method chaining. // Works with comparable types for inclusive range filtering. -// Example: query.FilterBetween("score", 80, 100) func (qb *QueryBuilder) FilterBetween(field string, start, end any) *QueryBuilder { qb.FilterMixin.FilterBetween(field, start, end) return qb } // FilterGT adds greater than filter and returns QueryBuilder for method chaining. -// Example: query.FilterGT("last_login", cutoffDate) func (qb *QueryBuilder) FilterGT(field string, value any) *QueryBuilder { qb.FilterMixin.FilterGT(field, value) return qb } // FilterLT adds less than filter and returns QueryBuilder for method chaining. -// Example: query.FilterLT("attempts", maxAttempts) func (qb *QueryBuilder) FilterLT(field string, value any) *QueryBuilder { qb.FilterMixin.FilterLT(field, value) return qb } // FilterGTE adds greater than or equal filter and returns QueryBuilder for method chaining. -// Example: query.FilterGTE("age", minimumAge) func (qb *QueryBuilder) FilterGTE(field string, value any) *QueryBuilder { qb.FilterMixin.FilterGTE(field, value) return qb } // FilterLTE adds less than or equal filter and returns QueryBuilder for method chaining. -// Example: query.FilterLTE("file_size", maxFileSize) func (qb *QueryBuilder) FilterLTE(field string, value any) *QueryBuilder { qb.FilterMixin.FilterLTE(field, value) return qb @@ -82,7 +74,6 @@ func (qb *QueryBuilder) FilterLTE(field string, value any) *QueryBuilder { // FilterExists adds attribute exists filter and returns QueryBuilder for method chaining. // Checks if the specified attribute exists in the item. -// Example: query.FilterExists("optional_field") func (qb *QueryBuilder) FilterExists(field string) *QueryBuilder { qb.FilterMixin.FilterExists(field) return qb @@ -96,7 +87,6 @@ func (qb *QueryBuilder) FilterNotExists(field string) *QueryBuilder { } // FilterNE adds not equal filter and returns QueryBuilder for method chaining. -// Example: query.FilterNE("status", "deleted") func (qb *QueryBuilder) FilterNE(field string, value any) *QueryBuilder { qb.FilterMixin.FilterNE(field, value) return qb @@ -104,7 +94,6 @@ func (qb *QueryBuilder) FilterNE(field string, value any) *QueryBuilder { // FilterIn adds IN filter and returns QueryBuilder for method chaining. // For scalar values only - use FilterContains for DynamoDB Sets. -// Example: query.FilterIn("category", "books", "electronics", "clothing") func (qb *QueryBuilder) FilterIn(field string, values ...any) *QueryBuilder { qb.FilterMixin.FilterIn(field, values...) return qb diff --git a/templates/v2/query/builder_with.go b/templates/v2/query/builder_with.go index 301d28c..f386fb5 100644 --- a/templates/v2/query/builder_with.go +++ b/templates/v2/query/builder_with.go @@ -4,7 +4,6 @@ package query const QueryBuilderWithTemplate = ` // With adds key condition and returns QueryBuilder for method chaining. // Only works with partition and sort key attributes for efficient querying. -// Example: query.With("user_id", EQ, "123").With("created_at", GT, timestamp) func (qb *QueryBuilder) With(field string, op OperatorType, values ...any) *QueryBuilder { qb.KeyConditionMixin.With(field, op, values...) if op == EQ && len(values) == 1 { @@ -21,7 +20,6 @@ const QueryBuilderWithSugarTemplate = ` // WithEQ adds equality key condition and returns QueryBuilder for method chaining. // Required for partition keys, commonly used for sort keys. -// Example: query.WithEQ("user_id", "123") func (qb *QueryBuilder) WithEQ(field string, value any) *QueryBuilder { qb.KeyConditionMixin.WithEQ(field, value) qb.Attributes[field] = value @@ -31,7 +29,6 @@ func (qb *QueryBuilder) WithEQ(field string, value any) *QueryBuilder { // WithBetween adds range key condition and returns QueryBuilder for method chaining. // Only valid for sort keys, not partition keys. -// Example: query.WithBetween("timestamp", startTime, endTime) func (qb *QueryBuilder) WithBetween(field string, start, end any) *QueryBuilder { qb.KeyConditionMixin.WithBetween(field, start, end) qb.Attributes[field+"_start"] = start @@ -42,7 +39,6 @@ func (qb *QueryBuilder) WithBetween(field string, start, end any) *QueryBuilder // WithGT adds greater than key condition and returns QueryBuilder for method chaining. // Only valid for sort keys in range queries. -// Example: query.WithGT("created_at", yesterday) func (qb *QueryBuilder) WithGT(field string, value any) *QueryBuilder { qb.KeyConditionMixin.WithGT(field, value) qb.Attributes[field] = value @@ -52,7 +48,6 @@ func (qb *QueryBuilder) WithGT(field string, value any) *QueryBuilder { // WithGTE adds greater than or equal key condition and returns QueryBuilder for method chaining. // Only valid for sort keys in range queries. -// Example: query.WithGTE("score", minimumScore) func (qb *QueryBuilder) WithGTE(field string, value any) *QueryBuilder { qb.KeyConditionMixin.WithGTE(field, value) qb.Attributes[field] = value @@ -62,7 +57,6 @@ func (qb *QueryBuilder) WithGTE(field string, value any) *QueryBuilder { // WithLT adds less than key condition and returns QueryBuilder for method chaining. // Only valid for sort keys in range queries. -// Example: query.WithLT("expiry_date", now) func (qb *QueryBuilder) WithLT(field string, value any) *QueryBuilder { qb.KeyConditionMixin.WithLT(field, value) qb.Attributes[field] = value @@ -72,7 +66,6 @@ func (qb *QueryBuilder) WithLT(field string, value any) *QueryBuilder { // WithLTE adds less than or equal key condition and returns QueryBuilder for method chaining. // Only valid for sort keys in range queries. -// Example: query.WithLTE("price", maxBudget) func (qb *QueryBuilder) WithLTE(field string, value any) *QueryBuilder { qb.KeyConditionMixin.WithLTE(field, value) qb.Attributes[field] = value @@ -83,14 +76,11 @@ func (qb *QueryBuilder) WithLTE(field string, value any) *QueryBuilder { // WithIndexHashKey sets hash key for any index by name. // Automatically handles both simple and composite keys based on schema metadata. // For composite keys, pass values in the order they appear in the schema. -// Example: query.WithIndexHashKey("user-status-index", "user123") -// Example: query.WithIndexHashKey("tenant-user-index", "tenant1", "user123") // composite func (qb *QueryBuilder) WithIndexHashKey(indexName string, values ...any) *QueryBuilder { index := qb.getIndexByName(indexName) if index == nil { return qb } - if index.HashKeyParts != nil { nonConstantParts := qb.getNonConstantParts(index.HashKeyParts) if len(values) != len(nonConstantParts) { @@ -111,14 +101,11 @@ func (qb *QueryBuilder) WithIndexHashKey(indexName string, values ...any) *Query // WithIndexRangeKey sets range key for any index by name. // Automatically handles both simple and composite keys based on schema metadata. // For composite keys, pass values in the order they appear in the schema. -// Example: query.WithIndexRangeKey("user-status-index", "active") -// Example: query.WithIndexRangeKey("date-type-index", "2023-01-01", "ORDER") // composite func (qb *QueryBuilder) WithIndexRangeKey(indexName string, values ...any) *QueryBuilder { index := qb.getIndexByName(indexName) if index == nil || index.RangeKey == "" { return qb } - if index.RangeKeyParts != nil { nonConstantParts := qb.getNonConstantParts(index.RangeKeyParts) if len(values) != len(nonConstantParts) { @@ -138,7 +125,6 @@ func (qb *QueryBuilder) WithIndexRangeKey(indexName string, values ...any) *Quer // WithIndexRangeKeyBetween sets range key condition for any index with BETWEEN operator. // Only works with simple range keys, not composite ones. -// Example: query.WithIndexRangeKeyBetween("date-index", startDate, endDate) func (qb *QueryBuilder) WithIndexRangeKeyBetween(indexName string, start, end any) *QueryBuilder { index := qb.getIndexByName(indexName) if index == nil || index.RangeKey == "" || index.RangeKeyParts != nil { @@ -153,7 +139,6 @@ func (qb *QueryBuilder) WithIndexRangeKeyBetween(indexName string, start, end an // WithIndexRangeKeyGT sets range key condition for any index with GT operator. // Only works with simple range keys, not composite ones. -// Example: query.WithIndexRangeKeyGT("score-index", 100) func (qb *QueryBuilder) WithIndexRangeKeyGT(indexName string, value any) *QueryBuilder { index := qb.getIndexByName(indexName) if index == nil || index.RangeKey == "" || index.RangeKeyParts != nil { @@ -167,7 +152,6 @@ func (qb *QueryBuilder) WithIndexRangeKeyGT(indexName string, value any) *QueryB // WithIndexRangeKeyLT sets range key condition for any index with LT operator. // Only works with simple range keys, not composite ones. -// Example: query.WithIndexRangeKeyLT("timestamp-index", cutoffTime) func (qb *QueryBuilder) WithIndexRangeKeyLT(indexName string, value any) *QueryBuilder { index := qb.getIndexByName(indexName) if index == nil || index.RangeKey == "" || index.RangeKeyParts != nil { @@ -181,7 +165,6 @@ func (qb *QueryBuilder) WithIndexRangeKeyLT(indexName string, value any) *QueryB // WithIndexRangeKeyGTE sets range key condition for any index with GTE operator. // Only works with simple range keys, not composite ones. -// Example: query.WithIndexRangeKeyGTE("score-index", 100) func (qb *QueryBuilder) WithIndexRangeKeyGTE(indexName string, value any) *QueryBuilder { index := qb.getIndexByName(indexName) if index == nil || index.RangeKey == "" || index.RangeKeyParts != nil { @@ -195,7 +178,6 @@ func (qb *QueryBuilder) WithIndexRangeKeyGTE(indexName string, value any) *Query // WithIndexRangeKeyLTE sets range key condition for any index with LTE operator. // Only works with simple range keys, not composite ones. -// Example: query.WithIndexRangeKeyLTE("timestamp-index", cutoffTime) func (qb *QueryBuilder) WithIndexRangeKeyLTE(indexName string, value any) *QueryBuilder { index := qb.getIndexByName(indexName) if index == nil || index.RangeKey == "" || index.RangeKeyParts != nil { diff --git a/templates/v2/query/utils.go b/templates/v2/query/utils.go index 1825c86..c1f0fb5 100644 --- a/templates/v2/query/utils.go +++ b/templates/v2/query/utils.go @@ -3,8 +3,6 @@ package query // QueryBuilderUtilsTemplate provides utility functions for composite key handling const QueryBuilderUtilsTemplate = ` // hasAllKeys checks if all non-constant parts of a composite key are available. -// Used to determine if a composite key can be fully constructed from current conditions. -// Constants are always available, variables must be present in UsedKeys. func (qb *QueryBuilder) hasAllKeys(parts []CompositeKeyPart) bool { for _, part := range parts { if !part.IsConstant && !qb.UsedKeys[part.Value] { @@ -15,8 +13,6 @@ func (qb *QueryBuilder) hasAllKeys(parts []CompositeKeyPart) bool { } // buildCompositeKeyCondition creates a key condition for composite keys. -// Combines multiple key parts into a single equality condition using "#" separator. -// Used internally by the index selection algorithm for complex key structures. func (qb *QueryBuilder) buildCompositeKeyCondition(parts []CompositeKeyPart) expression.KeyConditionBuilder { compositeKeyName := qb.getCompositeKeyName(parts) compositeValue := qb.buildCompositeKeyValue(parts) @@ -24,9 +20,6 @@ func (qb *QueryBuilder) buildCompositeKeyCondition(parts []CompositeKeyPart) exp } // getCompositeKeyName generates the attribute name for a composite key. -// For single parts, returns the part name directly. -// For multiple parts, joins them with "#" separator for DynamoDB storage. -// Example: ["user", "tenant"] -> "user#tenant" func (qb *QueryBuilder) getCompositeKeyName(parts []CompositeKeyPart) string { switch len(parts) { case 0: @@ -43,9 +36,6 @@ func (qb *QueryBuilder) getCompositeKeyName(parts []CompositeKeyPart) string { } // buildCompositeKeyValue constructs the actual value for a composite key. -// Combines constant values and variable values from query attributes. -// Uses "#" separator to create a single string value for DynamoDB. -// Example: constant "USER" + variable "123" -> "USER#123" func (qb *QueryBuilder) buildCompositeKeyValue(parts []CompositeKeyPart) string { if len(parts) == 0 { return "" @@ -62,14 +52,10 @@ func (qb *QueryBuilder) buildCompositeKeyValue(parts []CompositeKeyPart) string } // formatAttributeValue converts any Go value to its string representation for composite keys. -// Provides optimized fast paths for common types (string, bool) and proper handling -// of complex types through AWS SDK marshaling. Ensures consistent string formatting -// for reliable composite key construction. -func (qb *QueryBuilder) formatAttributeValue(value interface{}) string { +func (qb *QueryBuilder) formatAttributeValue(value any) string { if value == nil { return "" } - switch v := value.(type) { case string: return v @@ -79,12 +65,10 @@ func (qb *QueryBuilder) formatAttributeValue(value interface{}) string { } return "false" } - av, err := attributevalue.Marshal(value) if err != nil { return fmt.Sprintf("%v", value) } - switch typed := av.(type) { case *types.AttributeValueMemberS: return typed.Value diff --git a/templates/v2/scan/build.go b/templates/v2/scan/build.go index b459229..f629d08 100644 --- a/templates/v2/scan/build.go +++ b/templates/v2/scan/build.go @@ -5,7 +5,6 @@ const ScanBuilderBuildTemplate = ` // BuildScan constructs the final DynamoDB ScanInput with all configured options. // Combines filter conditions, projection attributes, pagination, and parallel scan settings. // Handles expression building and attribute mapping automatically. -// Example: input, err := scanBuilder.BuildScan() func (sb *ScanBuilder) BuildScan() (*dynamodb.ScanInput, error) { input := &dynamodb.ScanInput{ TableName: aws.String(TableName), @@ -54,7 +53,6 @@ func (sb *ScanBuilder) BuildScan() (*dynamodb.ScanInput, error) { input.ExpressionAttributeValues = expr.Values() } } - if sb.LimitValue != nil { input.Limit = aws.Int32(int32(*sb.LimitValue)) } @@ -71,13 +69,11 @@ func (sb *ScanBuilder) BuildScan() (*dynamodb.ScanInput, error) { // Execute runs the scan against DynamoDB and returns strongly-typed results. // Handles the complete scan lifecycle: build input, execute, unmarshal results. // Returns all items that match the filter conditions as SchemaItem structs. -// Example: items, err := scanBuilder.Execute(ctx, dynamoClient) func (sb *ScanBuilder) Execute(ctx context.Context, client *dynamodb.Client) ([]SchemaItem, error) { input, err := sb.BuildScan() if err != nil { return nil, err } - result, err := client.Scan(ctx, input) if err != nil { return nil, fmt.Errorf("failed to execute scan: %v", err) diff --git a/templates/v2/scan/builder.go b/templates/v2/scan/builder.go index f707a15..ce38d17 100644 --- a/templates/v2/scan/builder.go +++ b/templates/v2/scan/builder.go @@ -24,7 +24,6 @@ type ParallelScanConfig struct { // NewScanBuilder creates a new ScanBuilder instance with initialized mixins. // All mixins are properly initialized for immediate use. -// Example: scan := NewScanBuilder().FilterEQ("status", "active").Limit(100) func NewScanBuilder() *ScanBuilder { return &ScanBuilder{ FilterMixin: NewFilterMixin(), @@ -34,8 +33,6 @@ func NewScanBuilder() *ScanBuilder { // Limit sets the maximum number of items and returns ScanBuilder for method chaining. // Controls the number of items returned in a single scan request. -// Note: DynamoDB may return fewer items due to size limits even with this setting. -// Example: scan.Limit(100) func (sb *ScanBuilder) Limit(limit int) *ScanBuilder { sb.PaginationMixin.Limit(limit) return sb @@ -43,7 +40,6 @@ func (sb *ScanBuilder) Limit(limit int) *ScanBuilder { // StartFrom sets the exclusive start key and returns ScanBuilder for method chaining. // Use LastEvaluatedKey from previous response for pagination. -// Example: scan.StartFrom(previousResponse.LastEvaluatedKey) func (sb *ScanBuilder) StartFrom(lastEvaluatedKey map[string]types.AttributeValue) *ScanBuilder { sb.PaginationMixin.StartFrom(lastEvaluatedKey) return sb @@ -52,7 +48,6 @@ func (sb *ScanBuilder) StartFrom(lastEvaluatedKey map[string]types.AttributeValu // WithIndex sets the index name for scanning a secondary index. // Allows scanning GSI or LSI instead of the main table. // Index must exist and be in ACTIVE state. -// Example: scan.WithIndex("status-index") func (sb *ScanBuilder) WithIndex(indexName string) *ScanBuilder { sb.IndexName = indexName return sb @@ -61,7 +56,6 @@ func (sb *ScanBuilder) WithIndex(indexName string) *ScanBuilder { // WithProjection sets the projection attributes to return specific fields only. // Reduces network traffic and costs by returning only needed attributes. // Pass attribute names that should be included in the response. -// Example: scan.WithProjection([]string{"id", "name", "status"}) func (sb *ScanBuilder) WithProjection(attributes []string) *ScanBuilder { sb.ProjectionAttributes = attributes return sb @@ -71,7 +65,6 @@ func (sb *ScanBuilder) WithProjection(attributes []string) *ScanBuilder { // Divides the table into segments for concurrent processing by multiple workers. // totalSegments: how many segments to divide the table (typically number of workers) // segment: which segment this worker processes (0-based, must be < totalSegments) -// Example: scan.WithParallelScan(4, 0) // Process segment 0 of 4 total segments func (sb *ScanBuilder) WithParallelScan(totalSegments, segment int) *ScanBuilder { sb.ParallelScanConfig = &ParallelScanConfig{ TotalSegments: totalSegments, diff --git a/templates/v2/scan/builder_filter.go b/templates/v2/scan/builder_filter.go index 53a2744..9e3b097 100644 --- a/templates/v2/scan/builder_filter.go +++ b/templates/v2/scan/builder_filter.go @@ -15,7 +15,6 @@ const ScanBuilderFilterSugarTemplate = ` // CONVENIENCE METHODS - Only available in ALL mode // FilterEQ adds equality filter and returns ScanBuilder for method chaining. -// Example: scan.FilterEQ("status", "active") func (sb *ScanBuilder) FilterEQ(field string, value any) *ScanBuilder { sb.FilterMixin.FilterEQ(field, value) return sb @@ -23,7 +22,6 @@ func (sb *ScanBuilder) FilterEQ(field string, value any) *ScanBuilder { // FilterContains adds contains filter and returns ScanBuilder for method chaining. // Works with String attributes (substring) and Set attributes (membership). -// Example: scan.FilterContains("tags", "premium") func (sb *ScanBuilder) FilterContains(field string, value any) *ScanBuilder { sb.FilterMixin.FilterContains(field, value) return sb @@ -38,7 +36,6 @@ func (sb *ScanBuilder) FilterNotContains(field string, value any) *ScanBuilder { // FilterBeginsWith adds begins_with filter and returns ScanBuilder for method chaining. // Only works with String attributes for prefix matching. -// Example: scan.FilterBeginsWith("email", "admin@") func (sb *ScanBuilder) FilterBeginsWith(field string, value any) *ScanBuilder { sb.FilterMixin.FilterBeginsWith(field, value) return sb @@ -46,35 +43,30 @@ func (sb *ScanBuilder) FilterBeginsWith(field string, value any) *ScanBuilder { // FilterBetween adds range filter and returns ScanBuilder for method chaining. // Works with comparable types for inclusive range filtering. -// Example: scan.FilterBetween("score", 80, 100) func (sb *ScanBuilder) FilterBetween(field string, start, end any) *ScanBuilder { sb.FilterMixin.FilterBetween(field, start, end) return sb } // FilterGT adds greater than filter and returns ScanBuilder for method chaining. -// Example: scan.FilterGT("last_login", cutoffDate) func (sb *ScanBuilder) FilterGT(field string, value any) *ScanBuilder { sb.FilterMixin.FilterGT(field, value) return sb } // FilterLT adds less than filter and returns ScanBuilder for method chaining. -// Example: scan.FilterLT("attempts", maxAttempts) func (sb *ScanBuilder) FilterLT(field string, value any) *ScanBuilder { sb.FilterMixin.FilterLT(field, value) return sb } // FilterGTE adds greater than or equal filter and returns ScanBuilder for method chaining. -// Example: scan.FilterGTE("age", minimumAge) func (sb *ScanBuilder) FilterGTE(field string, value any) *ScanBuilder { sb.FilterMixin.FilterGTE(field, value) return sb } // FilterLTE adds less than or equal filter and returns ScanBuilder for method chaining. -// Example: scan.FilterLTE("file_size", maxFileSize) func (sb *ScanBuilder) FilterLTE(field string, value any) *ScanBuilder { sb.FilterMixin.FilterLTE(field, value) return sb @@ -82,7 +74,6 @@ func (sb *ScanBuilder) FilterLTE(field string, value any) *ScanBuilder { // FilterExists adds attribute exists filter and returns ScanBuilder for method chaining. // Checks if the specified attribute exists in the item. -// Example: scan.FilterExists("optional_field") func (sb *ScanBuilder) FilterExists(field string) *ScanBuilder { sb.FilterMixin.FilterExists(field) return sb @@ -96,7 +87,6 @@ func (sb *ScanBuilder) FilterNotExists(field string) *ScanBuilder { } // FilterNE adds not equal filter and returns ScanBuilder for method chaining. -// Example: scan.FilterNE("status", "deleted") func (sb *ScanBuilder) FilterNE(field string, value any) *ScanBuilder { sb.FilterMixin.FilterNE(field, value) return sb @@ -104,7 +94,6 @@ func (sb *ScanBuilder) FilterNE(field string, value any) *ScanBuilder { // FilterIn adds IN filter and returns ScanBuilder for method chaining. // For scalar values only - use FilterContains for DynamoDB Sets. -// Example: scan.FilterIn("category", "books", "electronics", "clothing") func (sb *ScanBuilder) FilterIn(field string, values ...any) *ScanBuilder { sb.FilterMixin.FilterIn(field, values...) return sb diff --git a/templates/v2/scan/universal.go b/templates/v2/scan/universal.go index 10bca14..a15a15c 100644 --- a/templates/v2/scan/universal.go +++ b/templates/v2/scan/universal.go @@ -5,7 +5,6 @@ const ScanBuilderUniversalTemplate = ` // Filter adds a filter condition using the universal operator system. // Works with any table attribute and validates operator compatibility with field types. // Filters are applied after items are read from DynamoDB, affecting the returned results. -// Example: scan.Filter("status", EQ, "active").Filter("tags", CONTAINS, "premium") func (sb *ScanBuilder) Filter(field string, op OperatorType, values ...any) *ScanBuilder { if !ValidateValues(op, values) { return sb @@ -33,14 +32,12 @@ func (sb *ScanBuilder) Filter(field string, op OperatorType, values ...any) *Sca // FilterEQ is a convenience method for equality filter conditions. // Most commonly used filter for exact matches. -// Example: scan.FilterEQ("status", "active") func (sb *ScanBuilder) FilterEQ(field string, value any) *ScanBuilder { return sb.Filter(field, EQ, value) } // FilterContains is a convenience method for contains filter conditions. // Works with String attributes (substring search) and Set attributes (membership check). -// Example: scan.FilterContains("description", "urgent") or scan.FilterContains("tags", "vip") func (sb *ScanBuilder) FilterContains(field string, value any) *ScanBuilder { return sb.Filter(field, CONTAINS, value) } @@ -48,7 +45,6 @@ func (sb *ScanBuilder) FilterContains(field string, value any) *ScanBuilder { // FilterBeginsWith is a convenience method for begins_with filter conditions. // Only works with String attributes for prefix matching. // Useful for hierarchical data or prefix-based searches. -// Example: scan.FilterBeginsWith("email", "admin@") func (sb *ScanBuilder) FilterBeginsWith(field string, value any) *ScanBuilder { return sb.Filter(field, BEGINS_WITH, value) } @@ -56,35 +52,30 @@ func (sb *ScanBuilder) FilterBeginsWith(field string, value any) *ScanBuilder { // FilterBetween is a convenience method for range filter conditions. // Works with comparable types (strings, numbers, dates) for inclusive range filtering. // Useful for date ranges, score ranges, or any bounded searches. -// Example: scan.FilterBetween("score", 80, 100) func (sb *ScanBuilder) FilterBetween(field string, start, end any) *ScanBuilder { return sb.Filter(field, BETWEEN, start, end) } // FilterGT is a convenience method for greater than filter conditions. // Works with comparable types for threshold-based filtering. -// Example: scan.FilterGT("last_login", cutoffDate) func (sb *ScanBuilder) FilterGT(field string, value any) *ScanBuilder { return sb.Filter(field, GT, value) } // FilterLT is a convenience method for less than filter conditions. // Works with comparable types for upper bound filtering. -// Example: scan.FilterLT("attempts", maxAttempts) func (sb *ScanBuilder) FilterLT(field string, value any) *ScanBuilder { return sb.Filter(field, LT, value) } // FilterGTE is a convenience method for greater than or equal filter conditions. // Works with comparable types for inclusive lower bound filtering. -// Example: scan.FilterGTE("age", minimumAge) func (sb *ScanBuilder) FilterGTE(field string, value any) *ScanBuilder { return sb.Filter(field, GTE, value) } // FilterLTE is a convenience method for less than or equal filter conditions. // Works with comparable types for inclusive upper bound filtering. -// Example: scan.FilterLTE("file_size", maxFileSize) func (sb *ScanBuilder) FilterLTE(field string, value any) *ScanBuilder { return sb.Filter(field, LTE, value) } @@ -92,7 +83,6 @@ func (sb *ScanBuilder) FilterLTE(field string, value any) *ScanBuilder { // FilterExists is a convenience method for attribute exists filter conditions. // Checks if the specified attribute exists in the item, regardless of its value. // Useful for filtering items that have optional attributes populated. -// Example: scan.FilterExists("optional_field") func (sb *ScanBuilder) FilterExists(field string) *ScanBuilder { return sb.Filter(field, EXISTS) } @@ -100,7 +90,6 @@ func (sb *ScanBuilder) FilterExists(field string) *ScanBuilder { // FilterNotExists is a convenience method for attribute not exists filter conditions. // Checks if the specified attribute does not exist in the item. // Useful for finding items missing certain attributes or filtering incomplete records. -// Example: scan.FilterNotExists("deprecated_field") func (sb *ScanBuilder) FilterNotExists(field string) *ScanBuilder { return sb.Filter(field, NOT_EXISTS) }