From a3c2fbaa78184523cae23022be3aea4101000323 Mon Sep 17 00:00:00 2001 From: Daniel Xu Date: Mon, 4 Mar 2024 13:03:05 -0800 Subject: [PATCH 1/2] fix dynamoodb codegen for datetime partition keys for GSIs --- samples/db.yml | 27 ++ .../db/dynamodb/dynamodb.go | 51 ++ .../db/dynamodb/thingwithdatetimegsi.go | 412 ++++++++++++++++ samples/gen-go-db-custom-path/db/interface.go | 85 ++++ samples/gen-go-db-custom-path/db/mock_db.go | 85 ++++ .../gen-go-db-custom-path/db/tests/tests.go | 453 ++++++++++++++++++ .../models/thing_with_datetime_g_s_i.go | 71 +++ .../gen-go-db-only/db/dynamodb/dynamodb.go | 51 ++ .../db/dynamodb/thingwithdatetimegsi.go | 412 ++++++++++++++++ samples/gen-go-db-only/db/interface.go | 85 ++++ samples/gen-go-db-only/db/mock_db.go | 85 ++++ samples/gen-go-db-only/db/tests/tests.go | 453 ++++++++++++++++++ .../models/thing_with_datetime_g_s_i.go | 71 +++ .../models/thing_with_datetime_g_s_i.go | 71 +++ .../gen-go-db/server/db/dynamodb/dynamodb.go | 51 ++ .../db/dynamodb/thingwithdatetimegsi.go | 412 ++++++++++++++++ samples/gen-go-db/server/db/interface.go | 85 ++++ samples/gen-go-db/server/db/mock_db.go | 85 ++++ samples/gen-go-db/server/db/tests/tests.go | 453 ++++++++++++++++++ samples/gen-js-db-custom-path/index.d.ts | 5 + samples/gen-js-db/index.d.ts | 5 + server/gendb/bindata.go | 6 +- server/gendb/table.go.tmpl | 8 + 23 files changed, 3519 insertions(+), 3 deletions(-) create mode 100644 samples/gen-go-db-custom-path/db/dynamodb/thingwithdatetimegsi.go create mode 100644 samples/gen-go-db-custom-path/models/thing_with_datetime_g_s_i.go create mode 100644 samples/gen-go-db-only/db/dynamodb/thingwithdatetimegsi.go create mode 100644 samples/gen-go-db-only/models/thing_with_datetime_g_s_i.go create mode 100644 samples/gen-go-db/models/thing_with_datetime_g_s_i.go create mode 100644 samples/gen-go-db/server/db/dynamodb/thingwithdatetimegsi.go diff --git a/samples/db.yml b/samples/db.yml index 2782b196..7b04d331 100644 --- a/samples/db.yml +++ b/samples/db.yml @@ -828,3 +828,30 @@ definitions: properties: name: type: string + + ThingWithDatetimeGSI: + x-db: + AllowOverwrites: false + AllowPrimaryIndexScan: true + AllowSecondaryIndexScan: + - byDate + DynamoDB: + KeySchema: + - AttributeName: id + KeyType: HASH + GlobalSecondaryIndexes: + - IndexName: byDate + Projection: + ProjectionType: ALL + KeySchema: + - AttributeName: datetime + KeyType: HASH + - AttributeName: id + KeyType: RANGE + type: object + properties: + id: + type: string + datetime: + type: string + format: date-time diff --git a/samples/gen-go-db-custom-path/db/dynamodb/dynamodb.go b/samples/gen-go-db-custom-path/db/dynamodb/dynamodb.go index 43e37471..ecc721c3 100644 --- a/samples/gen-go-db-custom-path/db/dynamodb/dynamodb.go +++ b/samples/gen-go-db-custom-path/db/dynamodb/dynamodb.go @@ -56,6 +56,8 @@ type Config struct { ThingWithDateRangeTable ThingWithDateRangeTable // ThingWithDateTimeCompositeTable configuration. ThingWithDateTimeCompositeTable ThingWithDateTimeCompositeTable + // ThingWithDatetimeGSITable configuration. + ThingWithDatetimeGSITable ThingWithDatetimeGSITable // ThingWithEnumHashKeyTable configuration. ThingWithEnumHashKeyTable ThingWithEnumHashKeyTable // ThingWithMatchingKeysTable configuration. @@ -277,6 +279,20 @@ func New(config Config) (*DB, error) { if thingWithDateTimeCompositeTable.WriteCapacityUnits == 0 { thingWithDateTimeCompositeTable.WriteCapacityUnits = config.DefaultWriteCapacityUnits } + // configure ThingWithDatetimeGSI table + thingWithDatetimeGSITable := config.ThingWithDatetimeGSITable + if thingWithDatetimeGSITable.DynamoDBAPI == nil { + thingWithDatetimeGSITable.DynamoDBAPI = config.DynamoDBAPI + } + if thingWithDatetimeGSITable.Prefix == "" { + thingWithDatetimeGSITable.Prefix = config.DefaultPrefix + } + if thingWithDatetimeGSITable.ReadCapacityUnits == 0 { + thingWithDatetimeGSITable.ReadCapacityUnits = config.DefaultReadCapacityUnits + } + if thingWithDatetimeGSITable.WriteCapacityUnits == 0 { + thingWithDatetimeGSITable.WriteCapacityUnits = config.DefaultWriteCapacityUnits + } // configure ThingWithEnumHashKey table thingWithEnumHashKeyTable := config.ThingWithEnumHashKeyTable if thingWithEnumHashKeyTable.DynamoDBAPI == nil { @@ -418,6 +434,7 @@ func New(config Config) (*DB, error) { thingWithCompositeEnumAttributesTable: thingWithCompositeEnumAttributesTable, thingWithDateRangeTable: thingWithDateRangeTable, thingWithDateTimeCompositeTable: thingWithDateTimeCompositeTable, + thingWithDatetimeGSITable: thingWithDatetimeGSITable, thingWithEnumHashKeyTable: thingWithEnumHashKeyTable, thingWithMatchingKeysTable: thingWithMatchingKeysTable, thingWithMultiUseCompositeAttributeTable: thingWithMultiUseCompositeAttributeTable, @@ -445,6 +462,7 @@ type DB struct { thingWithCompositeEnumAttributesTable ThingWithCompositeEnumAttributesTable thingWithDateRangeTable ThingWithDateRangeTable thingWithDateTimeCompositeTable ThingWithDateTimeCompositeTable + thingWithDatetimeGSITable ThingWithDatetimeGSITable thingWithEnumHashKeyTable ThingWithEnumHashKeyTable thingWithMatchingKeysTable ThingWithMatchingKeysTable thingWithMultiUseCompositeAttributeTable ThingWithMultiUseCompositeAttributeTable @@ -499,6 +517,9 @@ func (d DB) CreateTables(ctx context.Context) error { if err := d.thingWithDateTimeCompositeTable.create(ctx); err != nil { return err } + if err := d.thingWithDatetimeGSITable.create(ctx); err != nil { + return err + } if err := d.thingWithEnumHashKeyTable.create(ctx); err != nil { return err } @@ -999,6 +1020,36 @@ func (d DB) DeleteThingWithDateTimeComposite(ctx context.Context, typeVar string return d.thingWithDateTimeCompositeTable.deleteThingWithDateTimeComposite(ctx, typeVar, id, created, resource) } +// SaveThingWithDatetimeGSI saves a ThingWithDatetimeGSI to the database. +func (d DB) SaveThingWithDatetimeGSI(ctx context.Context, m models.ThingWithDatetimeGSI) error { + return d.thingWithDatetimeGSITable.saveThingWithDatetimeGSI(ctx, m) +} + +// GetThingWithDatetimeGSI retrieves a ThingWithDatetimeGSI from the database. +func (d DB) GetThingWithDatetimeGSI(ctx context.Context, id string) (*models.ThingWithDatetimeGSI, error) { + return d.thingWithDatetimeGSITable.getThingWithDatetimeGSI(ctx, id) +} + +// ScanThingWithDatetimeGSIs runs a scan on the ThingWithDatetimeGSIs table. +func (d DB) ScanThingWithDatetimeGSIs(ctx context.Context, input db.ScanThingWithDatetimeGSIsInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + return d.thingWithDatetimeGSITable.scanThingWithDatetimeGSIs(ctx, input, fn) +} + +// DeleteThingWithDatetimeGSI deletes a ThingWithDatetimeGSI from the database. +func (d DB) DeleteThingWithDatetimeGSI(ctx context.Context, id string) error { + return d.thingWithDatetimeGSITable.deleteThingWithDatetimeGSI(ctx, id) +} + +// GetThingWithDatetimeGSIsByDatetimeAndID retrieves a page of ThingWithDatetimeGSIs from the database. +func (d DB) GetThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + return d.thingWithDatetimeGSITable.getThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn) +} + +// ScanThingWithDatetimeGSIsByDatetimeAndID runs a scan on the DatetimeAndID index. +func (d DB) ScanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + return d.thingWithDatetimeGSITable.scanThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn) +} + // SaveThingWithEnumHashKey saves a ThingWithEnumHashKey to the database. func (d DB) SaveThingWithEnumHashKey(ctx context.Context, m models.ThingWithEnumHashKey) error { return d.thingWithEnumHashKeyTable.saveThingWithEnumHashKey(ctx, m) diff --git a/samples/gen-go-db-custom-path/db/dynamodb/thingwithdatetimegsi.go b/samples/gen-go-db-custom-path/db/dynamodb/thingwithdatetimegsi.go new file mode 100644 index 00000000..db3c7306 --- /dev/null +++ b/samples/gen-go-db-custom-path/db/dynamodb/thingwithdatetimegsi.go @@ -0,0 +1,412 @@ +package dynamodb + +import ( + "context" + "fmt" + + "github.com/Clever/wag/samples/gen-go-db-custom-path/models/v9" + "github.com/Clever/wag/samples/v9/gen-go-db-custom-path/db" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface" + "github.com/go-openapi/strfmt" +) + +var _ = strfmt.DateTime{} + +// ThingWithDatetimeGSITable represents the user-configurable properties of the ThingWithDatetimeGSI table. +type ThingWithDatetimeGSITable struct { + DynamoDBAPI dynamodbiface.DynamoDBAPI + Prefix string + TableName string + ReadCapacityUnits int64 + WriteCapacityUnits int64 +} + +// ddbThingWithDatetimeGSIPrimaryKey represents the primary key of a ThingWithDatetimeGSI in DynamoDB. +type ddbThingWithDatetimeGSIPrimaryKey struct { + ID string `dynamodbav:"id"` +} + +// ddbThingWithDatetimeGSIGSIByDate represents the byDate GSI. +type ddbThingWithDatetimeGSIGSIByDate struct { + Datetime strfmt.DateTime `dynamodbav:"datetime"` + ID string `dynamodbav:"id"` +} + +// ddbThingWithDatetimeGSI represents a ThingWithDatetimeGSI as stored in DynamoDB. +type ddbThingWithDatetimeGSI struct { + models.ThingWithDatetimeGSI +} + +func (t ThingWithDatetimeGSITable) name() string { + if t.TableName != "" { + return t.TableName + } + return fmt.Sprintf("%s-thing-with-datetime-g-s-is", t.Prefix) +} + +func (t ThingWithDatetimeGSITable) create(ctx context.Context) error { + if _, err := t.DynamoDBAPI.CreateTableWithContext(ctx, &dynamodb.CreateTableInput{ + AttributeDefinitions: []*dynamodb.AttributeDefinition{ + { + AttributeName: aws.String("datetime"), + AttributeType: aws.String("S"), + }, + { + AttributeName: aws.String("id"), + AttributeType: aws.String("S"), + }, + }, + KeySchema: []*dynamodb.KeySchemaElement{ + { + AttributeName: aws.String("id"), + KeyType: aws.String(dynamodb.KeyTypeHash), + }, + }, + GlobalSecondaryIndexes: []*dynamodb.GlobalSecondaryIndex{ + { + IndexName: aws.String("byDate"), + Projection: &dynamodb.Projection{ + ProjectionType: aws.String("ALL"), + }, + KeySchema: []*dynamodb.KeySchemaElement{ + { + AttributeName: aws.String("datetime"), + KeyType: aws.String(dynamodb.KeyTypeHash), + }, + { + AttributeName: aws.String("id"), + KeyType: aws.String(dynamodb.KeyTypeRange), + }, + }, + ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ + ReadCapacityUnits: aws.Int64(t.ReadCapacityUnits), + WriteCapacityUnits: aws.Int64(t.WriteCapacityUnits), + }, + }, + }, + ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ + ReadCapacityUnits: aws.Int64(t.ReadCapacityUnits), + WriteCapacityUnits: aws.Int64(t.WriteCapacityUnits), + }, + TableName: aws.String(t.name()), + }); err != nil { + return err + } + return nil +} + +func (t ThingWithDatetimeGSITable) saveThingWithDatetimeGSI(ctx context.Context, m models.ThingWithDatetimeGSI) error { + data, err := encodeThingWithDatetimeGSI(m) + if err != nil { + return err + } + _, err = t.DynamoDBAPI.PutItemWithContext(ctx, &dynamodb.PutItemInput{ + TableName: aws.String(t.name()), + Item: data, + ExpressionAttributeNames: map[string]*string{ + "#ID": aws.String("id"), + }, + ConditionExpression: aws.String("attribute_not_exists(#ID)"), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeConditionalCheckFailedException: + return db.ErrThingWithDatetimeGSIAlreadyExists{ + ID: m.ID, + } + case dynamodb.ErrCodeResourceNotFoundException: + return fmt.Errorf("table or index not found: %s", t.name()) + } + } + return err + } + return nil +} + +func (t ThingWithDatetimeGSITable) getThingWithDatetimeGSI(ctx context.Context, id string) (*models.ThingWithDatetimeGSI, error) { + key, err := dynamodbattribute.MarshalMap(ddbThingWithDatetimeGSIPrimaryKey{ + ID: id, + }) + if err != nil { + return nil, err + } + res, err := t.DynamoDBAPI.GetItemWithContext(ctx, &dynamodb.GetItemInput{ + Key: key, + TableName: aws.String(t.name()), + ConsistentRead: aws.Bool(true), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeResourceNotFoundException: + return nil, fmt.Errorf("table or index not found: %s", t.name()) + } + } + return nil, err + } + + if len(res.Item) == 0 { + return nil, db.ErrThingWithDatetimeGSINotFound{ + ID: id, + } + } + + var m models.ThingWithDatetimeGSI + if err := decodeThingWithDatetimeGSI(res.Item, &m); err != nil { + return nil, err + } + + return &m, nil +} + +func (t ThingWithDatetimeGSITable) scanThingWithDatetimeGSIs(ctx context.Context, input db.ScanThingWithDatetimeGSIsInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + scanInput := &dynamodb.ScanInput{ + TableName: aws.String(t.name()), + ConsistentRead: aws.Bool(!input.DisableConsistentRead), + Limit: input.Limit, + } + if input.StartingAfter != nil { + exclusiveStartKey, err := dynamodbattribute.MarshalMap(input.StartingAfter) + if err != nil { + return fmt.Errorf("error encoding exclusive start key for scan: %s", err.Error()) + } + // must provide only the fields constituting the index + scanInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "id": exclusiveStartKey["id"], + } + } + totalRecordsProcessed := int64(0) + var innerErr error + err := t.DynamoDBAPI.ScanPagesWithContext(ctx, scanInput, func(out *dynamodb.ScanOutput, lastPage bool) bool { + items, err := decodeThingWithDatetimeGSIs(out.Items) + if err != nil { + innerErr = fmt.Errorf("error decoding %s", err.Error()) + return false + } + for i := range items { + if input.Limiter != nil { + if err := input.Limiter.Wait(ctx); err != nil { + innerErr = err + return false + } + } + isLastModel := lastPage && i == len(items)-1 + if shouldContinue := fn(&items[i], isLastModel); !shouldContinue { + return false + } + totalRecordsProcessed++ + // if the Limit of records have been passed to fn, don't pass anymore records. + if input.Limit != nil && totalRecordsProcessed == *input.Limit { + return false + } + } + return true + }) + if innerErr != nil { + return innerErr + } + return err +} + +func (t ThingWithDatetimeGSITable) deleteThingWithDatetimeGSI(ctx context.Context, id string) error { + key, err := dynamodbattribute.MarshalMap(ddbThingWithDatetimeGSIPrimaryKey{ + ID: id, + }) + if err != nil { + return err + } + _, err = t.DynamoDBAPI.DeleteItemWithContext(ctx, &dynamodb.DeleteItemInput{ + Key: key, + TableName: aws.String(t.name()), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeResourceNotFoundException: + return fmt.Errorf("table or index not found: %s", t.name()) + } + } + return err + } + + return nil +} + +func (t ThingWithDatetimeGSITable) getThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + if input.IDStartingAt != nil && input.StartingAfter != nil { + return fmt.Errorf("Can specify only one of input.IDStartingAt or input.StartingAfter") + } + if toDynamoTimeString(input.Datetime) == "" { + return fmt.Errorf("Hash key input.Datetime cannot be empty") + } + queryInput := &dynamodb.QueryInput{ + TableName: aws.String(t.name()), + IndexName: aws.String("byDate"), + ExpressionAttributeNames: map[string]*string{ + "#DATETIME": aws.String("datetime"), + }, + ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ + ":datetime": &dynamodb.AttributeValue{ + S: aws.String(toDynamoTimeString(input.Datetime)), + }, + }, + ScanIndexForward: aws.Bool(!input.Descending), + ConsistentRead: aws.Bool(false), + } + if input.Limit != nil { + queryInput.Limit = input.Limit + } + if input.IDStartingAt == nil { + queryInput.KeyConditionExpression = aws.String("#DATETIME = :datetime") + } else { + queryInput.ExpressionAttributeNames["#ID"] = aws.String("id") + queryInput.ExpressionAttributeValues[":id"] = &dynamodb.AttributeValue{ + S: aws.String(*input.IDStartingAt), + } + if input.Descending { + queryInput.KeyConditionExpression = aws.String("#DATETIME = :datetime AND #ID <= :id") + } else { + queryInput.KeyConditionExpression = aws.String("#DATETIME = :datetime AND #ID >= :id") + } + } + if input.StartingAfter != nil { + queryInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "id": &dynamodb.AttributeValue{ + S: aws.String(input.StartingAfter.ID), + }, + "datetime": &dynamodb.AttributeValue{ + S: aws.String(toDynamoTimeString(input.StartingAfter.Datetime)), + }, + } + } + + totalRecordsProcessed := int64(0) + var pageFnErr error + pageFn := func(queryOutput *dynamodb.QueryOutput, lastPage bool) bool { + if len(queryOutput.Items) == 0 { + return false + } + items, err := decodeThingWithDatetimeGSIs(queryOutput.Items) + if err != nil { + pageFnErr = err + return false + } + hasMore := true + for i := range items { + if lastPage == true { + hasMore = i < len(items)-1 + } + if !fn(&items[i], !hasMore) { + return false + } + totalRecordsProcessed++ + // if the Limit of records have been passed to fn, don't pass anymore records. + if input.Limit != nil && totalRecordsProcessed == *input.Limit { + return false + } + } + return true + } + + err := t.DynamoDBAPI.QueryPagesWithContext(ctx, queryInput, pageFn) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeResourceNotFoundException: + return fmt.Errorf("table or index not found: %s", t.name()) + } + } + return err + } + if pageFnErr != nil { + return pageFnErr + } + + return nil +} +func (t ThingWithDatetimeGSITable) scanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + scanInput := &dynamodb.ScanInput{ + TableName: aws.String(t.name()), + ConsistentRead: aws.Bool(!input.DisableConsistentRead), + Limit: input.Limit, + IndexName: aws.String("byDate"), + } + if input.StartingAfter != nil { + exclusiveStartKey, err := dynamodbattribute.MarshalMap(input.StartingAfter) + if err != nil { + return fmt.Errorf("error encoding exclusive start key for scan: %s", err.Error()) + } + // must provide the fields constituting the index and the primary key + // https://stackoverflow.com/questions/40988397/dynamodb-pagination-with-withexclusivestartkey-on-a-global-secondary-index + scanInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "id": exclusiveStartKey["id"], + "datetime": exclusiveStartKey["datetime"], + } + } + totalRecordsProcessed := int64(0) + var innerErr error + err := t.DynamoDBAPI.ScanPagesWithContext(ctx, scanInput, func(out *dynamodb.ScanOutput, lastPage bool) bool { + items, err := decodeThingWithDatetimeGSIs(out.Items) + if err != nil { + innerErr = fmt.Errorf("error decoding %s", err.Error()) + return false + } + for i := range items { + if input.Limiter != nil { + if err := input.Limiter.Wait(ctx); err != nil { + innerErr = err + return false + } + } + isLastModel := lastPage && i == len(items)-1 + if shouldContinue := fn(&items[i], isLastModel); !shouldContinue { + return false + } + totalRecordsProcessed++ + // if the Limit of records have been passed to fn, don't pass anymore records. + if input.Limit != nil && totalRecordsProcessed == *input.Limit { + return false + } + } + return true + }) + if innerErr != nil { + return innerErr + } + return err +} + +// encodeThingWithDatetimeGSI encodes a ThingWithDatetimeGSI as a DynamoDB map of attribute values. +func encodeThingWithDatetimeGSI(m models.ThingWithDatetimeGSI) (map[string]*dynamodb.AttributeValue, error) { + return dynamodbattribute.MarshalMap(ddbThingWithDatetimeGSI{ + ThingWithDatetimeGSI: m, + }) +} + +// decodeThingWithDatetimeGSI translates a ThingWithDatetimeGSI stored in DynamoDB to a ThingWithDatetimeGSI struct. +func decodeThingWithDatetimeGSI(m map[string]*dynamodb.AttributeValue, out *models.ThingWithDatetimeGSI) error { + var ddbThingWithDatetimeGSI ddbThingWithDatetimeGSI + if err := dynamodbattribute.UnmarshalMap(m, &ddbThingWithDatetimeGSI); err != nil { + return err + } + *out = ddbThingWithDatetimeGSI.ThingWithDatetimeGSI + return nil +} + +// decodeThingWithDatetimeGSIs translates a list of ThingWithDatetimeGSIs stored in DynamoDB to a slice of ThingWithDatetimeGSI structs. +func decodeThingWithDatetimeGSIs(ms []map[string]*dynamodb.AttributeValue) ([]models.ThingWithDatetimeGSI, error) { + thingWithDatetimeGSIs := make([]models.ThingWithDatetimeGSI, len(ms)) + for i, m := range ms { + var thingWithDatetimeGSI models.ThingWithDatetimeGSI + if err := decodeThingWithDatetimeGSI(m, &thingWithDatetimeGSI); err != nil { + return nil, err + } + thingWithDatetimeGSIs[i] = thingWithDatetimeGSI + } + return thingWithDatetimeGSIs, nil +} diff --git a/samples/gen-go-db-custom-path/db/interface.go b/samples/gen-go-db-custom-path/db/interface.go index ff9af828..cdbff2b3 100644 --- a/samples/gen-go-db-custom-path/db/interface.go +++ b/samples/gen-go-db-custom-path/db/interface.go @@ -214,6 +214,19 @@ type Interface interface { // DeleteThingWithDateTimeComposite deletes a ThingWithDateTimeComposite from the database. DeleteThingWithDateTimeComposite(ctx context.Context, typeVar string, id string, created strfmt.DateTime, resource string) error + // SaveThingWithDatetimeGSI saves a ThingWithDatetimeGSI to the database. + SaveThingWithDatetimeGSI(ctx context.Context, m models.ThingWithDatetimeGSI) error + // GetThingWithDatetimeGSI retrieves a ThingWithDatetimeGSI from the database. + GetThingWithDatetimeGSI(ctx context.Context, id string) (*models.ThingWithDatetimeGSI, error) + // ScanThingWithDatetimeGSIs runs a scan on the ThingWithDatetimeGSIs table. + ScanThingWithDatetimeGSIs(ctx context.Context, input ScanThingWithDatetimeGSIsInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error + // DeleteThingWithDatetimeGSI deletes a ThingWithDatetimeGSI from the database. + DeleteThingWithDatetimeGSI(ctx context.Context, id string) error + // GetThingWithDatetimeGSIsByDatetimeAndID retrieves a page of ThingWithDatetimeGSIs from the database. + GetThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error + // ScanThingWithDatetimeGSIsByDatetimeAndID runs a scan on the DatetimeAndID index. + ScanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error + // SaveThingWithEnumHashKey saves a ThingWithEnumHashKey to the database. SaveThingWithEnumHashKey(ctx context.Context, m models.ThingWithEnumHashKey) error // GetThingWithEnumHashKey retrieves a ThingWithEnumHashKey from the database. @@ -1577,6 +1590,78 @@ type CreatedResource struct { Resource string } +// ScanThingWithDatetimeGSIsInput is the input to the ScanThingWithDatetimeGSIs method. +type ScanThingWithDatetimeGSIsInput struct { + // StartingAfter is an optional specification of an (exclusive) starting point. + StartingAfter *models.ThingWithDatetimeGSI + // DisableConsistentRead turns off the default behavior of running a consistent read. + DisableConsistentRead bool + // Limit is an optional limit of how many items to evaluate. + Limit *int64 + // Limiter is an optional limit on how quickly items are scanned. + Limiter *rate.Limiter +} + +// ErrThingWithDatetimeGSINotFound is returned when the database fails to find a ThingWithDatetimeGSI. +type ErrThingWithDatetimeGSINotFound struct { + ID string +} + +var _ error = ErrThingWithDatetimeGSINotFound{} + +// Error returns a description of the error. +func (e ErrThingWithDatetimeGSINotFound) Error() string { + return "could not find ThingWithDatetimeGSI" +} + +// GetThingWithDatetimeGSIsByDatetimeAndIDInput is the query input to GetThingWithDatetimeGSIsByDatetimeAndID. +type GetThingWithDatetimeGSIsByDatetimeAndIDInput struct { + // Datetime is required + Datetime strfmt.DateTime + IDStartingAt *string + StartingAfter *models.ThingWithDatetimeGSI + Descending bool + // Limit is an optional limit of how many items to evaluate. + Limit *int64 +} + +// ErrThingWithDatetimeGSIByDatetimeAndIDNotFound is returned when the database fails to find a ThingWithDatetimeGSI. +type ErrThingWithDatetimeGSIByDatetimeAndIDNotFound struct { + Datetime strfmt.DateTime + ID string +} + +var _ error = ErrThingWithDatetimeGSIByDatetimeAndIDNotFound{} + +// Error returns a description of the error. +func (e ErrThingWithDatetimeGSIByDatetimeAndIDNotFound) Error() string { + return "could not find ThingWithDatetimeGSI" +} + +// ScanThingWithDatetimeGSIsByDatetimeAndIDInput is the input to the ScanThingWithDatetimeGSIsByDatetimeAndID method. +type ScanThingWithDatetimeGSIsByDatetimeAndIDInput struct { + // StartingAfter is an optional specification of an (exclusive) starting point. + StartingAfter *models.ThingWithDatetimeGSI + // DisableConsistentRead turns off the default behavior of running a consistent read. + DisableConsistentRead bool + // Limit is an optional limit of how many items to evaluate. + Limit *int64 + // Limiter is an optional limit on how quickly items are scanned. + Limiter *rate.Limiter +} + +// ErrThingWithDatetimeGSIAlreadyExists is returned when trying to overwrite a ThingWithDatetimeGSI. +type ErrThingWithDatetimeGSIAlreadyExists struct { + ID string +} + +var _ error = ErrThingWithDatetimeGSIAlreadyExists{} + +// Error returns a description of the error. +func (e ErrThingWithDatetimeGSIAlreadyExists) Error() string { + return "ThingWithDatetimeGSI already exists" +} + // ScanThingWithEnumHashKeysInput is the input to the ScanThingWithEnumHashKeys method. type ScanThingWithEnumHashKeysInput struct { // StartingAfter is an optional specification of an (exclusive) starting point. diff --git a/samples/gen-go-db-custom-path/db/mock_db.go b/samples/gen-go-db-custom-path/db/mock_db.go index 56cd3827..3a033110 100644 --- a/samples/gen-go-db-custom-path/db/mock_db.go +++ b/samples/gen-go-db-custom-path/db/mock_db.go @@ -247,6 +247,20 @@ func (mr *MockInterfaceMockRecorder) DeleteThingWithDateTimeComposite(ctx, typeV return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteThingWithDateTimeComposite", reflect.TypeOf((*MockInterface)(nil).DeleteThingWithDateTimeComposite), ctx, typeVar, id, created, resource) } +// DeleteThingWithDatetimeGSI mocks base method. +func (m *MockInterface) DeleteThingWithDatetimeGSI(ctx context.Context, id string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteThingWithDatetimeGSI", ctx, id) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteThingWithDatetimeGSI indicates an expected call of DeleteThingWithDatetimeGSI. +func (mr *MockInterfaceMockRecorder) DeleteThingWithDatetimeGSI(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteThingWithDatetimeGSI", reflect.TypeOf((*MockInterface)(nil).DeleteThingWithDatetimeGSI), ctx, id) +} + // DeleteThingWithEnumHashKey mocks base method. func (m *MockInterface) DeleteThingWithEnumHashKey(ctx context.Context, branch v9.Branch, date strfmt.DateTime) error { m.ctrl.T.Helper() @@ -879,6 +893,35 @@ func (mr *MockInterfaceMockRecorder) GetThingWithDateTimeCompositesByTypeIDAndCr return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThingWithDateTimeCompositesByTypeIDAndCreatedResource", reflect.TypeOf((*MockInterface)(nil).GetThingWithDateTimeCompositesByTypeIDAndCreatedResource), ctx, input, fn) } +// GetThingWithDatetimeGSI mocks base method. +func (m *MockInterface) GetThingWithDatetimeGSI(ctx context.Context, id string) (*v9.ThingWithDatetimeGSI, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetThingWithDatetimeGSI", ctx, id) + ret0, _ := ret[0].(*v9.ThingWithDatetimeGSI) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetThingWithDatetimeGSI indicates an expected call of GetThingWithDatetimeGSI. +func (mr *MockInterfaceMockRecorder) GetThingWithDatetimeGSI(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThingWithDatetimeGSI", reflect.TypeOf((*MockInterface)(nil).GetThingWithDatetimeGSI), ctx, id) +} + +// GetThingWithDatetimeGSIsByDatetimeAndID mocks base method. +func (m *MockInterface) GetThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(*v9.ThingWithDatetimeGSI, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetThingWithDatetimeGSIsByDatetimeAndID", ctx, input, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetThingWithDatetimeGSIsByDatetimeAndID indicates an expected call of GetThingWithDatetimeGSIsByDatetimeAndID. +func (mr *MockInterfaceMockRecorder) GetThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThingWithDatetimeGSIsByDatetimeAndID", reflect.TypeOf((*MockInterface)(nil).GetThingWithDatetimeGSIsByDatetimeAndID), ctx, input, fn) +} + // GetThingWithEnumHashKey mocks base method. func (m *MockInterface) GetThingWithEnumHashKey(ctx context.Context, branch v9.Branch, date strfmt.DateTime) (*v9.ThingWithEnumHashKey, error) { m.ctrl.T.Helper() @@ -1392,6 +1435,20 @@ func (mr *MockInterfaceMockRecorder) SaveThingWithDateTimeComposite(ctx, m inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveThingWithDateTimeComposite", reflect.TypeOf((*MockInterface)(nil).SaveThingWithDateTimeComposite), ctx, m) } +// SaveThingWithDatetimeGSI mocks base method. +func (m_2 *MockInterface) SaveThingWithDatetimeGSI(ctx context.Context, m v9.ThingWithDatetimeGSI) error { + m_2.ctrl.T.Helper() + ret := m_2.ctrl.Call(m_2, "SaveThingWithDatetimeGSI", ctx, m) + ret0, _ := ret[0].(error) + return ret0 +} + +// SaveThingWithDatetimeGSI indicates an expected call of SaveThingWithDatetimeGSI. +func (mr *MockInterfaceMockRecorder) SaveThingWithDatetimeGSI(ctx, m interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveThingWithDatetimeGSI", reflect.TypeOf((*MockInterface)(nil).SaveThingWithDatetimeGSI), ctx, m) +} + // SaveThingWithEnumHashKey mocks base method. func (m_2 *MockInterface) SaveThingWithEnumHashKey(ctx context.Context, m v9.ThingWithEnumHashKey) error { m_2.ctrl.T.Helper() @@ -1812,6 +1869,34 @@ func (mr *MockInterfaceMockRecorder) ScanThingWithDateTimeComposites(ctx, input, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanThingWithDateTimeComposites", reflect.TypeOf((*MockInterface)(nil).ScanThingWithDateTimeComposites), ctx, input, fn) } +// ScanThingWithDatetimeGSIs mocks base method. +func (m *MockInterface) ScanThingWithDatetimeGSIs(ctx context.Context, input ScanThingWithDatetimeGSIsInput, fn func(*v9.ThingWithDatetimeGSI, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ScanThingWithDatetimeGSIs", ctx, input, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// ScanThingWithDatetimeGSIs indicates an expected call of ScanThingWithDatetimeGSIs. +func (mr *MockInterfaceMockRecorder) ScanThingWithDatetimeGSIs(ctx, input, fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanThingWithDatetimeGSIs", reflect.TypeOf((*MockInterface)(nil).ScanThingWithDatetimeGSIs), ctx, input, fn) +} + +// ScanThingWithDatetimeGSIsByDatetimeAndID mocks base method. +func (m *MockInterface) ScanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(*v9.ThingWithDatetimeGSI, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ScanThingWithDatetimeGSIsByDatetimeAndID", ctx, input, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// ScanThingWithDatetimeGSIsByDatetimeAndID indicates an expected call of ScanThingWithDatetimeGSIsByDatetimeAndID. +func (mr *MockInterfaceMockRecorder) ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanThingWithDatetimeGSIsByDatetimeAndID", reflect.TypeOf((*MockInterface)(nil).ScanThingWithDatetimeGSIsByDatetimeAndID), ctx, input, fn) +} + // ScanThingWithEnumHashKeys mocks base method. func (m *MockInterface) ScanThingWithEnumHashKeys(ctx context.Context, input ScanThingWithEnumHashKeysInput, fn func(*v9.ThingWithEnumHashKey, bool) bool) error { m.ctrl.T.Helper() diff --git a/samples/gen-go-db-custom-path/db/tests/tests.go b/samples/gen-go-db-custom-path/db/tests/tests.go index 27d3659c..0c56e35b 100644 --- a/samples/gen-go-db-custom-path/db/tests/tests.go +++ b/samples/gen-go-db-custom-path/db/tests/tests.go @@ -115,6 +115,12 @@ func RunDBTests(t *testing.T, dbFactory func() db.Interface) { t.Run("GetThingWithDateTimeCompositesByTypeIDAndCreatedResource", GetThingWithDateTimeCompositesByTypeIDAndCreatedResource(dbFactory(), t)) t.Run("SaveThingWithDateTimeComposite", SaveThingWithDateTimeComposite(dbFactory(), t)) t.Run("DeleteThingWithDateTimeComposite", DeleteThingWithDateTimeComposite(dbFactory(), t)) + t.Run("GetThingWithDatetimeGSI", GetThingWithDatetimeGSI(dbFactory(), t)) + t.Run("ScanThingWithDatetimeGSIs", ScanThingWithDatetimeGSIs(dbFactory(), t)) + t.Run("SaveThingWithDatetimeGSI", SaveThingWithDatetimeGSI(dbFactory(), t)) + t.Run("DeleteThingWithDatetimeGSI", DeleteThingWithDatetimeGSI(dbFactory(), t)) + t.Run("GetThingWithDatetimeGSIsByDatetimeAndID", GetThingWithDatetimeGSIsByDatetimeAndID(dbFactory(), t)) + t.Run("ScanThingWithDatetimeGSIsByDatetimeAndID", ScanThingWithDatetimeGSIsByDatetimeAndID(dbFactory(), t)) t.Run("GetThingWithEnumHashKey", GetThingWithEnumHashKey(dbFactory(), t)) t.Run("ScanThingWithEnumHashKeys", ScanThingWithEnumHashKeys(dbFactory(), t)) t.Run("GetThingWithEnumHashKeysByBranchAndDate", GetThingWithEnumHashKeysByBranchAndDate(dbFactory(), t)) @@ -8774,6 +8780,453 @@ func DeleteThingWithDateTimeComposite(s db.Interface, t *testing.T) func(t *test } } +func GetThingWithDatetimeGSI(s db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + m := models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + } + require.Nil(t, s.SaveThingWithDatetimeGSI(ctx, m)) + m2, err := s.GetThingWithDatetimeGSI(ctx, m.ID) + require.Nil(t, err) + require.Equal(t, m.ID, m2.ID) + + _, err = s.GetThingWithDatetimeGSI(ctx, "string2") + require.NotNil(t, err) + require.IsType(t, err, db.ErrThingWithDatetimeGSINotFound{}) + } +} + +// The scan tests are structured differently compared to other tests in because items returned by scans +// are not returned in any particular order, so we can't simply declare what the expected arrays of items are. +func ScanThingWithDatetimeGSIs(d db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + })) + + t.Run("basic", func(t *testing.T) { + expected := []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + }, + } + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, db.ScanThingWithDatetimeGSIsInput{}, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + // We can't use Equal here because Scan doesn't return items in any specific order. + require.ElementsMatch(t, expected, actual) + }) + + t.Run("starting after", func(t *testing.T) { + // Scan for everything. + allItems := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, db.ScanThingWithDatetimeGSIsInput{}, func(m *models.ThingWithDatetimeGSI, last bool) bool { + allItems = append(allItems, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + firstItem := allItems[0] + + // Scan for everything after the first item. + scanInput := db.ScanThingWithDatetimeGSIsInput{ + StartingAfter: &models.ThingWithDatetimeGSI{ + ID: firstItem.ID, + }, + } + actual := []models.ThingWithDatetimeGSI{} + err = d.ScanThingWithDatetimeGSIs(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + expected := allItems[1:] + require.Equal(t, expected, actual) + }) + + t.Run("limit", func(t *testing.T) { + limit := int64(1) + // Scan for just the first item. + scanInput := db.ScanThingWithDatetimeGSIsInput{ + Limit: &limit, + } + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + require.Len(t, actual, 1) + }) + } +} + +func SaveThingWithDatetimeGSI(s db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + m := models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + } + require.Nil(t, s.SaveThingWithDatetimeGSI(ctx, m)) + require.IsType(t, db.ErrThingWithDatetimeGSIAlreadyExists{}, s.SaveThingWithDatetimeGSI(ctx, m)) + } +} + +func DeleteThingWithDatetimeGSI(s db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + m := models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + } + require.Nil(t, s.SaveThingWithDatetimeGSI(ctx, m)) + require.Nil(t, s.DeleteThingWithDatetimeGSI(ctx, m.ID)) + } +} + +type getThingWithDatetimeGSIsByDatetimeAndIDInput struct { + ctx context.Context + input db.GetThingWithDatetimeGSIsByDatetimeAndIDInput +} +type getThingWithDatetimeGSIsByDatetimeAndIDOutput struct { + thingWithDatetimeGSIs []models.ThingWithDatetimeGSI + err error +} +type getThingWithDatetimeGSIsByDatetimeAndIDTest struct { + testName string + d db.Interface + input getThingWithDatetimeGSIsByDatetimeAndIDInput + output getThingWithDatetimeGSIsByDatetimeAndIDOutput +} + +func (g getThingWithDatetimeGSIsByDatetimeAndIDTest) run(t *testing.T) { + thingWithDatetimeGSIs := []models.ThingWithDatetimeGSI{} + fn := func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool { + thingWithDatetimeGSIs = append(thingWithDatetimeGSIs, *m) + if lastThingWithDatetimeGSI { + return false + } + return true + } + err := g.d.GetThingWithDatetimeGSIsByDatetimeAndID(g.input.ctx, g.input.input, fn) + if err != nil { + fmt.Println(err.Error()) + } + require.Equal(t, g.output.err, err) + require.Equal(t, g.output.thingWithDatetimeGSIs, thingWithDatetimeGSIs) +} + +func GetThingWithDatetimeGSIsByDatetimeAndID(d db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + })) + limit := int64(3) + tests := []getThingWithDatetimeGSIsByDatetimeAndIDTest{ + { + testName: "basic", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + Limit: &limit, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + }, + err: nil, + }, + }, + { + testName: "descending", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + Descending: true, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + }, + err: nil, + }, + }, + { + testName: "starting after", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + StartingAfter: &models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + }, + err: nil, + }, + }, + { + testName: "starting after descending", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + StartingAfter: &models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + Descending: true, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + }, + err: nil, + }, + }, + { + testName: "starting at", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + IDStartingAt: db.String("string2"), + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + }, + err: nil, + }, + }, + } + for _, test := range tests { + t.Run(test.testName, test.run) + } + } +} + +// The scan tests are structured differently compared to other tests in because items returned by scans +// are not returned in any particular order, so we can't simply declare what the expected arrays of items are. +func ScanThingWithDatetimeGSIsByDatetimeAndID(d db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + })) + + t.Run("basic", func(t *testing.T) { + expected := []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + }, + } + // Consistent read must be disabled when scaning a GSI. + scanInput := db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput{DisableConsistentRead: true} + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + // We can't use Equal here because Scan doesn't return items in any specific order. + require.ElementsMatch(t, expected, actual) + }) + + t.Run("starting after", func(t *testing.T) { + // Scan for everything. + allItems := []models.ThingWithDatetimeGSI{} + // Consistent read must be disabled when scaning a GSI. + scanInput := db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput{DisableConsistentRead: true} + err := d.ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + allItems = append(allItems, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + firstItem := allItems[0] + + // Scan for everything after the first item. + scanInput = db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput{ + DisableConsistentRead: true, + StartingAfter: &models.ThingWithDatetimeGSI{ + Datetime: firstItem.Datetime, + ID: firstItem.ID, + }, + } + actual := []models.ThingWithDatetimeGSI{} + err = d.ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + expected := allItems[1:] + require.Equal(t, expected, actual) + }) + + t.Run("limit", func(t *testing.T) { + limit := int64(1) + // Scan for just the first item. + scanInput := db.ScanThingWithDatetimeGSIsInput{ + Limit: &limit, + } + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + require.Len(t, actual, 1) + }) + } +} + func GetThingWithEnumHashKey(s db.Interface, t *testing.T) func(t *testing.T) { return func(t *testing.T) { ctx := context.Background() diff --git a/samples/gen-go-db-custom-path/models/thing_with_datetime_g_s_i.go b/samples/gen-go-db-custom-path/models/thing_with_datetime_g_s_i.go new file mode 100644 index 00000000..2c3ce255 --- /dev/null +++ b/samples/gen-go-db-custom-path/models/thing_with_datetime_g_s_i.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ThingWithDatetimeGSI thing with datetime g s i +// +// swagger:model ThingWithDatetimeGSI +type ThingWithDatetimeGSI struct { + + // datetime + // Format: date-time + Datetime strfmt.DateTime `json:"datetime,omitempty"` + + // id + ID string `json:"id,omitempty"` +} + +// Validate validates this thing with datetime g s i +func (m *ThingWithDatetimeGSI) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateDatetime(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ThingWithDatetimeGSI) validateDatetime(formats strfmt.Registry) error { + + if swag.IsZero(m.Datetime) { // not required + return nil + } + + if err := validate.FormatOf("datetime", "body", "date-time", m.Datetime.String(), formats); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ThingWithDatetimeGSI) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ThingWithDatetimeGSI) UnmarshalBinary(b []byte) error { + var res ThingWithDatetimeGSI + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/samples/gen-go-db-only/db/dynamodb/dynamodb.go b/samples/gen-go-db-only/db/dynamodb/dynamodb.go index 01294533..88328c6a 100644 --- a/samples/gen-go-db-only/db/dynamodb/dynamodb.go +++ b/samples/gen-go-db-only/db/dynamodb/dynamodb.go @@ -56,6 +56,8 @@ type Config struct { ThingWithDateRangeTable ThingWithDateRangeTable // ThingWithDateTimeCompositeTable configuration. ThingWithDateTimeCompositeTable ThingWithDateTimeCompositeTable + // ThingWithDatetimeGSITable configuration. + ThingWithDatetimeGSITable ThingWithDatetimeGSITable // ThingWithEnumHashKeyTable configuration. ThingWithEnumHashKeyTable ThingWithEnumHashKeyTable // ThingWithMatchingKeysTable configuration. @@ -277,6 +279,20 @@ func New(config Config) (*DB, error) { if thingWithDateTimeCompositeTable.WriteCapacityUnits == 0 { thingWithDateTimeCompositeTable.WriteCapacityUnits = config.DefaultWriteCapacityUnits } + // configure ThingWithDatetimeGSI table + thingWithDatetimeGSITable := config.ThingWithDatetimeGSITable + if thingWithDatetimeGSITable.DynamoDBAPI == nil { + thingWithDatetimeGSITable.DynamoDBAPI = config.DynamoDBAPI + } + if thingWithDatetimeGSITable.Prefix == "" { + thingWithDatetimeGSITable.Prefix = config.DefaultPrefix + } + if thingWithDatetimeGSITable.ReadCapacityUnits == 0 { + thingWithDatetimeGSITable.ReadCapacityUnits = config.DefaultReadCapacityUnits + } + if thingWithDatetimeGSITable.WriteCapacityUnits == 0 { + thingWithDatetimeGSITable.WriteCapacityUnits = config.DefaultWriteCapacityUnits + } // configure ThingWithEnumHashKey table thingWithEnumHashKeyTable := config.ThingWithEnumHashKeyTable if thingWithEnumHashKeyTable.DynamoDBAPI == nil { @@ -418,6 +434,7 @@ func New(config Config) (*DB, error) { thingWithCompositeEnumAttributesTable: thingWithCompositeEnumAttributesTable, thingWithDateRangeTable: thingWithDateRangeTable, thingWithDateTimeCompositeTable: thingWithDateTimeCompositeTable, + thingWithDatetimeGSITable: thingWithDatetimeGSITable, thingWithEnumHashKeyTable: thingWithEnumHashKeyTable, thingWithMatchingKeysTable: thingWithMatchingKeysTable, thingWithMultiUseCompositeAttributeTable: thingWithMultiUseCompositeAttributeTable, @@ -445,6 +462,7 @@ type DB struct { thingWithCompositeEnumAttributesTable ThingWithCompositeEnumAttributesTable thingWithDateRangeTable ThingWithDateRangeTable thingWithDateTimeCompositeTable ThingWithDateTimeCompositeTable + thingWithDatetimeGSITable ThingWithDatetimeGSITable thingWithEnumHashKeyTable ThingWithEnumHashKeyTable thingWithMatchingKeysTable ThingWithMatchingKeysTable thingWithMultiUseCompositeAttributeTable ThingWithMultiUseCompositeAttributeTable @@ -499,6 +517,9 @@ func (d DB) CreateTables(ctx context.Context) error { if err := d.thingWithDateTimeCompositeTable.create(ctx); err != nil { return err } + if err := d.thingWithDatetimeGSITable.create(ctx); err != nil { + return err + } if err := d.thingWithEnumHashKeyTable.create(ctx); err != nil { return err } @@ -999,6 +1020,36 @@ func (d DB) DeleteThingWithDateTimeComposite(ctx context.Context, typeVar string return d.thingWithDateTimeCompositeTable.deleteThingWithDateTimeComposite(ctx, typeVar, id, created, resource) } +// SaveThingWithDatetimeGSI saves a ThingWithDatetimeGSI to the database. +func (d DB) SaveThingWithDatetimeGSI(ctx context.Context, m models.ThingWithDatetimeGSI) error { + return d.thingWithDatetimeGSITable.saveThingWithDatetimeGSI(ctx, m) +} + +// GetThingWithDatetimeGSI retrieves a ThingWithDatetimeGSI from the database. +func (d DB) GetThingWithDatetimeGSI(ctx context.Context, id string) (*models.ThingWithDatetimeGSI, error) { + return d.thingWithDatetimeGSITable.getThingWithDatetimeGSI(ctx, id) +} + +// ScanThingWithDatetimeGSIs runs a scan on the ThingWithDatetimeGSIs table. +func (d DB) ScanThingWithDatetimeGSIs(ctx context.Context, input db.ScanThingWithDatetimeGSIsInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + return d.thingWithDatetimeGSITable.scanThingWithDatetimeGSIs(ctx, input, fn) +} + +// DeleteThingWithDatetimeGSI deletes a ThingWithDatetimeGSI from the database. +func (d DB) DeleteThingWithDatetimeGSI(ctx context.Context, id string) error { + return d.thingWithDatetimeGSITable.deleteThingWithDatetimeGSI(ctx, id) +} + +// GetThingWithDatetimeGSIsByDatetimeAndID retrieves a page of ThingWithDatetimeGSIs from the database. +func (d DB) GetThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + return d.thingWithDatetimeGSITable.getThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn) +} + +// ScanThingWithDatetimeGSIsByDatetimeAndID runs a scan on the DatetimeAndID index. +func (d DB) ScanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + return d.thingWithDatetimeGSITable.scanThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn) +} + // SaveThingWithEnumHashKey saves a ThingWithEnumHashKey to the database. func (d DB) SaveThingWithEnumHashKey(ctx context.Context, m models.ThingWithEnumHashKey) error { return d.thingWithEnumHashKeyTable.saveThingWithEnumHashKey(ctx, m) diff --git a/samples/gen-go-db-only/db/dynamodb/thingwithdatetimegsi.go b/samples/gen-go-db-only/db/dynamodb/thingwithdatetimegsi.go new file mode 100644 index 00000000..3851be90 --- /dev/null +++ b/samples/gen-go-db-only/db/dynamodb/thingwithdatetimegsi.go @@ -0,0 +1,412 @@ +package dynamodb + +import ( + "context" + "fmt" + + "github.com/Clever/wag/samples/gen-go-db-only/models/v9" + "github.com/Clever/wag/samples/v9/gen-go-db-only/db" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface" + "github.com/go-openapi/strfmt" +) + +var _ = strfmt.DateTime{} + +// ThingWithDatetimeGSITable represents the user-configurable properties of the ThingWithDatetimeGSI table. +type ThingWithDatetimeGSITable struct { + DynamoDBAPI dynamodbiface.DynamoDBAPI + Prefix string + TableName string + ReadCapacityUnits int64 + WriteCapacityUnits int64 +} + +// ddbThingWithDatetimeGSIPrimaryKey represents the primary key of a ThingWithDatetimeGSI in DynamoDB. +type ddbThingWithDatetimeGSIPrimaryKey struct { + ID string `dynamodbav:"id"` +} + +// ddbThingWithDatetimeGSIGSIByDate represents the byDate GSI. +type ddbThingWithDatetimeGSIGSIByDate struct { + Datetime strfmt.DateTime `dynamodbav:"datetime"` + ID string `dynamodbav:"id"` +} + +// ddbThingWithDatetimeGSI represents a ThingWithDatetimeGSI as stored in DynamoDB. +type ddbThingWithDatetimeGSI struct { + models.ThingWithDatetimeGSI +} + +func (t ThingWithDatetimeGSITable) name() string { + if t.TableName != "" { + return t.TableName + } + return fmt.Sprintf("%s-thing-with-datetime-g-s-is", t.Prefix) +} + +func (t ThingWithDatetimeGSITable) create(ctx context.Context) error { + if _, err := t.DynamoDBAPI.CreateTableWithContext(ctx, &dynamodb.CreateTableInput{ + AttributeDefinitions: []*dynamodb.AttributeDefinition{ + { + AttributeName: aws.String("datetime"), + AttributeType: aws.String("S"), + }, + { + AttributeName: aws.String("id"), + AttributeType: aws.String("S"), + }, + }, + KeySchema: []*dynamodb.KeySchemaElement{ + { + AttributeName: aws.String("id"), + KeyType: aws.String(dynamodb.KeyTypeHash), + }, + }, + GlobalSecondaryIndexes: []*dynamodb.GlobalSecondaryIndex{ + { + IndexName: aws.String("byDate"), + Projection: &dynamodb.Projection{ + ProjectionType: aws.String("ALL"), + }, + KeySchema: []*dynamodb.KeySchemaElement{ + { + AttributeName: aws.String("datetime"), + KeyType: aws.String(dynamodb.KeyTypeHash), + }, + { + AttributeName: aws.String("id"), + KeyType: aws.String(dynamodb.KeyTypeRange), + }, + }, + ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ + ReadCapacityUnits: aws.Int64(t.ReadCapacityUnits), + WriteCapacityUnits: aws.Int64(t.WriteCapacityUnits), + }, + }, + }, + ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ + ReadCapacityUnits: aws.Int64(t.ReadCapacityUnits), + WriteCapacityUnits: aws.Int64(t.WriteCapacityUnits), + }, + TableName: aws.String(t.name()), + }); err != nil { + return err + } + return nil +} + +func (t ThingWithDatetimeGSITable) saveThingWithDatetimeGSI(ctx context.Context, m models.ThingWithDatetimeGSI) error { + data, err := encodeThingWithDatetimeGSI(m) + if err != nil { + return err + } + _, err = t.DynamoDBAPI.PutItemWithContext(ctx, &dynamodb.PutItemInput{ + TableName: aws.String(t.name()), + Item: data, + ExpressionAttributeNames: map[string]*string{ + "#ID": aws.String("id"), + }, + ConditionExpression: aws.String("attribute_not_exists(#ID)"), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeConditionalCheckFailedException: + return db.ErrThingWithDatetimeGSIAlreadyExists{ + ID: m.ID, + } + case dynamodb.ErrCodeResourceNotFoundException: + return fmt.Errorf("table or index not found: %s", t.name()) + } + } + return err + } + return nil +} + +func (t ThingWithDatetimeGSITable) getThingWithDatetimeGSI(ctx context.Context, id string) (*models.ThingWithDatetimeGSI, error) { + key, err := dynamodbattribute.MarshalMap(ddbThingWithDatetimeGSIPrimaryKey{ + ID: id, + }) + if err != nil { + return nil, err + } + res, err := t.DynamoDBAPI.GetItemWithContext(ctx, &dynamodb.GetItemInput{ + Key: key, + TableName: aws.String(t.name()), + ConsistentRead: aws.Bool(true), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeResourceNotFoundException: + return nil, fmt.Errorf("table or index not found: %s", t.name()) + } + } + return nil, err + } + + if len(res.Item) == 0 { + return nil, db.ErrThingWithDatetimeGSINotFound{ + ID: id, + } + } + + var m models.ThingWithDatetimeGSI + if err := decodeThingWithDatetimeGSI(res.Item, &m); err != nil { + return nil, err + } + + return &m, nil +} + +func (t ThingWithDatetimeGSITable) scanThingWithDatetimeGSIs(ctx context.Context, input db.ScanThingWithDatetimeGSIsInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + scanInput := &dynamodb.ScanInput{ + TableName: aws.String(t.name()), + ConsistentRead: aws.Bool(!input.DisableConsistentRead), + Limit: input.Limit, + } + if input.StartingAfter != nil { + exclusiveStartKey, err := dynamodbattribute.MarshalMap(input.StartingAfter) + if err != nil { + return fmt.Errorf("error encoding exclusive start key for scan: %s", err.Error()) + } + // must provide only the fields constituting the index + scanInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "id": exclusiveStartKey["id"], + } + } + totalRecordsProcessed := int64(0) + var innerErr error + err := t.DynamoDBAPI.ScanPagesWithContext(ctx, scanInput, func(out *dynamodb.ScanOutput, lastPage bool) bool { + items, err := decodeThingWithDatetimeGSIs(out.Items) + if err != nil { + innerErr = fmt.Errorf("error decoding %s", err.Error()) + return false + } + for i := range items { + if input.Limiter != nil { + if err := input.Limiter.Wait(ctx); err != nil { + innerErr = err + return false + } + } + isLastModel := lastPage && i == len(items)-1 + if shouldContinue := fn(&items[i], isLastModel); !shouldContinue { + return false + } + totalRecordsProcessed++ + // if the Limit of records have been passed to fn, don't pass anymore records. + if input.Limit != nil && totalRecordsProcessed == *input.Limit { + return false + } + } + return true + }) + if innerErr != nil { + return innerErr + } + return err +} + +func (t ThingWithDatetimeGSITable) deleteThingWithDatetimeGSI(ctx context.Context, id string) error { + key, err := dynamodbattribute.MarshalMap(ddbThingWithDatetimeGSIPrimaryKey{ + ID: id, + }) + if err != nil { + return err + } + _, err = t.DynamoDBAPI.DeleteItemWithContext(ctx, &dynamodb.DeleteItemInput{ + Key: key, + TableName: aws.String(t.name()), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeResourceNotFoundException: + return fmt.Errorf("table or index not found: %s", t.name()) + } + } + return err + } + + return nil +} + +func (t ThingWithDatetimeGSITable) getThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + if input.IDStartingAt != nil && input.StartingAfter != nil { + return fmt.Errorf("Can specify only one of input.IDStartingAt or input.StartingAfter") + } + if toDynamoTimeString(input.Datetime) == "" { + return fmt.Errorf("Hash key input.Datetime cannot be empty") + } + queryInput := &dynamodb.QueryInput{ + TableName: aws.String(t.name()), + IndexName: aws.String("byDate"), + ExpressionAttributeNames: map[string]*string{ + "#DATETIME": aws.String("datetime"), + }, + ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ + ":datetime": &dynamodb.AttributeValue{ + S: aws.String(toDynamoTimeString(input.Datetime)), + }, + }, + ScanIndexForward: aws.Bool(!input.Descending), + ConsistentRead: aws.Bool(false), + } + if input.Limit != nil { + queryInput.Limit = input.Limit + } + if input.IDStartingAt == nil { + queryInput.KeyConditionExpression = aws.String("#DATETIME = :datetime") + } else { + queryInput.ExpressionAttributeNames["#ID"] = aws.String("id") + queryInput.ExpressionAttributeValues[":id"] = &dynamodb.AttributeValue{ + S: aws.String(*input.IDStartingAt), + } + if input.Descending { + queryInput.KeyConditionExpression = aws.String("#DATETIME = :datetime AND #ID <= :id") + } else { + queryInput.KeyConditionExpression = aws.String("#DATETIME = :datetime AND #ID >= :id") + } + } + if input.StartingAfter != nil { + queryInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "id": &dynamodb.AttributeValue{ + S: aws.String(input.StartingAfter.ID), + }, + "datetime": &dynamodb.AttributeValue{ + S: aws.String(toDynamoTimeString(input.StartingAfter.Datetime)), + }, + } + } + + totalRecordsProcessed := int64(0) + var pageFnErr error + pageFn := func(queryOutput *dynamodb.QueryOutput, lastPage bool) bool { + if len(queryOutput.Items) == 0 { + return false + } + items, err := decodeThingWithDatetimeGSIs(queryOutput.Items) + if err != nil { + pageFnErr = err + return false + } + hasMore := true + for i := range items { + if lastPage == true { + hasMore = i < len(items)-1 + } + if !fn(&items[i], !hasMore) { + return false + } + totalRecordsProcessed++ + // if the Limit of records have been passed to fn, don't pass anymore records. + if input.Limit != nil && totalRecordsProcessed == *input.Limit { + return false + } + } + return true + } + + err := t.DynamoDBAPI.QueryPagesWithContext(ctx, queryInput, pageFn) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeResourceNotFoundException: + return fmt.Errorf("table or index not found: %s", t.name()) + } + } + return err + } + if pageFnErr != nil { + return pageFnErr + } + + return nil +} +func (t ThingWithDatetimeGSITable) scanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + scanInput := &dynamodb.ScanInput{ + TableName: aws.String(t.name()), + ConsistentRead: aws.Bool(!input.DisableConsistentRead), + Limit: input.Limit, + IndexName: aws.String("byDate"), + } + if input.StartingAfter != nil { + exclusiveStartKey, err := dynamodbattribute.MarshalMap(input.StartingAfter) + if err != nil { + return fmt.Errorf("error encoding exclusive start key for scan: %s", err.Error()) + } + // must provide the fields constituting the index and the primary key + // https://stackoverflow.com/questions/40988397/dynamodb-pagination-with-withexclusivestartkey-on-a-global-secondary-index + scanInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "id": exclusiveStartKey["id"], + "datetime": exclusiveStartKey["datetime"], + } + } + totalRecordsProcessed := int64(0) + var innerErr error + err := t.DynamoDBAPI.ScanPagesWithContext(ctx, scanInput, func(out *dynamodb.ScanOutput, lastPage bool) bool { + items, err := decodeThingWithDatetimeGSIs(out.Items) + if err != nil { + innerErr = fmt.Errorf("error decoding %s", err.Error()) + return false + } + for i := range items { + if input.Limiter != nil { + if err := input.Limiter.Wait(ctx); err != nil { + innerErr = err + return false + } + } + isLastModel := lastPage && i == len(items)-1 + if shouldContinue := fn(&items[i], isLastModel); !shouldContinue { + return false + } + totalRecordsProcessed++ + // if the Limit of records have been passed to fn, don't pass anymore records. + if input.Limit != nil && totalRecordsProcessed == *input.Limit { + return false + } + } + return true + }) + if innerErr != nil { + return innerErr + } + return err +} + +// encodeThingWithDatetimeGSI encodes a ThingWithDatetimeGSI as a DynamoDB map of attribute values. +func encodeThingWithDatetimeGSI(m models.ThingWithDatetimeGSI) (map[string]*dynamodb.AttributeValue, error) { + return dynamodbattribute.MarshalMap(ddbThingWithDatetimeGSI{ + ThingWithDatetimeGSI: m, + }) +} + +// decodeThingWithDatetimeGSI translates a ThingWithDatetimeGSI stored in DynamoDB to a ThingWithDatetimeGSI struct. +func decodeThingWithDatetimeGSI(m map[string]*dynamodb.AttributeValue, out *models.ThingWithDatetimeGSI) error { + var ddbThingWithDatetimeGSI ddbThingWithDatetimeGSI + if err := dynamodbattribute.UnmarshalMap(m, &ddbThingWithDatetimeGSI); err != nil { + return err + } + *out = ddbThingWithDatetimeGSI.ThingWithDatetimeGSI + return nil +} + +// decodeThingWithDatetimeGSIs translates a list of ThingWithDatetimeGSIs stored in DynamoDB to a slice of ThingWithDatetimeGSI structs. +func decodeThingWithDatetimeGSIs(ms []map[string]*dynamodb.AttributeValue) ([]models.ThingWithDatetimeGSI, error) { + thingWithDatetimeGSIs := make([]models.ThingWithDatetimeGSI, len(ms)) + for i, m := range ms { + var thingWithDatetimeGSI models.ThingWithDatetimeGSI + if err := decodeThingWithDatetimeGSI(m, &thingWithDatetimeGSI); err != nil { + return nil, err + } + thingWithDatetimeGSIs[i] = thingWithDatetimeGSI + } + return thingWithDatetimeGSIs, nil +} diff --git a/samples/gen-go-db-only/db/interface.go b/samples/gen-go-db-only/db/interface.go index 7533fb85..e4e73a0c 100644 --- a/samples/gen-go-db-only/db/interface.go +++ b/samples/gen-go-db-only/db/interface.go @@ -214,6 +214,19 @@ type Interface interface { // DeleteThingWithDateTimeComposite deletes a ThingWithDateTimeComposite from the database. DeleteThingWithDateTimeComposite(ctx context.Context, typeVar string, id string, created strfmt.DateTime, resource string) error + // SaveThingWithDatetimeGSI saves a ThingWithDatetimeGSI to the database. + SaveThingWithDatetimeGSI(ctx context.Context, m models.ThingWithDatetimeGSI) error + // GetThingWithDatetimeGSI retrieves a ThingWithDatetimeGSI from the database. + GetThingWithDatetimeGSI(ctx context.Context, id string) (*models.ThingWithDatetimeGSI, error) + // ScanThingWithDatetimeGSIs runs a scan on the ThingWithDatetimeGSIs table. + ScanThingWithDatetimeGSIs(ctx context.Context, input ScanThingWithDatetimeGSIsInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error + // DeleteThingWithDatetimeGSI deletes a ThingWithDatetimeGSI from the database. + DeleteThingWithDatetimeGSI(ctx context.Context, id string) error + // GetThingWithDatetimeGSIsByDatetimeAndID retrieves a page of ThingWithDatetimeGSIs from the database. + GetThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error + // ScanThingWithDatetimeGSIsByDatetimeAndID runs a scan on the DatetimeAndID index. + ScanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error + // SaveThingWithEnumHashKey saves a ThingWithEnumHashKey to the database. SaveThingWithEnumHashKey(ctx context.Context, m models.ThingWithEnumHashKey) error // GetThingWithEnumHashKey retrieves a ThingWithEnumHashKey from the database. @@ -1577,6 +1590,78 @@ type CreatedResource struct { Resource string } +// ScanThingWithDatetimeGSIsInput is the input to the ScanThingWithDatetimeGSIs method. +type ScanThingWithDatetimeGSIsInput struct { + // StartingAfter is an optional specification of an (exclusive) starting point. + StartingAfter *models.ThingWithDatetimeGSI + // DisableConsistentRead turns off the default behavior of running a consistent read. + DisableConsistentRead bool + // Limit is an optional limit of how many items to evaluate. + Limit *int64 + // Limiter is an optional limit on how quickly items are scanned. + Limiter *rate.Limiter +} + +// ErrThingWithDatetimeGSINotFound is returned when the database fails to find a ThingWithDatetimeGSI. +type ErrThingWithDatetimeGSINotFound struct { + ID string +} + +var _ error = ErrThingWithDatetimeGSINotFound{} + +// Error returns a description of the error. +func (e ErrThingWithDatetimeGSINotFound) Error() string { + return "could not find ThingWithDatetimeGSI" +} + +// GetThingWithDatetimeGSIsByDatetimeAndIDInput is the query input to GetThingWithDatetimeGSIsByDatetimeAndID. +type GetThingWithDatetimeGSIsByDatetimeAndIDInput struct { + // Datetime is required + Datetime strfmt.DateTime + IDStartingAt *string + StartingAfter *models.ThingWithDatetimeGSI + Descending bool + // Limit is an optional limit of how many items to evaluate. + Limit *int64 +} + +// ErrThingWithDatetimeGSIByDatetimeAndIDNotFound is returned when the database fails to find a ThingWithDatetimeGSI. +type ErrThingWithDatetimeGSIByDatetimeAndIDNotFound struct { + Datetime strfmt.DateTime + ID string +} + +var _ error = ErrThingWithDatetimeGSIByDatetimeAndIDNotFound{} + +// Error returns a description of the error. +func (e ErrThingWithDatetimeGSIByDatetimeAndIDNotFound) Error() string { + return "could not find ThingWithDatetimeGSI" +} + +// ScanThingWithDatetimeGSIsByDatetimeAndIDInput is the input to the ScanThingWithDatetimeGSIsByDatetimeAndID method. +type ScanThingWithDatetimeGSIsByDatetimeAndIDInput struct { + // StartingAfter is an optional specification of an (exclusive) starting point. + StartingAfter *models.ThingWithDatetimeGSI + // DisableConsistentRead turns off the default behavior of running a consistent read. + DisableConsistentRead bool + // Limit is an optional limit of how many items to evaluate. + Limit *int64 + // Limiter is an optional limit on how quickly items are scanned. + Limiter *rate.Limiter +} + +// ErrThingWithDatetimeGSIAlreadyExists is returned when trying to overwrite a ThingWithDatetimeGSI. +type ErrThingWithDatetimeGSIAlreadyExists struct { + ID string +} + +var _ error = ErrThingWithDatetimeGSIAlreadyExists{} + +// Error returns a description of the error. +func (e ErrThingWithDatetimeGSIAlreadyExists) Error() string { + return "ThingWithDatetimeGSI already exists" +} + // ScanThingWithEnumHashKeysInput is the input to the ScanThingWithEnumHashKeys method. type ScanThingWithEnumHashKeysInput struct { // StartingAfter is an optional specification of an (exclusive) starting point. diff --git a/samples/gen-go-db-only/db/mock_db.go b/samples/gen-go-db-only/db/mock_db.go index bcd1b3ef..b87dbd4f 100644 --- a/samples/gen-go-db-only/db/mock_db.go +++ b/samples/gen-go-db-only/db/mock_db.go @@ -247,6 +247,20 @@ func (mr *MockInterfaceMockRecorder) DeleteThingWithDateTimeComposite(ctx, typeV return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteThingWithDateTimeComposite", reflect.TypeOf((*MockInterface)(nil).DeleteThingWithDateTimeComposite), ctx, typeVar, id, created, resource) } +// DeleteThingWithDatetimeGSI mocks base method. +func (m *MockInterface) DeleteThingWithDatetimeGSI(ctx context.Context, id string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteThingWithDatetimeGSI", ctx, id) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteThingWithDatetimeGSI indicates an expected call of DeleteThingWithDatetimeGSI. +func (mr *MockInterfaceMockRecorder) DeleteThingWithDatetimeGSI(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteThingWithDatetimeGSI", reflect.TypeOf((*MockInterface)(nil).DeleteThingWithDatetimeGSI), ctx, id) +} + // DeleteThingWithEnumHashKey mocks base method. func (m *MockInterface) DeleteThingWithEnumHashKey(ctx context.Context, branch v9.Branch, date strfmt.DateTime) error { m.ctrl.T.Helper() @@ -879,6 +893,35 @@ func (mr *MockInterfaceMockRecorder) GetThingWithDateTimeCompositesByTypeIDAndCr return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThingWithDateTimeCompositesByTypeIDAndCreatedResource", reflect.TypeOf((*MockInterface)(nil).GetThingWithDateTimeCompositesByTypeIDAndCreatedResource), ctx, input, fn) } +// GetThingWithDatetimeGSI mocks base method. +func (m *MockInterface) GetThingWithDatetimeGSI(ctx context.Context, id string) (*v9.ThingWithDatetimeGSI, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetThingWithDatetimeGSI", ctx, id) + ret0, _ := ret[0].(*v9.ThingWithDatetimeGSI) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetThingWithDatetimeGSI indicates an expected call of GetThingWithDatetimeGSI. +func (mr *MockInterfaceMockRecorder) GetThingWithDatetimeGSI(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThingWithDatetimeGSI", reflect.TypeOf((*MockInterface)(nil).GetThingWithDatetimeGSI), ctx, id) +} + +// GetThingWithDatetimeGSIsByDatetimeAndID mocks base method. +func (m *MockInterface) GetThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(*v9.ThingWithDatetimeGSI, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetThingWithDatetimeGSIsByDatetimeAndID", ctx, input, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetThingWithDatetimeGSIsByDatetimeAndID indicates an expected call of GetThingWithDatetimeGSIsByDatetimeAndID. +func (mr *MockInterfaceMockRecorder) GetThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThingWithDatetimeGSIsByDatetimeAndID", reflect.TypeOf((*MockInterface)(nil).GetThingWithDatetimeGSIsByDatetimeAndID), ctx, input, fn) +} + // GetThingWithEnumHashKey mocks base method. func (m *MockInterface) GetThingWithEnumHashKey(ctx context.Context, branch v9.Branch, date strfmt.DateTime) (*v9.ThingWithEnumHashKey, error) { m.ctrl.T.Helper() @@ -1392,6 +1435,20 @@ func (mr *MockInterfaceMockRecorder) SaveThingWithDateTimeComposite(ctx, m inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveThingWithDateTimeComposite", reflect.TypeOf((*MockInterface)(nil).SaveThingWithDateTimeComposite), ctx, m) } +// SaveThingWithDatetimeGSI mocks base method. +func (m_2 *MockInterface) SaveThingWithDatetimeGSI(ctx context.Context, m v9.ThingWithDatetimeGSI) error { + m_2.ctrl.T.Helper() + ret := m_2.ctrl.Call(m_2, "SaveThingWithDatetimeGSI", ctx, m) + ret0, _ := ret[0].(error) + return ret0 +} + +// SaveThingWithDatetimeGSI indicates an expected call of SaveThingWithDatetimeGSI. +func (mr *MockInterfaceMockRecorder) SaveThingWithDatetimeGSI(ctx, m interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveThingWithDatetimeGSI", reflect.TypeOf((*MockInterface)(nil).SaveThingWithDatetimeGSI), ctx, m) +} + // SaveThingWithEnumHashKey mocks base method. func (m_2 *MockInterface) SaveThingWithEnumHashKey(ctx context.Context, m v9.ThingWithEnumHashKey) error { m_2.ctrl.T.Helper() @@ -1812,6 +1869,34 @@ func (mr *MockInterfaceMockRecorder) ScanThingWithDateTimeComposites(ctx, input, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanThingWithDateTimeComposites", reflect.TypeOf((*MockInterface)(nil).ScanThingWithDateTimeComposites), ctx, input, fn) } +// ScanThingWithDatetimeGSIs mocks base method. +func (m *MockInterface) ScanThingWithDatetimeGSIs(ctx context.Context, input ScanThingWithDatetimeGSIsInput, fn func(*v9.ThingWithDatetimeGSI, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ScanThingWithDatetimeGSIs", ctx, input, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// ScanThingWithDatetimeGSIs indicates an expected call of ScanThingWithDatetimeGSIs. +func (mr *MockInterfaceMockRecorder) ScanThingWithDatetimeGSIs(ctx, input, fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanThingWithDatetimeGSIs", reflect.TypeOf((*MockInterface)(nil).ScanThingWithDatetimeGSIs), ctx, input, fn) +} + +// ScanThingWithDatetimeGSIsByDatetimeAndID mocks base method. +func (m *MockInterface) ScanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(*v9.ThingWithDatetimeGSI, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ScanThingWithDatetimeGSIsByDatetimeAndID", ctx, input, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// ScanThingWithDatetimeGSIsByDatetimeAndID indicates an expected call of ScanThingWithDatetimeGSIsByDatetimeAndID. +func (mr *MockInterfaceMockRecorder) ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanThingWithDatetimeGSIsByDatetimeAndID", reflect.TypeOf((*MockInterface)(nil).ScanThingWithDatetimeGSIsByDatetimeAndID), ctx, input, fn) +} + // ScanThingWithEnumHashKeys mocks base method. func (m *MockInterface) ScanThingWithEnumHashKeys(ctx context.Context, input ScanThingWithEnumHashKeysInput, fn func(*v9.ThingWithEnumHashKey, bool) bool) error { m.ctrl.T.Helper() diff --git a/samples/gen-go-db-only/db/tests/tests.go b/samples/gen-go-db-only/db/tests/tests.go index 9490700e..20a56c3d 100644 --- a/samples/gen-go-db-only/db/tests/tests.go +++ b/samples/gen-go-db-only/db/tests/tests.go @@ -115,6 +115,12 @@ func RunDBTests(t *testing.T, dbFactory func() db.Interface) { t.Run("GetThingWithDateTimeCompositesByTypeIDAndCreatedResource", GetThingWithDateTimeCompositesByTypeIDAndCreatedResource(dbFactory(), t)) t.Run("SaveThingWithDateTimeComposite", SaveThingWithDateTimeComposite(dbFactory(), t)) t.Run("DeleteThingWithDateTimeComposite", DeleteThingWithDateTimeComposite(dbFactory(), t)) + t.Run("GetThingWithDatetimeGSI", GetThingWithDatetimeGSI(dbFactory(), t)) + t.Run("ScanThingWithDatetimeGSIs", ScanThingWithDatetimeGSIs(dbFactory(), t)) + t.Run("SaveThingWithDatetimeGSI", SaveThingWithDatetimeGSI(dbFactory(), t)) + t.Run("DeleteThingWithDatetimeGSI", DeleteThingWithDatetimeGSI(dbFactory(), t)) + t.Run("GetThingWithDatetimeGSIsByDatetimeAndID", GetThingWithDatetimeGSIsByDatetimeAndID(dbFactory(), t)) + t.Run("ScanThingWithDatetimeGSIsByDatetimeAndID", ScanThingWithDatetimeGSIsByDatetimeAndID(dbFactory(), t)) t.Run("GetThingWithEnumHashKey", GetThingWithEnumHashKey(dbFactory(), t)) t.Run("ScanThingWithEnumHashKeys", ScanThingWithEnumHashKeys(dbFactory(), t)) t.Run("GetThingWithEnumHashKeysByBranchAndDate", GetThingWithEnumHashKeysByBranchAndDate(dbFactory(), t)) @@ -8774,6 +8780,453 @@ func DeleteThingWithDateTimeComposite(s db.Interface, t *testing.T) func(t *test } } +func GetThingWithDatetimeGSI(s db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + m := models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + } + require.Nil(t, s.SaveThingWithDatetimeGSI(ctx, m)) + m2, err := s.GetThingWithDatetimeGSI(ctx, m.ID) + require.Nil(t, err) + require.Equal(t, m.ID, m2.ID) + + _, err = s.GetThingWithDatetimeGSI(ctx, "string2") + require.NotNil(t, err) + require.IsType(t, err, db.ErrThingWithDatetimeGSINotFound{}) + } +} + +// The scan tests are structured differently compared to other tests in because items returned by scans +// are not returned in any particular order, so we can't simply declare what the expected arrays of items are. +func ScanThingWithDatetimeGSIs(d db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + })) + + t.Run("basic", func(t *testing.T) { + expected := []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + }, + } + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, db.ScanThingWithDatetimeGSIsInput{}, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + // We can't use Equal here because Scan doesn't return items in any specific order. + require.ElementsMatch(t, expected, actual) + }) + + t.Run("starting after", func(t *testing.T) { + // Scan for everything. + allItems := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, db.ScanThingWithDatetimeGSIsInput{}, func(m *models.ThingWithDatetimeGSI, last bool) bool { + allItems = append(allItems, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + firstItem := allItems[0] + + // Scan for everything after the first item. + scanInput := db.ScanThingWithDatetimeGSIsInput{ + StartingAfter: &models.ThingWithDatetimeGSI{ + ID: firstItem.ID, + }, + } + actual := []models.ThingWithDatetimeGSI{} + err = d.ScanThingWithDatetimeGSIs(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + expected := allItems[1:] + require.Equal(t, expected, actual) + }) + + t.Run("limit", func(t *testing.T) { + limit := int64(1) + // Scan for just the first item. + scanInput := db.ScanThingWithDatetimeGSIsInput{ + Limit: &limit, + } + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + require.Len(t, actual, 1) + }) + } +} + +func SaveThingWithDatetimeGSI(s db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + m := models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + } + require.Nil(t, s.SaveThingWithDatetimeGSI(ctx, m)) + require.IsType(t, db.ErrThingWithDatetimeGSIAlreadyExists{}, s.SaveThingWithDatetimeGSI(ctx, m)) + } +} + +func DeleteThingWithDatetimeGSI(s db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + m := models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + } + require.Nil(t, s.SaveThingWithDatetimeGSI(ctx, m)) + require.Nil(t, s.DeleteThingWithDatetimeGSI(ctx, m.ID)) + } +} + +type getThingWithDatetimeGSIsByDatetimeAndIDInput struct { + ctx context.Context + input db.GetThingWithDatetimeGSIsByDatetimeAndIDInput +} +type getThingWithDatetimeGSIsByDatetimeAndIDOutput struct { + thingWithDatetimeGSIs []models.ThingWithDatetimeGSI + err error +} +type getThingWithDatetimeGSIsByDatetimeAndIDTest struct { + testName string + d db.Interface + input getThingWithDatetimeGSIsByDatetimeAndIDInput + output getThingWithDatetimeGSIsByDatetimeAndIDOutput +} + +func (g getThingWithDatetimeGSIsByDatetimeAndIDTest) run(t *testing.T) { + thingWithDatetimeGSIs := []models.ThingWithDatetimeGSI{} + fn := func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool { + thingWithDatetimeGSIs = append(thingWithDatetimeGSIs, *m) + if lastThingWithDatetimeGSI { + return false + } + return true + } + err := g.d.GetThingWithDatetimeGSIsByDatetimeAndID(g.input.ctx, g.input.input, fn) + if err != nil { + fmt.Println(err.Error()) + } + require.Equal(t, g.output.err, err) + require.Equal(t, g.output.thingWithDatetimeGSIs, thingWithDatetimeGSIs) +} + +func GetThingWithDatetimeGSIsByDatetimeAndID(d db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + })) + limit := int64(3) + tests := []getThingWithDatetimeGSIsByDatetimeAndIDTest{ + { + testName: "basic", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + Limit: &limit, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + }, + err: nil, + }, + }, + { + testName: "descending", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + Descending: true, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + }, + err: nil, + }, + }, + { + testName: "starting after", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + StartingAfter: &models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + }, + err: nil, + }, + }, + { + testName: "starting after descending", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + StartingAfter: &models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + Descending: true, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + }, + err: nil, + }, + }, + { + testName: "starting at", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + IDStartingAt: db.String("string2"), + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + }, + err: nil, + }, + }, + } + for _, test := range tests { + t.Run(test.testName, test.run) + } + } +} + +// The scan tests are structured differently compared to other tests in because items returned by scans +// are not returned in any particular order, so we can't simply declare what the expected arrays of items are. +func ScanThingWithDatetimeGSIsByDatetimeAndID(d db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + })) + + t.Run("basic", func(t *testing.T) { + expected := []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + }, + } + // Consistent read must be disabled when scaning a GSI. + scanInput := db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput{DisableConsistentRead: true} + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + // We can't use Equal here because Scan doesn't return items in any specific order. + require.ElementsMatch(t, expected, actual) + }) + + t.Run("starting after", func(t *testing.T) { + // Scan for everything. + allItems := []models.ThingWithDatetimeGSI{} + // Consistent read must be disabled when scaning a GSI. + scanInput := db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput{DisableConsistentRead: true} + err := d.ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + allItems = append(allItems, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + firstItem := allItems[0] + + // Scan for everything after the first item. + scanInput = db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput{ + DisableConsistentRead: true, + StartingAfter: &models.ThingWithDatetimeGSI{ + Datetime: firstItem.Datetime, + ID: firstItem.ID, + }, + } + actual := []models.ThingWithDatetimeGSI{} + err = d.ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + expected := allItems[1:] + require.Equal(t, expected, actual) + }) + + t.Run("limit", func(t *testing.T) { + limit := int64(1) + // Scan for just the first item. + scanInput := db.ScanThingWithDatetimeGSIsInput{ + Limit: &limit, + } + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + require.Len(t, actual, 1) + }) + } +} + func GetThingWithEnumHashKey(s db.Interface, t *testing.T) func(t *testing.T) { return func(t *testing.T) { ctx := context.Background() diff --git a/samples/gen-go-db-only/models/thing_with_datetime_g_s_i.go b/samples/gen-go-db-only/models/thing_with_datetime_g_s_i.go new file mode 100644 index 00000000..2c3ce255 --- /dev/null +++ b/samples/gen-go-db-only/models/thing_with_datetime_g_s_i.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ThingWithDatetimeGSI thing with datetime g s i +// +// swagger:model ThingWithDatetimeGSI +type ThingWithDatetimeGSI struct { + + // datetime + // Format: date-time + Datetime strfmt.DateTime `json:"datetime,omitempty"` + + // id + ID string `json:"id,omitempty"` +} + +// Validate validates this thing with datetime g s i +func (m *ThingWithDatetimeGSI) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateDatetime(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ThingWithDatetimeGSI) validateDatetime(formats strfmt.Registry) error { + + if swag.IsZero(m.Datetime) { // not required + return nil + } + + if err := validate.FormatOf("datetime", "body", "date-time", m.Datetime.String(), formats); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ThingWithDatetimeGSI) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ThingWithDatetimeGSI) UnmarshalBinary(b []byte) error { + var res ThingWithDatetimeGSI + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/samples/gen-go-db/models/thing_with_datetime_g_s_i.go b/samples/gen-go-db/models/thing_with_datetime_g_s_i.go new file mode 100644 index 00000000..2c3ce255 --- /dev/null +++ b/samples/gen-go-db/models/thing_with_datetime_g_s_i.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ThingWithDatetimeGSI thing with datetime g s i +// +// swagger:model ThingWithDatetimeGSI +type ThingWithDatetimeGSI struct { + + // datetime + // Format: date-time + Datetime strfmt.DateTime `json:"datetime,omitempty"` + + // id + ID string `json:"id,omitempty"` +} + +// Validate validates this thing with datetime g s i +func (m *ThingWithDatetimeGSI) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateDatetime(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ThingWithDatetimeGSI) validateDatetime(formats strfmt.Registry) error { + + if swag.IsZero(m.Datetime) { // not required + return nil + } + + if err := validate.FormatOf("datetime", "body", "date-time", m.Datetime.String(), formats); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ThingWithDatetimeGSI) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ThingWithDatetimeGSI) UnmarshalBinary(b []byte) error { + var res ThingWithDatetimeGSI + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/samples/gen-go-db/server/db/dynamodb/dynamodb.go b/samples/gen-go-db/server/db/dynamodb/dynamodb.go index 484d5eeb..7eef8ded 100644 --- a/samples/gen-go-db/server/db/dynamodb/dynamodb.go +++ b/samples/gen-go-db/server/db/dynamodb/dynamodb.go @@ -56,6 +56,8 @@ type Config struct { ThingWithDateRangeTable ThingWithDateRangeTable // ThingWithDateTimeCompositeTable configuration. ThingWithDateTimeCompositeTable ThingWithDateTimeCompositeTable + // ThingWithDatetimeGSITable configuration. + ThingWithDatetimeGSITable ThingWithDatetimeGSITable // ThingWithEnumHashKeyTable configuration. ThingWithEnumHashKeyTable ThingWithEnumHashKeyTable // ThingWithMatchingKeysTable configuration. @@ -277,6 +279,20 @@ func New(config Config) (*DB, error) { if thingWithDateTimeCompositeTable.WriteCapacityUnits == 0 { thingWithDateTimeCompositeTable.WriteCapacityUnits = config.DefaultWriteCapacityUnits } + // configure ThingWithDatetimeGSI table + thingWithDatetimeGSITable := config.ThingWithDatetimeGSITable + if thingWithDatetimeGSITable.DynamoDBAPI == nil { + thingWithDatetimeGSITable.DynamoDBAPI = config.DynamoDBAPI + } + if thingWithDatetimeGSITable.Prefix == "" { + thingWithDatetimeGSITable.Prefix = config.DefaultPrefix + } + if thingWithDatetimeGSITable.ReadCapacityUnits == 0 { + thingWithDatetimeGSITable.ReadCapacityUnits = config.DefaultReadCapacityUnits + } + if thingWithDatetimeGSITable.WriteCapacityUnits == 0 { + thingWithDatetimeGSITable.WriteCapacityUnits = config.DefaultWriteCapacityUnits + } // configure ThingWithEnumHashKey table thingWithEnumHashKeyTable := config.ThingWithEnumHashKeyTable if thingWithEnumHashKeyTable.DynamoDBAPI == nil { @@ -418,6 +434,7 @@ func New(config Config) (*DB, error) { thingWithCompositeEnumAttributesTable: thingWithCompositeEnumAttributesTable, thingWithDateRangeTable: thingWithDateRangeTable, thingWithDateTimeCompositeTable: thingWithDateTimeCompositeTable, + thingWithDatetimeGSITable: thingWithDatetimeGSITable, thingWithEnumHashKeyTable: thingWithEnumHashKeyTable, thingWithMatchingKeysTable: thingWithMatchingKeysTable, thingWithMultiUseCompositeAttributeTable: thingWithMultiUseCompositeAttributeTable, @@ -445,6 +462,7 @@ type DB struct { thingWithCompositeEnumAttributesTable ThingWithCompositeEnumAttributesTable thingWithDateRangeTable ThingWithDateRangeTable thingWithDateTimeCompositeTable ThingWithDateTimeCompositeTable + thingWithDatetimeGSITable ThingWithDatetimeGSITable thingWithEnumHashKeyTable ThingWithEnumHashKeyTable thingWithMatchingKeysTable ThingWithMatchingKeysTable thingWithMultiUseCompositeAttributeTable ThingWithMultiUseCompositeAttributeTable @@ -499,6 +517,9 @@ func (d DB) CreateTables(ctx context.Context) error { if err := d.thingWithDateTimeCompositeTable.create(ctx); err != nil { return err } + if err := d.thingWithDatetimeGSITable.create(ctx); err != nil { + return err + } if err := d.thingWithEnumHashKeyTable.create(ctx); err != nil { return err } @@ -999,6 +1020,36 @@ func (d DB) DeleteThingWithDateTimeComposite(ctx context.Context, typeVar string return d.thingWithDateTimeCompositeTable.deleteThingWithDateTimeComposite(ctx, typeVar, id, created, resource) } +// SaveThingWithDatetimeGSI saves a ThingWithDatetimeGSI to the database. +func (d DB) SaveThingWithDatetimeGSI(ctx context.Context, m models.ThingWithDatetimeGSI) error { + return d.thingWithDatetimeGSITable.saveThingWithDatetimeGSI(ctx, m) +} + +// GetThingWithDatetimeGSI retrieves a ThingWithDatetimeGSI from the database. +func (d DB) GetThingWithDatetimeGSI(ctx context.Context, id string) (*models.ThingWithDatetimeGSI, error) { + return d.thingWithDatetimeGSITable.getThingWithDatetimeGSI(ctx, id) +} + +// ScanThingWithDatetimeGSIs runs a scan on the ThingWithDatetimeGSIs table. +func (d DB) ScanThingWithDatetimeGSIs(ctx context.Context, input db.ScanThingWithDatetimeGSIsInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + return d.thingWithDatetimeGSITable.scanThingWithDatetimeGSIs(ctx, input, fn) +} + +// DeleteThingWithDatetimeGSI deletes a ThingWithDatetimeGSI from the database. +func (d DB) DeleteThingWithDatetimeGSI(ctx context.Context, id string) error { + return d.thingWithDatetimeGSITable.deleteThingWithDatetimeGSI(ctx, id) +} + +// GetThingWithDatetimeGSIsByDatetimeAndID retrieves a page of ThingWithDatetimeGSIs from the database. +func (d DB) GetThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + return d.thingWithDatetimeGSITable.getThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn) +} + +// ScanThingWithDatetimeGSIsByDatetimeAndID runs a scan on the DatetimeAndID index. +func (d DB) ScanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + return d.thingWithDatetimeGSITable.scanThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn) +} + // SaveThingWithEnumHashKey saves a ThingWithEnumHashKey to the database. func (d DB) SaveThingWithEnumHashKey(ctx context.Context, m models.ThingWithEnumHashKey) error { return d.thingWithEnumHashKeyTable.saveThingWithEnumHashKey(ctx, m) diff --git a/samples/gen-go-db/server/db/dynamodb/thingwithdatetimegsi.go b/samples/gen-go-db/server/db/dynamodb/thingwithdatetimegsi.go new file mode 100644 index 00000000..13b3b139 --- /dev/null +++ b/samples/gen-go-db/server/db/dynamodb/thingwithdatetimegsi.go @@ -0,0 +1,412 @@ +package dynamodb + +import ( + "context" + "fmt" + + "github.com/Clever/wag/samples/gen-go-db/models/v9" + "github.com/Clever/wag/samples/v9/gen-go-db/server/db" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" + "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface" + "github.com/go-openapi/strfmt" +) + +var _ = strfmt.DateTime{} + +// ThingWithDatetimeGSITable represents the user-configurable properties of the ThingWithDatetimeGSI table. +type ThingWithDatetimeGSITable struct { + DynamoDBAPI dynamodbiface.DynamoDBAPI + Prefix string + TableName string + ReadCapacityUnits int64 + WriteCapacityUnits int64 +} + +// ddbThingWithDatetimeGSIPrimaryKey represents the primary key of a ThingWithDatetimeGSI in DynamoDB. +type ddbThingWithDatetimeGSIPrimaryKey struct { + ID string `dynamodbav:"id"` +} + +// ddbThingWithDatetimeGSIGSIByDate represents the byDate GSI. +type ddbThingWithDatetimeGSIGSIByDate struct { + Datetime strfmt.DateTime `dynamodbav:"datetime"` + ID string `dynamodbav:"id"` +} + +// ddbThingWithDatetimeGSI represents a ThingWithDatetimeGSI as stored in DynamoDB. +type ddbThingWithDatetimeGSI struct { + models.ThingWithDatetimeGSI +} + +func (t ThingWithDatetimeGSITable) name() string { + if t.TableName != "" { + return t.TableName + } + return fmt.Sprintf("%s-thing-with-datetime-g-s-is", t.Prefix) +} + +func (t ThingWithDatetimeGSITable) create(ctx context.Context) error { + if _, err := t.DynamoDBAPI.CreateTableWithContext(ctx, &dynamodb.CreateTableInput{ + AttributeDefinitions: []*dynamodb.AttributeDefinition{ + { + AttributeName: aws.String("datetime"), + AttributeType: aws.String("S"), + }, + { + AttributeName: aws.String("id"), + AttributeType: aws.String("S"), + }, + }, + KeySchema: []*dynamodb.KeySchemaElement{ + { + AttributeName: aws.String("id"), + KeyType: aws.String(dynamodb.KeyTypeHash), + }, + }, + GlobalSecondaryIndexes: []*dynamodb.GlobalSecondaryIndex{ + { + IndexName: aws.String("byDate"), + Projection: &dynamodb.Projection{ + ProjectionType: aws.String("ALL"), + }, + KeySchema: []*dynamodb.KeySchemaElement{ + { + AttributeName: aws.String("datetime"), + KeyType: aws.String(dynamodb.KeyTypeHash), + }, + { + AttributeName: aws.String("id"), + KeyType: aws.String(dynamodb.KeyTypeRange), + }, + }, + ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ + ReadCapacityUnits: aws.Int64(t.ReadCapacityUnits), + WriteCapacityUnits: aws.Int64(t.WriteCapacityUnits), + }, + }, + }, + ProvisionedThroughput: &dynamodb.ProvisionedThroughput{ + ReadCapacityUnits: aws.Int64(t.ReadCapacityUnits), + WriteCapacityUnits: aws.Int64(t.WriteCapacityUnits), + }, + TableName: aws.String(t.name()), + }); err != nil { + return err + } + return nil +} + +func (t ThingWithDatetimeGSITable) saveThingWithDatetimeGSI(ctx context.Context, m models.ThingWithDatetimeGSI) error { + data, err := encodeThingWithDatetimeGSI(m) + if err != nil { + return err + } + _, err = t.DynamoDBAPI.PutItemWithContext(ctx, &dynamodb.PutItemInput{ + TableName: aws.String(t.name()), + Item: data, + ExpressionAttributeNames: map[string]*string{ + "#ID": aws.String("id"), + }, + ConditionExpression: aws.String("attribute_not_exists(#ID)"), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeConditionalCheckFailedException: + return db.ErrThingWithDatetimeGSIAlreadyExists{ + ID: m.ID, + } + case dynamodb.ErrCodeResourceNotFoundException: + return fmt.Errorf("table or index not found: %s", t.name()) + } + } + return err + } + return nil +} + +func (t ThingWithDatetimeGSITable) getThingWithDatetimeGSI(ctx context.Context, id string) (*models.ThingWithDatetimeGSI, error) { + key, err := dynamodbattribute.MarshalMap(ddbThingWithDatetimeGSIPrimaryKey{ + ID: id, + }) + if err != nil { + return nil, err + } + res, err := t.DynamoDBAPI.GetItemWithContext(ctx, &dynamodb.GetItemInput{ + Key: key, + TableName: aws.String(t.name()), + ConsistentRead: aws.Bool(true), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeResourceNotFoundException: + return nil, fmt.Errorf("table or index not found: %s", t.name()) + } + } + return nil, err + } + + if len(res.Item) == 0 { + return nil, db.ErrThingWithDatetimeGSINotFound{ + ID: id, + } + } + + var m models.ThingWithDatetimeGSI + if err := decodeThingWithDatetimeGSI(res.Item, &m); err != nil { + return nil, err + } + + return &m, nil +} + +func (t ThingWithDatetimeGSITable) scanThingWithDatetimeGSIs(ctx context.Context, input db.ScanThingWithDatetimeGSIsInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + scanInput := &dynamodb.ScanInput{ + TableName: aws.String(t.name()), + ConsistentRead: aws.Bool(!input.DisableConsistentRead), + Limit: input.Limit, + } + if input.StartingAfter != nil { + exclusiveStartKey, err := dynamodbattribute.MarshalMap(input.StartingAfter) + if err != nil { + return fmt.Errorf("error encoding exclusive start key for scan: %s", err.Error()) + } + // must provide only the fields constituting the index + scanInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "id": exclusiveStartKey["id"], + } + } + totalRecordsProcessed := int64(0) + var innerErr error + err := t.DynamoDBAPI.ScanPagesWithContext(ctx, scanInput, func(out *dynamodb.ScanOutput, lastPage bool) bool { + items, err := decodeThingWithDatetimeGSIs(out.Items) + if err != nil { + innerErr = fmt.Errorf("error decoding %s", err.Error()) + return false + } + for i := range items { + if input.Limiter != nil { + if err := input.Limiter.Wait(ctx); err != nil { + innerErr = err + return false + } + } + isLastModel := lastPage && i == len(items)-1 + if shouldContinue := fn(&items[i], isLastModel); !shouldContinue { + return false + } + totalRecordsProcessed++ + // if the Limit of records have been passed to fn, don't pass anymore records. + if input.Limit != nil && totalRecordsProcessed == *input.Limit { + return false + } + } + return true + }) + if innerErr != nil { + return innerErr + } + return err +} + +func (t ThingWithDatetimeGSITable) deleteThingWithDatetimeGSI(ctx context.Context, id string) error { + key, err := dynamodbattribute.MarshalMap(ddbThingWithDatetimeGSIPrimaryKey{ + ID: id, + }) + if err != nil { + return err + } + _, err = t.DynamoDBAPI.DeleteItemWithContext(ctx, &dynamodb.DeleteItemInput{ + Key: key, + TableName: aws.String(t.name()), + }) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeResourceNotFoundException: + return fmt.Errorf("table or index not found: %s", t.name()) + } + } + return err + } + + return nil +} + +func (t ThingWithDatetimeGSITable) getThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + if input.IDStartingAt != nil && input.StartingAfter != nil { + return fmt.Errorf("Can specify only one of input.IDStartingAt or input.StartingAfter") + } + if toDynamoTimeString(input.Datetime) == "" { + return fmt.Errorf("Hash key input.Datetime cannot be empty") + } + queryInput := &dynamodb.QueryInput{ + TableName: aws.String(t.name()), + IndexName: aws.String("byDate"), + ExpressionAttributeNames: map[string]*string{ + "#DATETIME": aws.String("datetime"), + }, + ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ + ":datetime": &dynamodb.AttributeValue{ + S: aws.String(toDynamoTimeString(input.Datetime)), + }, + }, + ScanIndexForward: aws.Bool(!input.Descending), + ConsistentRead: aws.Bool(false), + } + if input.Limit != nil { + queryInput.Limit = input.Limit + } + if input.IDStartingAt == nil { + queryInput.KeyConditionExpression = aws.String("#DATETIME = :datetime") + } else { + queryInput.ExpressionAttributeNames["#ID"] = aws.String("id") + queryInput.ExpressionAttributeValues[":id"] = &dynamodb.AttributeValue{ + S: aws.String(*input.IDStartingAt), + } + if input.Descending { + queryInput.KeyConditionExpression = aws.String("#DATETIME = :datetime AND #ID <= :id") + } else { + queryInput.KeyConditionExpression = aws.String("#DATETIME = :datetime AND #ID >= :id") + } + } + if input.StartingAfter != nil { + queryInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "id": &dynamodb.AttributeValue{ + S: aws.String(input.StartingAfter.ID), + }, + "datetime": &dynamodb.AttributeValue{ + S: aws.String(toDynamoTimeString(input.StartingAfter.Datetime)), + }, + } + } + + totalRecordsProcessed := int64(0) + var pageFnErr error + pageFn := func(queryOutput *dynamodb.QueryOutput, lastPage bool) bool { + if len(queryOutput.Items) == 0 { + return false + } + items, err := decodeThingWithDatetimeGSIs(queryOutput.Items) + if err != nil { + pageFnErr = err + return false + } + hasMore := true + for i := range items { + if lastPage == true { + hasMore = i < len(items)-1 + } + if !fn(&items[i], !hasMore) { + return false + } + totalRecordsProcessed++ + // if the Limit of records have been passed to fn, don't pass anymore records. + if input.Limit != nil && totalRecordsProcessed == *input.Limit { + return false + } + } + return true + } + + err := t.DynamoDBAPI.QueryPagesWithContext(ctx, queryInput, pageFn) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case dynamodb.ErrCodeResourceNotFoundException: + return fmt.Errorf("table or index not found: %s", t.name()) + } + } + return err + } + if pageFnErr != nil { + return pageFnErr + } + + return nil +} +func (t ThingWithDatetimeGSITable) scanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error { + scanInput := &dynamodb.ScanInput{ + TableName: aws.String(t.name()), + ConsistentRead: aws.Bool(!input.DisableConsistentRead), + Limit: input.Limit, + IndexName: aws.String("byDate"), + } + if input.StartingAfter != nil { + exclusiveStartKey, err := dynamodbattribute.MarshalMap(input.StartingAfter) + if err != nil { + return fmt.Errorf("error encoding exclusive start key for scan: %s", err.Error()) + } + // must provide the fields constituting the index and the primary key + // https://stackoverflow.com/questions/40988397/dynamodb-pagination-with-withexclusivestartkey-on-a-global-secondary-index + scanInput.ExclusiveStartKey = map[string]*dynamodb.AttributeValue{ + "id": exclusiveStartKey["id"], + "datetime": exclusiveStartKey["datetime"], + } + } + totalRecordsProcessed := int64(0) + var innerErr error + err := t.DynamoDBAPI.ScanPagesWithContext(ctx, scanInput, func(out *dynamodb.ScanOutput, lastPage bool) bool { + items, err := decodeThingWithDatetimeGSIs(out.Items) + if err != nil { + innerErr = fmt.Errorf("error decoding %s", err.Error()) + return false + } + for i := range items { + if input.Limiter != nil { + if err := input.Limiter.Wait(ctx); err != nil { + innerErr = err + return false + } + } + isLastModel := lastPage && i == len(items)-1 + if shouldContinue := fn(&items[i], isLastModel); !shouldContinue { + return false + } + totalRecordsProcessed++ + // if the Limit of records have been passed to fn, don't pass anymore records. + if input.Limit != nil && totalRecordsProcessed == *input.Limit { + return false + } + } + return true + }) + if innerErr != nil { + return innerErr + } + return err +} + +// encodeThingWithDatetimeGSI encodes a ThingWithDatetimeGSI as a DynamoDB map of attribute values. +func encodeThingWithDatetimeGSI(m models.ThingWithDatetimeGSI) (map[string]*dynamodb.AttributeValue, error) { + return dynamodbattribute.MarshalMap(ddbThingWithDatetimeGSI{ + ThingWithDatetimeGSI: m, + }) +} + +// decodeThingWithDatetimeGSI translates a ThingWithDatetimeGSI stored in DynamoDB to a ThingWithDatetimeGSI struct. +func decodeThingWithDatetimeGSI(m map[string]*dynamodb.AttributeValue, out *models.ThingWithDatetimeGSI) error { + var ddbThingWithDatetimeGSI ddbThingWithDatetimeGSI + if err := dynamodbattribute.UnmarshalMap(m, &ddbThingWithDatetimeGSI); err != nil { + return err + } + *out = ddbThingWithDatetimeGSI.ThingWithDatetimeGSI + return nil +} + +// decodeThingWithDatetimeGSIs translates a list of ThingWithDatetimeGSIs stored in DynamoDB to a slice of ThingWithDatetimeGSI structs. +func decodeThingWithDatetimeGSIs(ms []map[string]*dynamodb.AttributeValue) ([]models.ThingWithDatetimeGSI, error) { + thingWithDatetimeGSIs := make([]models.ThingWithDatetimeGSI, len(ms)) + for i, m := range ms { + var thingWithDatetimeGSI models.ThingWithDatetimeGSI + if err := decodeThingWithDatetimeGSI(m, &thingWithDatetimeGSI); err != nil { + return nil, err + } + thingWithDatetimeGSIs[i] = thingWithDatetimeGSI + } + return thingWithDatetimeGSIs, nil +} diff --git a/samples/gen-go-db/server/db/interface.go b/samples/gen-go-db/server/db/interface.go index 892c5659..bd4cacc9 100644 --- a/samples/gen-go-db/server/db/interface.go +++ b/samples/gen-go-db/server/db/interface.go @@ -214,6 +214,19 @@ type Interface interface { // DeleteThingWithDateTimeComposite deletes a ThingWithDateTimeComposite from the database. DeleteThingWithDateTimeComposite(ctx context.Context, typeVar string, id string, created strfmt.DateTime, resource string) error + // SaveThingWithDatetimeGSI saves a ThingWithDatetimeGSI to the database. + SaveThingWithDatetimeGSI(ctx context.Context, m models.ThingWithDatetimeGSI) error + // GetThingWithDatetimeGSI retrieves a ThingWithDatetimeGSI from the database. + GetThingWithDatetimeGSI(ctx context.Context, id string) (*models.ThingWithDatetimeGSI, error) + // ScanThingWithDatetimeGSIs runs a scan on the ThingWithDatetimeGSIs table. + ScanThingWithDatetimeGSIs(ctx context.Context, input ScanThingWithDatetimeGSIsInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error + // DeleteThingWithDatetimeGSI deletes a ThingWithDatetimeGSI from the database. + DeleteThingWithDatetimeGSI(ctx context.Context, id string) error + // GetThingWithDatetimeGSIsByDatetimeAndID retrieves a page of ThingWithDatetimeGSIs from the database. + GetThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error + // ScanThingWithDatetimeGSIsByDatetimeAndID runs a scan on the DatetimeAndID index. + ScanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool) error + // SaveThingWithEnumHashKey saves a ThingWithEnumHashKey to the database. SaveThingWithEnumHashKey(ctx context.Context, m models.ThingWithEnumHashKey) error // GetThingWithEnumHashKey retrieves a ThingWithEnumHashKey from the database. @@ -1577,6 +1590,78 @@ type CreatedResource struct { Resource string } +// ScanThingWithDatetimeGSIsInput is the input to the ScanThingWithDatetimeGSIs method. +type ScanThingWithDatetimeGSIsInput struct { + // StartingAfter is an optional specification of an (exclusive) starting point. + StartingAfter *models.ThingWithDatetimeGSI + // DisableConsistentRead turns off the default behavior of running a consistent read. + DisableConsistentRead bool + // Limit is an optional limit of how many items to evaluate. + Limit *int64 + // Limiter is an optional limit on how quickly items are scanned. + Limiter *rate.Limiter +} + +// ErrThingWithDatetimeGSINotFound is returned when the database fails to find a ThingWithDatetimeGSI. +type ErrThingWithDatetimeGSINotFound struct { + ID string +} + +var _ error = ErrThingWithDatetimeGSINotFound{} + +// Error returns a description of the error. +func (e ErrThingWithDatetimeGSINotFound) Error() string { + return "could not find ThingWithDatetimeGSI" +} + +// GetThingWithDatetimeGSIsByDatetimeAndIDInput is the query input to GetThingWithDatetimeGSIsByDatetimeAndID. +type GetThingWithDatetimeGSIsByDatetimeAndIDInput struct { + // Datetime is required + Datetime strfmt.DateTime + IDStartingAt *string + StartingAfter *models.ThingWithDatetimeGSI + Descending bool + // Limit is an optional limit of how many items to evaluate. + Limit *int64 +} + +// ErrThingWithDatetimeGSIByDatetimeAndIDNotFound is returned when the database fails to find a ThingWithDatetimeGSI. +type ErrThingWithDatetimeGSIByDatetimeAndIDNotFound struct { + Datetime strfmt.DateTime + ID string +} + +var _ error = ErrThingWithDatetimeGSIByDatetimeAndIDNotFound{} + +// Error returns a description of the error. +func (e ErrThingWithDatetimeGSIByDatetimeAndIDNotFound) Error() string { + return "could not find ThingWithDatetimeGSI" +} + +// ScanThingWithDatetimeGSIsByDatetimeAndIDInput is the input to the ScanThingWithDatetimeGSIsByDatetimeAndID method. +type ScanThingWithDatetimeGSIsByDatetimeAndIDInput struct { + // StartingAfter is an optional specification of an (exclusive) starting point. + StartingAfter *models.ThingWithDatetimeGSI + // DisableConsistentRead turns off the default behavior of running a consistent read. + DisableConsistentRead bool + // Limit is an optional limit of how many items to evaluate. + Limit *int64 + // Limiter is an optional limit on how quickly items are scanned. + Limiter *rate.Limiter +} + +// ErrThingWithDatetimeGSIAlreadyExists is returned when trying to overwrite a ThingWithDatetimeGSI. +type ErrThingWithDatetimeGSIAlreadyExists struct { + ID string +} + +var _ error = ErrThingWithDatetimeGSIAlreadyExists{} + +// Error returns a description of the error. +func (e ErrThingWithDatetimeGSIAlreadyExists) Error() string { + return "ThingWithDatetimeGSI already exists" +} + // ScanThingWithEnumHashKeysInput is the input to the ScanThingWithEnumHashKeys method. type ScanThingWithEnumHashKeysInput struct { // StartingAfter is an optional specification of an (exclusive) starting point. diff --git a/samples/gen-go-db/server/db/mock_db.go b/samples/gen-go-db/server/db/mock_db.go index d468ae25..1782d4c2 100644 --- a/samples/gen-go-db/server/db/mock_db.go +++ b/samples/gen-go-db/server/db/mock_db.go @@ -247,6 +247,20 @@ func (mr *MockInterfaceMockRecorder) DeleteThingWithDateTimeComposite(ctx, typeV return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteThingWithDateTimeComposite", reflect.TypeOf((*MockInterface)(nil).DeleteThingWithDateTimeComposite), ctx, typeVar, id, created, resource) } +// DeleteThingWithDatetimeGSI mocks base method. +func (m *MockInterface) DeleteThingWithDatetimeGSI(ctx context.Context, id string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteThingWithDatetimeGSI", ctx, id) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteThingWithDatetimeGSI indicates an expected call of DeleteThingWithDatetimeGSI. +func (mr *MockInterfaceMockRecorder) DeleteThingWithDatetimeGSI(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteThingWithDatetimeGSI", reflect.TypeOf((*MockInterface)(nil).DeleteThingWithDatetimeGSI), ctx, id) +} + // DeleteThingWithEnumHashKey mocks base method. func (m *MockInterface) DeleteThingWithEnumHashKey(ctx context.Context, branch v9.Branch, date strfmt.DateTime) error { m.ctrl.T.Helper() @@ -879,6 +893,35 @@ func (mr *MockInterfaceMockRecorder) GetThingWithDateTimeCompositesByTypeIDAndCr return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThingWithDateTimeCompositesByTypeIDAndCreatedResource", reflect.TypeOf((*MockInterface)(nil).GetThingWithDateTimeCompositesByTypeIDAndCreatedResource), ctx, input, fn) } +// GetThingWithDatetimeGSI mocks base method. +func (m *MockInterface) GetThingWithDatetimeGSI(ctx context.Context, id string) (*v9.ThingWithDatetimeGSI, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetThingWithDatetimeGSI", ctx, id) + ret0, _ := ret[0].(*v9.ThingWithDatetimeGSI) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetThingWithDatetimeGSI indicates an expected call of GetThingWithDatetimeGSI. +func (mr *MockInterfaceMockRecorder) GetThingWithDatetimeGSI(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThingWithDatetimeGSI", reflect.TypeOf((*MockInterface)(nil).GetThingWithDatetimeGSI), ctx, id) +} + +// GetThingWithDatetimeGSIsByDatetimeAndID mocks base method. +func (m *MockInterface) GetThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input GetThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(*v9.ThingWithDatetimeGSI, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetThingWithDatetimeGSIsByDatetimeAndID", ctx, input, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetThingWithDatetimeGSIsByDatetimeAndID indicates an expected call of GetThingWithDatetimeGSIsByDatetimeAndID. +func (mr *MockInterfaceMockRecorder) GetThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetThingWithDatetimeGSIsByDatetimeAndID", reflect.TypeOf((*MockInterface)(nil).GetThingWithDatetimeGSIsByDatetimeAndID), ctx, input, fn) +} + // GetThingWithEnumHashKey mocks base method. func (m *MockInterface) GetThingWithEnumHashKey(ctx context.Context, branch v9.Branch, date strfmt.DateTime) (*v9.ThingWithEnumHashKey, error) { m.ctrl.T.Helper() @@ -1392,6 +1435,20 @@ func (mr *MockInterfaceMockRecorder) SaveThingWithDateTimeComposite(ctx, m inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveThingWithDateTimeComposite", reflect.TypeOf((*MockInterface)(nil).SaveThingWithDateTimeComposite), ctx, m) } +// SaveThingWithDatetimeGSI mocks base method. +func (m_2 *MockInterface) SaveThingWithDatetimeGSI(ctx context.Context, m v9.ThingWithDatetimeGSI) error { + m_2.ctrl.T.Helper() + ret := m_2.ctrl.Call(m_2, "SaveThingWithDatetimeGSI", ctx, m) + ret0, _ := ret[0].(error) + return ret0 +} + +// SaveThingWithDatetimeGSI indicates an expected call of SaveThingWithDatetimeGSI. +func (mr *MockInterfaceMockRecorder) SaveThingWithDatetimeGSI(ctx, m interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveThingWithDatetimeGSI", reflect.TypeOf((*MockInterface)(nil).SaveThingWithDatetimeGSI), ctx, m) +} + // SaveThingWithEnumHashKey mocks base method. func (m_2 *MockInterface) SaveThingWithEnumHashKey(ctx context.Context, m v9.ThingWithEnumHashKey) error { m_2.ctrl.T.Helper() @@ -1812,6 +1869,34 @@ func (mr *MockInterfaceMockRecorder) ScanThingWithDateTimeComposites(ctx, input, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanThingWithDateTimeComposites", reflect.TypeOf((*MockInterface)(nil).ScanThingWithDateTimeComposites), ctx, input, fn) } +// ScanThingWithDatetimeGSIs mocks base method. +func (m *MockInterface) ScanThingWithDatetimeGSIs(ctx context.Context, input ScanThingWithDatetimeGSIsInput, fn func(*v9.ThingWithDatetimeGSI, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ScanThingWithDatetimeGSIs", ctx, input, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// ScanThingWithDatetimeGSIs indicates an expected call of ScanThingWithDatetimeGSIs. +func (mr *MockInterfaceMockRecorder) ScanThingWithDatetimeGSIs(ctx, input, fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanThingWithDatetimeGSIs", reflect.TypeOf((*MockInterface)(nil).ScanThingWithDatetimeGSIs), ctx, input, fn) +} + +// ScanThingWithDatetimeGSIsByDatetimeAndID mocks base method. +func (m *MockInterface) ScanThingWithDatetimeGSIsByDatetimeAndID(ctx context.Context, input ScanThingWithDatetimeGSIsByDatetimeAndIDInput, fn func(*v9.ThingWithDatetimeGSI, bool) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ScanThingWithDatetimeGSIsByDatetimeAndID", ctx, input, fn) + ret0, _ := ret[0].(error) + return ret0 +} + +// ScanThingWithDatetimeGSIsByDatetimeAndID indicates an expected call of ScanThingWithDatetimeGSIsByDatetimeAndID. +func (mr *MockInterfaceMockRecorder) ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, input, fn interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScanThingWithDatetimeGSIsByDatetimeAndID", reflect.TypeOf((*MockInterface)(nil).ScanThingWithDatetimeGSIsByDatetimeAndID), ctx, input, fn) +} + // ScanThingWithEnumHashKeys mocks base method. func (m *MockInterface) ScanThingWithEnumHashKeys(ctx context.Context, input ScanThingWithEnumHashKeysInput, fn func(*v9.ThingWithEnumHashKey, bool) bool) error { m.ctrl.T.Helper() diff --git a/samples/gen-go-db/server/db/tests/tests.go b/samples/gen-go-db/server/db/tests/tests.go index 14de5dae..1647f0be 100644 --- a/samples/gen-go-db/server/db/tests/tests.go +++ b/samples/gen-go-db/server/db/tests/tests.go @@ -115,6 +115,12 @@ func RunDBTests(t *testing.T, dbFactory func() db.Interface) { t.Run("GetThingWithDateTimeCompositesByTypeIDAndCreatedResource", GetThingWithDateTimeCompositesByTypeIDAndCreatedResource(dbFactory(), t)) t.Run("SaveThingWithDateTimeComposite", SaveThingWithDateTimeComposite(dbFactory(), t)) t.Run("DeleteThingWithDateTimeComposite", DeleteThingWithDateTimeComposite(dbFactory(), t)) + t.Run("GetThingWithDatetimeGSI", GetThingWithDatetimeGSI(dbFactory(), t)) + t.Run("ScanThingWithDatetimeGSIs", ScanThingWithDatetimeGSIs(dbFactory(), t)) + t.Run("SaveThingWithDatetimeGSI", SaveThingWithDatetimeGSI(dbFactory(), t)) + t.Run("DeleteThingWithDatetimeGSI", DeleteThingWithDatetimeGSI(dbFactory(), t)) + t.Run("GetThingWithDatetimeGSIsByDatetimeAndID", GetThingWithDatetimeGSIsByDatetimeAndID(dbFactory(), t)) + t.Run("ScanThingWithDatetimeGSIsByDatetimeAndID", ScanThingWithDatetimeGSIsByDatetimeAndID(dbFactory(), t)) t.Run("GetThingWithEnumHashKey", GetThingWithEnumHashKey(dbFactory(), t)) t.Run("ScanThingWithEnumHashKeys", ScanThingWithEnumHashKeys(dbFactory(), t)) t.Run("GetThingWithEnumHashKeysByBranchAndDate", GetThingWithEnumHashKeysByBranchAndDate(dbFactory(), t)) @@ -8774,6 +8780,453 @@ func DeleteThingWithDateTimeComposite(s db.Interface, t *testing.T) func(t *test } } +func GetThingWithDatetimeGSI(s db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + m := models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + } + require.Nil(t, s.SaveThingWithDatetimeGSI(ctx, m)) + m2, err := s.GetThingWithDatetimeGSI(ctx, m.ID) + require.Nil(t, err) + require.Equal(t, m.ID, m2.ID) + + _, err = s.GetThingWithDatetimeGSI(ctx, "string2") + require.NotNil(t, err) + require.IsType(t, err, db.ErrThingWithDatetimeGSINotFound{}) + } +} + +// The scan tests are structured differently compared to other tests in because items returned by scans +// are not returned in any particular order, so we can't simply declare what the expected arrays of items are. +func ScanThingWithDatetimeGSIs(d db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + })) + + t.Run("basic", func(t *testing.T) { + expected := []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + }, + } + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, db.ScanThingWithDatetimeGSIsInput{}, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + // We can't use Equal here because Scan doesn't return items in any specific order. + require.ElementsMatch(t, expected, actual) + }) + + t.Run("starting after", func(t *testing.T) { + // Scan for everything. + allItems := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, db.ScanThingWithDatetimeGSIsInput{}, func(m *models.ThingWithDatetimeGSI, last bool) bool { + allItems = append(allItems, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + firstItem := allItems[0] + + // Scan for everything after the first item. + scanInput := db.ScanThingWithDatetimeGSIsInput{ + StartingAfter: &models.ThingWithDatetimeGSI{ + ID: firstItem.ID, + }, + } + actual := []models.ThingWithDatetimeGSI{} + err = d.ScanThingWithDatetimeGSIs(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + expected := allItems[1:] + require.Equal(t, expected, actual) + }) + + t.Run("limit", func(t *testing.T) { + limit := int64(1) + // Scan for just the first item. + scanInput := db.ScanThingWithDatetimeGSIsInput{ + Limit: &limit, + } + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + require.Len(t, actual, 1) + }) + } +} + +func SaveThingWithDatetimeGSI(s db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + m := models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + } + require.Nil(t, s.SaveThingWithDatetimeGSI(ctx, m)) + require.IsType(t, db.ErrThingWithDatetimeGSIAlreadyExists{}, s.SaveThingWithDatetimeGSI(ctx, m)) + } +} + +func DeleteThingWithDatetimeGSI(s db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + m := models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + } + require.Nil(t, s.SaveThingWithDatetimeGSI(ctx, m)) + require.Nil(t, s.DeleteThingWithDatetimeGSI(ctx, m.ID)) + } +} + +type getThingWithDatetimeGSIsByDatetimeAndIDInput struct { + ctx context.Context + input db.GetThingWithDatetimeGSIsByDatetimeAndIDInput +} +type getThingWithDatetimeGSIsByDatetimeAndIDOutput struct { + thingWithDatetimeGSIs []models.ThingWithDatetimeGSI + err error +} +type getThingWithDatetimeGSIsByDatetimeAndIDTest struct { + testName string + d db.Interface + input getThingWithDatetimeGSIsByDatetimeAndIDInput + output getThingWithDatetimeGSIsByDatetimeAndIDOutput +} + +func (g getThingWithDatetimeGSIsByDatetimeAndIDTest) run(t *testing.T) { + thingWithDatetimeGSIs := []models.ThingWithDatetimeGSI{} + fn := func(m *models.ThingWithDatetimeGSI, lastThingWithDatetimeGSI bool) bool { + thingWithDatetimeGSIs = append(thingWithDatetimeGSIs, *m) + if lastThingWithDatetimeGSI { + return false + } + return true + } + err := g.d.GetThingWithDatetimeGSIsByDatetimeAndID(g.input.ctx, g.input.input, fn) + if err != nil { + fmt.Println(err.Error()) + } + require.Equal(t, g.output.err, err) + require.Equal(t, g.output.thingWithDatetimeGSIs, thingWithDatetimeGSIs) +} + +func GetThingWithDatetimeGSIsByDatetimeAndID(d db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + })) + limit := int64(3) + tests := []getThingWithDatetimeGSIsByDatetimeAndIDTest{ + { + testName: "basic", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + Limit: &limit, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + }, + err: nil, + }, + }, + { + testName: "descending", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + Descending: true, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + }, + err: nil, + }, + }, + { + testName: "starting after", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + StartingAfter: &models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + }, + err: nil, + }, + }, + { + testName: "starting after descending", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + StartingAfter: &models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + Descending: true, + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + }, + err: nil, + }, + }, + { + testName: "starting at", + d: d, + input: getThingWithDatetimeGSIsByDatetimeAndIDInput{ + ctx: context.Background(), + input: db.GetThingWithDatetimeGSIsByDatetimeAndIDInput{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + IDStartingAt: db.String("string2"), + }, + }, + output: getThingWithDatetimeGSIsByDatetimeAndIDOutput{ + thingWithDatetimeGSIs: []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string3", + }, + }, + err: nil, + }, + }, + } + for _, test := range tests { + t.Run(test.testName, test.run) + } + } +} + +// The scan tests are structured differently compared to other tests in because items returned by scans +// are not returned in any particular order, so we can't simply declare what the expected arrays of items are. +func ScanThingWithDatetimeGSIsByDatetimeAndID(d db.Interface, t *testing.T) func(t *testing.T) { + return func(t *testing.T) { + ctx := context.Background() + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + })) + require.Nil(t, d.SaveThingWithDatetimeGSI(ctx, models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + })) + + t.Run("basic", func(t *testing.T) { + expected := []models.ThingWithDatetimeGSI{ + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:01+07:00"), + ID: "string1", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:02+07:00"), + ID: "string2", + }, + models.ThingWithDatetimeGSI{ + Datetime: mustTime("2018-03-11T15:04:03+07:00"), + ID: "string3", + }, + } + // Consistent read must be disabled when scaning a GSI. + scanInput := db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput{DisableConsistentRead: true} + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + // We can't use Equal here because Scan doesn't return items in any specific order. + require.ElementsMatch(t, expected, actual) + }) + + t.Run("starting after", func(t *testing.T) { + // Scan for everything. + allItems := []models.ThingWithDatetimeGSI{} + // Consistent read must be disabled when scaning a GSI. + scanInput := db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput{DisableConsistentRead: true} + err := d.ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + allItems = append(allItems, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + firstItem := allItems[0] + + // Scan for everything after the first item. + scanInput = db.ScanThingWithDatetimeGSIsByDatetimeAndIDInput{ + DisableConsistentRead: true, + StartingAfter: &models.ThingWithDatetimeGSI{ + Datetime: firstItem.Datetime, + ID: firstItem.ID, + }, + } + actual := []models.ThingWithDatetimeGSI{} + err = d.ScanThingWithDatetimeGSIsByDatetimeAndID(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + expected := allItems[1:] + require.Equal(t, expected, actual) + }) + + t.Run("limit", func(t *testing.T) { + limit := int64(1) + // Scan for just the first item. + scanInput := db.ScanThingWithDatetimeGSIsInput{ + Limit: &limit, + } + actual := []models.ThingWithDatetimeGSI{} + err := d.ScanThingWithDatetimeGSIs(ctx, scanInput, func(m *models.ThingWithDatetimeGSI, last bool) bool { + actual = append(actual, *m) + return true + }) + var errStr string + if err != nil { + errStr = err.Error() + } + require.NoError(t, err, errStr) + + require.Len(t, actual, 1) + }) + } +} + func GetThingWithEnumHashKey(s db.Interface, t *testing.T) func(t *testing.T) { return func(t *testing.T) { ctx := context.Background() diff --git a/samples/gen-js-db-custom-path/index.d.ts b/samples/gen-js-db-custom-path/index.d.ts index 574927e7..a5175d1f 100644 --- a/samples/gen-js-db-custom-path/index.d.ts +++ b/samples/gen-js-db-custom-path/index.d.ts @@ -201,6 +201,11 @@ declare namespace SwaggerTest { type?: string; }; + type ThingWithDatetimeGSI = { + datetime?: string; + id?: string; +}; + type ThingWithEnumHashKey = { branch?: Branch; date?: string; diff --git a/samples/gen-js-db/index.d.ts b/samples/gen-js-db/index.d.ts index 574927e7..a5175d1f 100644 --- a/samples/gen-js-db/index.d.ts +++ b/samples/gen-js-db/index.d.ts @@ -201,6 +201,11 @@ declare namespace SwaggerTest { type?: string; }; + type ThingWithDatetimeGSI = { + datetime?: string; + id?: string; +}; + type ThingWithEnumHashKey = { branch?: Branch; date?: string; diff --git a/server/gendb/bindata.go b/server/gendb/bindata.go index a7aa7867..5fc3f8d7 100644 --- a/server/gendb/bindata.go +++ b/server/gendb/bindata.go @@ -4,7 +4,7 @@ // dynamodb.go.tmpl (10.058kB) // dynamodb_test.go.tmpl (3.608kB) // interface.go.tmpl (13.851kB) -// table.go.tmpl (51.604kB) +// table.go.tmpl (52.115kB) // tests.go.tmpl (64.233kB) package gendb @@ -153,7 +153,7 @@ func interfaceGoTmpl() (*asset, error) { return a, nil } -var _tableGoTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x3d\x6b\x73\xdb\x38\x92\x9f\xa5\x5f\x81\xe8\x12\x8f\xe4\x95\xe5\x24\xb7\x75\xb7\xab\x59\x6f\x95\x93\x38\x89\x2b\x9b\x8c\x2f\xce\xcc\x5c\x95\xcb\x95\x85\x49\xc8\xc2\x5a\x24\x15\x00\x52\xec\xd3\xea\xbf\x5f\xe1\x41\x12\x20\x01\x12\xa4\x64\x27\x99\x92\xab\xf2\x30\x09\x34\xba\x1b\x8d\x7e\xa1\x01\xae\x56\x07\xe0\x71\x94\x84\x68\xf6\x01\x46\x08\x8c\x8f\xc0\x1c\xd2\x00\xce\xf0\xff\x21\x30\xfa\xdf\x57\x2f\x5e\x26\xf1\x04\x5f\x8f\xce\x83\x29\x8a\xa0\x68\xb2\x5e\x77\x45\xa7\xdb\xf0\x4a\xbe\xe4\x9d\xf2\xa6\xd9\xfb\x19\x8a\x5f\x26\xd1\x3c\xa1\x98\xa1\x63\xc6\x08\xe5\xed\x66\x28\xd6\x7a\x8e\x8c\x06\xf8\x6a\xc1\x10\xcd\xfa\x4f\x21\x2d\xf7\xbf\x66\x36\xc0\x4f\xb3\x4e\x73\x92\xfc\x0b\x05\x0c\x85\xa7\x71\x88\x6e\x11\xfd\x1d\xb3\xa9\x6d\x10\x4e\xa7\x67\x53\x8d\x50\x8d\xb4\x33\xff\x81\x04\xcd\xbe\xa3\x69\xd4\x37\x18\x42\xb1\xc5\xb7\x47\xce\x2f\xca\x08\x8e\xaf\xcf\x48\x32\x47\x84\x61\x44\x4f\x73\xd6\x0a\xc0\x95\xef\xed\x9c\x39\xaf\x01\x29\xd8\x51\x09\x57\xe3\x41\x1d\x30\x45\x78\x65\x33\x41\xed\x1c\x06\x37\xf0\x1a\x81\xf0\x2e\x86\x51\x12\x5e\x75\xbb\x38\x9a\x27\x84\x81\x7e\x17\x80\x5e\x90\xc4\x0c\xdd\xb2\x1e\xff\xff\x24\x12\xff\xca\x1f\x8e\x07\x9e\x80\x84\x34\x9a\x91\x5a\xcc\xd7\xeb\x6c\x84\x9e\x64\x05\x35\xc7\x44\x71\xc8\x1b\x71\x7c\x56\x2b\x30\x7a\x9f\x84\x8b\x19\x52\xcb\x6f\xb5\x1a\xbd\x49\x7e\x59\xb0\xf9\x82\x9d\x41\x36\x5d\xaf\x0f\xc5\xfa\xa5\xab\xd5\xe8\x37\x44\x28\x4e\xe2\xf3\xc5\x64\x82\x6f\xd7\xeb\x5e\xda\xff\x4c\x52\xaf\x00\x1c\xf2\x47\x39\x00\xa0\x1a\x5e\x63\x36\x5d\x5c\x8d\x82\x24\x3a\x84\x5f\x29\xff\x73\x40\xc3\x9b\x83\xeb\x84\xff\xb7\xc4\x92\x38\x61\xfa\x4a\x3e\x9e\xcd\x92\xaf\xbf\x2c\x11\xf9\x4a\x72\x0a\xab\x41\xf2\x3f\x88\x10\x2b\xe1\x95\x7d\x29\x22\x4b\x1c\xa0\xc3\x74\x2e\x6b\xb0\x2f\x36\xcf\xfe\x03\xd3\x19\x6b\x0b\x00\x4f\x60\x20\x3a\x2b\x9e\x30\x02\x63\x0a\x03\x86\x93\x98\x9e\xc4\xf0\x6a\x86\xc2\xc2\x22\x69\x36\x0e\xba\x9d\x13\x44\xf9\x8c\xa6\x83\x58\xd9\x73\x9d\x1c\x24\x73\x14\xc3\x39\x3e\xa4\x8c\x08\xf9\x1d\x74\xbb\x4b\x48\xc0\x67\x20\x96\xf0\x24\x62\xa3\x57\x90\xa1\x4f\x38\x42\xab\x75\xb7\x7b\x78\x08\x56\x2b\x5d\xed\xaf\xd7\x9f\x38\xb6\x80\x20\x3e\x20\x8a\x19\x05\x6c\x8a\xc0\x82\x22\x72\x10\x08\xe4\x17\x44\x34\x98\x67\xf2\x0c\x92\x89\x68\x53\x04\x04\x18\x6f\x38\xea\xb2\xbb\x79\xf9\xa5\x1c\x85\x32\xb2\x08\x18\x58\x75\x01\x78\x25\x48\x7d\xf5\xe2\xf8\xec\x34\x95\x02\x83\xb9\x23\xad\x41\x17\x80\x33\x82\x26\xf8\x16\xe8\x3f\x72\xf9\x74\x01\x10\xb0\xc5\x38\xe5\x77\x1f\x11\x0c\x5f\xc2\x39\x0c\x30\xbb\xfb\x35\xc6\x8c\x02\x80\x63\xf6\x5f\x7f\xee\x02\xf0\x3b\x97\x57\xf3\x9d\x7c\x25\xf9\x14\x86\x57\x45\x22\xce\x08\x8e\x20\xb9\x7b\x87\xee\x8a\xfc\x9a\xcb\x37\xe0\x06\xdd\x71\xf6\xc0\x32\x73\x70\x9c\x91\xac\x58\x54\x3d\x80\xc6\x2a\x3e\xfd\x87\xfb\x60\x96\x24\x73\x90\x2c\x11\x31\x46\x83\xb9\xf2\x81\x71\x08\x60\x18\x72\x84\x22\x00\x29\x98\x60\x34\x0b\x39\x51\x02\x45\x05\x70\xff\x50\x48\x11\x87\x49\x60\x7c\x8d\xc0\xe3\xcf\x43\xf0\x78\x7e\xc3\x75\xaa\xb6\xaa\x33\x5c\xdf\xa1\x3b\xe9\x02\x00\xd5\x4f\x73\x13\x1e\xcf\x6f\x46\x99\xf2\x4b\x09\x5d\xad\xc0\x75\xf2\xe9\x6e\x8e\x5e\x27\x24\x7b\xa9\x2f\x06\x6b\xaf\x7f\x66\x4b\x73\x39\xe6\x8a\xcb\xd6\xa8\xf7\x4f\x73\x2d\xac\xbb\xdd\xd5\x2a\xa5\xe2\x9a\x62\x17\x09\x6f\x66\xc9\x15\x9c\x9d\xa3\x20\x89\x43\x48\xee\x94\x0a\xe7\x10\xd2\x69\xce\x27\x61\xbd\x7e\x73\x7e\x6a\x12\x79\x4d\xf1\x48\xf4\x49\x71\x2d\xcc\x3d\xc7\xb6\xd4\xe6\xcd\xf9\xa9\x3e\xcd\xcd\x06\x30\xe6\x3e\x25\xf0\x46\x98\x3d\xd1\xb8\x34\x27\x07\x3a\xb8\x1b\xda\x66\x52\x6c\xbd\x4a\x93\x62\x69\x24\x26\x25\xd5\xe2\xf9\xcc\xe4\xff\x77\xac\x25\x9d\x8b\x96\xe5\x02\x29\xa0\x2c\x21\x28\xf4\x5b\x38\x3a\xcb\xa4\x4d\x1c\x15\x9b\x70\x71\x99\x2c\xe2\x00\xf4\x99\x5d\x3b\x0d\x40\x0c\x23\xd4\x1f\x28\xe5\x21\x60\x71\xd5\x3e\xca\xf5\xcb\xa3\x23\xd0\xeb\x89\x17\x00\x10\xc4\x16\x24\xd6\x5f\x77\x01\xe0\xd3\xa1\x5e\x70\xd5\x7b\x3e\x27\x38\x66\x93\x7e\xef\xc9\x13\x7a\xb0\x5a\x81\x10\xd2\x29\x22\x62\x96\xf4\xe1\x69\x6f\x08\xd8\x48\xaa\xb8\x81\x07\x9e\x01\x41\x90\xa1\x7e\xc0\x6e\x81\xf2\x5f\x46\x2f\xe5\xbf\x03\x80\x08\x49\x48\x8a\xfb\xe7\x21\xff\x9d\xcb\x0d\xd3\xf5\xe9\xe8\xa5\x00\x20\xa0\x49\x4f\x46\x74\xe6\x00\x87\x60\x2f\x9d\x74\xbd\xd5\x69\x3c\x5f\x30\x49\x77\x26\x00\xaf\xd0\x04\xc7\x58\x98\xbc\x31\xb8\xb8\xdc\xcf\xfa\x59\x5a\xac\xba\xc0\xf1\x23\xfc\x3d\x98\x7a\xf9\x50\x97\x2e\x6a\x5b\xcd\xba\x07\x65\x83\xa5\x56\x0b\x07\x24\xd6\x8b\x04\x9d\x75\xca\x11\x31\x04\x79\x0c\xe0\x57\x3a\x92\x9e\x5b\xbf\x97\xe1\xc4\xe5\x7b\x30\x2c\x77\xe1\x2b\xa9\xd8\x45\x99\xaf\x57\x2f\x2a\x97\x59\x09\xea\x7a\x58\x49\x4e\x66\xf6\xb3\x96\xd9\xe2\x37\x99\x9e\x3d\x3e\x99\xa1\x08\xc5\xac\x9a\xe3\x9a\xea\x57\x7a\xa5\x5e\xf5\x37\x63\x9f\x4d\x55\x0c\xdc\xb4\xea\xc8\xe1\x09\x40\x5f\x04\x84\x77\xe8\x8e\x73\x13\xf4\xde\x1e\x9f\xbf\xed\xe9\x53\xaf\xde\x8c\xd5\xaf\xda\xf0\x3a\x47\x78\x93\xb7\x90\x4e\x3d\x07\x46\x33\x8a\x6c\xa3\x7f\x3c\xfe\xf0\xe6\xa4\xe5\xf0\x1f\x39\xa7\x7d\xc7\xcf\x27\xbb\x85\x60\x14\x5b\xf0\xf0\xe8\xcd\xf9\xa9\x2d\xfa\xf6\x30\x8d\x8e\x89\x51\x61\x97\x80\xfb\x34\x6d\x67\x87\x62\xca\xa7\xad\x8d\x97\x8c\xb6\x32\xec\x0a\x4a\x06\x3f\x33\xaf\xa5\x55\x5b\xb2\xbe\xba\x94\xaa\xb0\x0f\x27\xf1\x58\xd3\x8b\xf9\x53\x9d\x80\xfc\xa9\x55\x3b\x88\x71\xf2\x36\x23\xb3\x79\x61\x5c\x6d\x46\x37\x5e\xee\x15\xac\xd5\x96\xbf\xc5\xad\xc8\x3a\x78\x83\xbe\x37\xad\x60\x62\xef\xa3\x1f\xc0\x7d\xe8\x08\x13\x0d\x7f\x6d\xd1\x0c\x19\x5f\x8d\x51\xc0\x46\xd7\x1d\xa0\x5a\x7f\x78\xf4\x5f\x1b\x8b\x60\x89\x79\x28\x8a\xc2\x4f\x53\x92\x2c\xae\xa7\xf3\x05\x2b\xac\x87\x72\x03\x5d\x6a\x4a\x41\xd8\x58\x32\xe0\x94\x87\x5b\x7d\x36\x2a\xbd\x37\xa8\x2f\xc7\x69\x63\xa3\x77\xf9\xbd\x6d\x25\x6d\xac\x4e\xb5\x16\x9b\x71\xa4\x3d\x37\x5a\x73\x42\x91\x94\x79\xaa\xc6\xca\x64\x23\xe9\xf7\x8a\xb6\xeb\xc1\xcf\xc2\x65\x7c\x74\x04\x62\x3c\x33\x5d\x5d\x44\x88\xe9\xe2\xc6\x78\xe6\xe1\xad\x52\xb8\x44\xc5\x57\x36\xdf\x75\x08\x22\x97\xef\xae\xbb\xb5\x21\x64\x30\x73\x6b\x51\x1c\x24\x61\x19\x7a\x34\x90\xfe\x6f\x3d\x25\xf9\xfc\xe2\x49\x6d\x56\x4b\xb9\xd3\x45\x6f\xfa\x6c\xc1\x4e\x19\x8a\x2a\x3c\x69\xd5\x42\xf3\xa2\x3d\xa6\x02\x00\xde\x47\x29\x0c\x41\xb5\x98\x9f\x6e\x81\x0a\x4d\x42\xb9\x42\xfa\x5e\x10\x05\xe0\x24\xcb\x61\x19\x2a\x9f\x8e\x41\x04\xe7\x17\x32\xc8\xba\xdc\x97\xff\xa6\x6b\xa3\x6d\x5a\x82\xff\xf4\xfe\x63\xb5\x02\x8b\xf9\x1c\x11\x7b\xf6\xa0\x6c\x92\x6d\xad\xb2\xc5\x66\xd7\x0a\x2f\x93\x38\x14\x11\x4d\x4e\x9d\x09\xd7\xa2\x65\x34\xa2\xb0\x27\x51\x07\xd6\x28\x27\x4d\xc0\x72\x40\xc2\xfb\x02\xc7\x1f\x5e\xa5\x61\xb7\xbd\x4b\x16\x50\x7d\x8e\x13\xf6\x19\xdd\x62\xca\x68\xbf\x9a\x51\x03\xc7\xc8\xee\x41\x14\xff\x07\xc3\x6e\x67\x3d\xe8\x76\x8a\x0b\xaf\xc3\x9f\x40\x44\xc8\x10\x24\x82\x76\x44\xc8\xa8\x2f\x73\xc0\xa3\x13\xbe\xb0\x07\x3f\xf3\x37\xbc\x65\x87\x7e\xc5\x2c\x98\x8a\xe6\xa3\x97\x49\xc8\xe3\x71\xf1\xbc\x13\x40\x9a\x27\xf1\x79\x37\xfe\x36\x9b\x0e\x38\x7b\x39\x45\xc1\xcd\x6b\x88\x67\x28\x3c\xb9\x0d\xd0\x5c\x78\x6d\xa2\x67\x47\xad\x17\xd9\xad\xa8\x2c\x8e\x67\x04\xc1\xf0\xee\x44\xb0\x46\x0e\xd5\xe9\xb4\x93\xc3\xb4\x6f\x4d\x8a\x6c\xcc\xa7\x0c\x4f\x00\xce\xb7\xb9\xea\xf2\x63\xab\x15\x08\xd2\xb6\xbf\xc1\xd9\xa2\xa6\x43\x2f\xea\xc9\x4e\x4a\x25\xac\x56\xb9\x20\x7c\x4a\xde\x73\xea\xbd\xa0\x8c\x52\x30\x62\x1d\x0c\x35\xe6\xa8\x95\x21\x7e\x5f\xbb\xe7\xe7\x23\xa2\xc9\x82\x04\xe8\x43\xc2\x5e\x27\x8b\xd8\x35\x33\x93\x88\x49\x41\x98\xf4\x7b\x22\x8b\x0c\x12\x02\x30\x77\xca\xc5\x6e\xc3\x84\x77\x1d\x83\x27\x4f\x64\xa2\x44\xa9\x9e\xae\x1a\x9a\xff\xd1\x34\x22\xff\x5d\xb3\x4f\x45\x09\x4e\xf3\x86\x56\x85\xff\x02\xb2\x60\xfa\x7b\xa6\xf1\x3d\x0c\xdb\x31\x21\xf0\xee\x97\x89\xa7\x7d\xa3\xe0\xe2\xb2\xd6\xc4\xf1\xd5\x32\x43\x71\x3f\xa2\x03\xf0\x77\x10\xc1\xdb\x54\xd8\x04\x76\x5c\xd5\x52\xb1\x24\x2c\xbc\xab\xc0\x08\x10\x14\x20\xbc\x44\x21\x78\xf2\x24\x04\x58\x40\x61\x89\xa0\x61\x08\xbe\x4e\x71\x30\x05\x98\x82\x6b\x91\xf2\x21\x80\x4d\xa1\xcc\x1a\x47\xf0\x16\x47\x8b\x08\x24\x13\xde\xaf\x37\x4c\x31\x1b\xda\x11\x1b\x70\xf6\x1b\x14\x1c\x1d\x81\xa7\x3a\xba\x7c\x4e\x44\x9b\x2b\xde\x89\x2f\xab\x08\xde\xa0\xbe\x1e\xdb\x88\x09\xf8\x88\xbe\x2c\x10\x65\xd9\x80\x83\x6e\x67\xc2\x85\x82\xf7\x90\x2b\x53\xb2\xc1\xcb\x1d\xa0\x17\xf8\x72\x20\xf5\x50\x41\x33\x19\x92\x23\x44\x49\xe0\x75\x81\x2f\xc1\x91\x66\x17\x75\x94\x44\xb7\xb3\x05\x53\xbf\x8e\x4d\xf3\xa9\x37\xea\x48\xbb\x28\x4d\x22\x97\xd6\xa1\x1c\x63\xdd\xed\xb0\x58\x6d\xba\xa7\xe2\x2c\xc9\x53\xca\x32\x59\x30\x47\xe6\x2e\x97\xd0\x1a\x4b\x6e\x36\x54\x06\xbd\xd3\xe9\x28\xfc\xc4\x6c\x19\x96\xd8\x35\x03\x92\x12\x81\xef\x18\x08\xe6\x68\xb4\x14\xbd\x45\xfb\x92\x36\x71\xe1\x0b\x79\xd9\x13\xf4\xf1\x39\x59\x67\x81\x54\xb2\x60\xa3\x5f\xe3\x39\x49\x02\x44\x29\x0a\xa5\xa4\x2b\xd0\x7b\x7b\x42\x10\x6c\x4d\x2e\x04\x6e\x97\x7c\xb1\x48\x49\x53\xa2\x75\x64\x05\xa8\x5a\xe7\x03\xcb\x1e\x04\xc1\x9b\x6c\x72\x1a\xb9\xb7\x21\x9a\x21\xf6\x9d\xe9\x81\x4a\x9c\x1c\x9a\x40\xf6\xd9\xb6\x2e\x00\xe0\x41\xb5\x41\xe7\x06\xdd\x65\x2b\xa7\xb4\xdb\x3b\x7a\x0f\x09\x9d\xc2\xd9\x7b\x38\xef\x57\xee\xbe\x71\x48\xad\x5d\x80\x07\xb1\xff\xaf\x49\x12\x89\xf9\xad\x33\xe1\x5c\xf5\xf5\x0a\xbe\x00\x77\xc7\xd2\x66\xa7\xf4\x2c\xc1\x31\x9f\xe7\x1a\x14\xf6\x33\x3f\x40\xc0\x1c\xd5\x92\x69\xf8\x0d\x86\xcf\xb0\xf6\xd3\xc5\xde\xca\xf8\x95\x10\x5d\x8b\x3e\x36\x5e\x48\x45\xf6\x0e\xdd\x8d\x01\x97\x92\x9d\x42\xfe\x41\x15\xb2\x5e\x25\x23\xa3\x38\xe1\xd0\x9a\x41\xa6\x50\x25\xe5\xc7\xaf\x13\x22\x72\xbd\x86\xb0\xd7\xad\xe9\x1a\x03\x70\x8d\x98\x97\xda\x2f\xef\xba\x1b\xbb\x5d\x42\xbd\xd8\x69\xc9\x76\x79\x97\x90\x08\x31\x2d\xf4\xf4\xda\xe0\x2d\x76\x19\x76\xcd\xb0\x6e\x00\xfa\xfb\x0e\x9b\x34\x94\x36\x49\x44\x62\xdf\x5e\xc1\x7e\x0f\xf1\x55\x29\xbc\xca\x66\xa6\x89\x22\xb4\x07\xcb\xb9\xb0\x0f\xb5\x80\x86\x3a\xf4\xcf\x1b\x54\x97\xd3\x51\x2d\x32\x8d\x23\x34\xa0\xfc\x51\x7a\xb0\x36\xc7\xd3\xe9\xbc\x4c\x62\x8a\x29\x43\x31\xfb\x88\x60\x28\xdb\xbd\x48\x92\x59\x9f\x91\x05\x4a\x03\x7f\xaf\x34\x9a\x27\xc5\x7a\x42\xeb\x21\x33\x0a\x9e\x11\xab\xc0\x75\x5b\x61\x6b\x99\xf0\xac\x08\x50\xf9\x4e\x04\xd1\x11\x9f\xc4\xcc\x83\xd2\x92\x99\xa2\xb7\x3d\xb7\x91\x12\x21\xdb\x6f\xa2\x7d\x8a\x95\x3f\x45\x6d\x22\xd6\x9c\x53\x3d\x0d\xbb\xb6\x8c\x9a\x48\xc1\x76\x01\xef\xe6\x4e\xfd\xe6\x89\x5c\xae\x70\x90\x35\xbe\x4b\xb9\x33\x04\x7b\x51\x45\xfe\x3a\x63\xb3\x1a\x57\x3d\xde\x8b\x86\xa9\xab\x2f\xf5\x46\x51\x7e\x95\xda\x12\x86\xe3\x3c\x80\xb1\x4f\x6a\x20\x80\x71\xf1\x15\xb5\x07\x03\x98\xaf\x4b\x3e\x7f\xe7\xb6\x3e\x62\xd5\x0e\xc1\x24\x06\x7c\xc4\x7e\x04\xdc\x3a\x7a\x06\x69\xc9\x10\x81\xab\x24\x99\x0d\xd4\xdf\x79\x0e\x9d\xa3\x27\x20\x73\xa6\xe6\x9a\xe2\x3c\x7d\x5c\x4a\xfd\xca\x5c\xa2\x33\xff\xeb\xd4\x0e\x8f\x04\x75\xa3\x57\x98\x72\x58\x66\x33\xd5\xf7\x1f\x38\xc2\x6c\x9c\xa7\x11\x65\x0f\xf1\x74\xa8\x92\xf4\x5c\x97\x8b\xa7\xe7\x0c\x12\x86\xe3\xeb\xe3\x09\x77\x58\xb5\x39\x56\x9a\x47\xac\x3d\xce\x59\x88\x63\xfa\x21\x89\xcb\x25\xba\x8d\x0c\x3f\x47\x07\xdd\x06\xb3\x05\xc5\x4b\x24\xc6\x7e\xe7\x6b\xfd\x2c\xf8\xca\xbc\xaa\x6d\x57\xc2\xa8\x19\x4a\x15\x8a\x9c\x2d\x91\xd1\xc0\xf1\x75\x8e\x07\xa0\x1c\xa8\xa8\xfd\xe3\x0e\x2a\x9f\xca\x54\xc7\x64\xda\x8f\xab\x99\x74\x89\x15\x56\xdd\xe1\x21\x88\x16\x94\x81\x39\x49\x96\x38\x44\x20\x89\x67\x77\x22\xba\x53\xd5\x82\x41\x12\x53\x86\xd9\x82\xa3\x2d\x9e\x0b\x9e\x8a\xae\x99\xd4\x8c\x4e\x8a\x4c\x01\x47\x46\x52\xbf\x5c\x0a\x24\x8c\xaa\x6d\x27\x59\x53\x4a\x8d\x2a\x51\xac\x99\x71\x97\xc5\xb7\x6c\x3b\xa7\x9b\x06\xae\xb2\x36\x3d\x8c\x70\x53\x71\x6e\x58\xcc\xd5\xca\x74\x22\xce\xe0\x75\x0d\x1e\x3d\x8b\x9c\xf4\xd6\xeb\xba\xea\x20\x73\xb7\xa7\x86\x0e\x9b\x10\x5f\x38\xdb\x5f\x56\x8c\x58\xdc\x62\x76\xbc\x5c\xab\x65\xcb\x12\x06\x67\x1f\x51\x90\x90\x90\x9e\xa5\x3e\x3f\x9f\x62\x51\x66\xdb\x7f\x3a\x50\xba\x1f\xc7\x31\x22\x27\x84\x48\x05\xd5\x05\x76\x47\x87\x2b\x27\xce\x50\x5a\x72\x75\x32\xb9\x1c\x4a\x25\x99\x2c\x18\xd8\x37\x94\x9a\x2c\x79\x97\x2a\x52\x4c\x4a\xae\x16\xd5\x12\x14\x69\x90\x61\xb5\x9d\xa1\x22\xda\x51\xb9\x0d\xf7\x5a\xce\xc8\x39\xb2\xac\x67\x01\x99\x2f\x2d\xc7\x8a\xcd\x55\x01\x9c\x51\x94\xb1\x13\x80\x42\xba\x03\xab\xf4\x8f\x1a\x72\xa2\xab\x4d\x54\x42\x49\xb7\xa1\x46\xc3\xd1\xef\x10\x0b\x2e\xda\xcc\x66\x89\x1c\x7d\xc3\xd1\x82\x68\x8e\x6c\xfe\x2f\xa6\xff\x80\x94\x09\xb7\x42\xd4\x21\xa5\x13\xb0\xb7\x07\x30\xf7\x65\xb8\x73\x23\x68\x19\x1c\x3c\xcb\x89\xa1\xd3\x64\x31\x0b\xf9\x24\xe3\x78\x21\x7c\x93\x49\xdc\xdf\x13\xed\x2e\xf0\xe5\x50\x87\x3a\xf8\x19\x3c\x2a\x34\xcf\xb1\xb7\xa0\x98\x22\x66\x15\xce\x3f\xfd\x49\xbd\x3d\x3c\x14\xa5\x9f\x53\x24\xcd\x13\x48\x26\x80\xc8\xb6\x60\x0a\x97\x08\x5c\x21\x14\x73\x77\x88\x0b\x34\x4b\xc0\x24\x1e\x82\x30\x89\x7f\x62\xe2\x19\x80\xf1\x5d\x94\x10\x94\x76\x19\x59\x67\x49\x0b\xa4\xed\x0b\xe5\xe8\x08\xec\xeb\xed\x3d\xc8\x5a\x1b\x95\xa9\x64\x81\xd2\x1d\x63\x31\xb4\x9a\x47\x9b\x6b\x94\xbe\x34\xf7\xf7\xf9\x7c\x97\xe2\xed\xd4\xcc\xbe\x85\x54\x54\xaa\x70\xed\x5f\xab\xb3\x45\xf1\x0f\x57\x8e\x8b\xf4\x0c\x4d\xea\x74\xe2\xec\x17\x3f\x20\x53\x48\xa7\x7c\xc8\xb4\x6b\x4d\xb7\xfc\xc0\x13\x49\x91\xf5\xec\xf9\xac\xd4\xd3\x3c\x79\x65\xcf\x2e\xa4\x55\x40\xde\x6e\x86\x56\x2e\x94\x16\xed\x7d\xb4\x8e\x27\x4a\xf8\x2c\xa8\x14\x18\x73\x3f\x48\x66\xf5\x55\x29\x8e\x6f\x6d\xa3\x09\x14\xcb\x78\x64\x47\xf4\xee\x3b\x51\x23\x46\x89\x93\x58\xf4\x2c\x0f\x93\xbe\xb1\x61\x61\x86\xfb\x96\x16\x1a\xed\x1f\x8a\x23\xe4\xc4\x3b\x06\x5f\xe7\x5b\x8e\xaa\x80\xd2\x02\x43\x95\x53\x5a\x23\x8b\xaa\x64\x13\x7d\x71\xc7\x1f\x95\x57\xd7\x7a\x7d\x06\x09\x45\xaf\xf1\x8c\x21\x42\xfb\x5f\x16\x88\x07\x31\x73\xc3\x3a\xfe\x4f\xf6\x50\x8b\x44\xde\x34\x1a\x44\xf4\x1e\x28\x7d\xc2\xcd\xd4\xe7\x21\x98\x88\x31\x65\x26\x25\xb7\x59\x42\x99\xbd\xce\x5f\xe5\x06\x4c\xc5\xe8\x5a\xb7\x82\x67\x62\x29\x10\x29\xc7\xaf\x76\xee\x67\x2e\x8b\x8c\xf7\xaf\x4a\xa1\x53\x4d\x64\x9b\xa9\xdd\x9c\x81\x23\x57\x6d\xcb\x85\x5e\x84\x52\x84\xd4\xbb\x04\x47\xba\xbb\x48\x55\xfd\x5f\x53\x8c\x06\x03\x63\x57\x5b\xbc\x7f\xa5\x15\x9f\xe7\xc1\x89\x47\x31\xba\xc5\x27\xd6\xc0\xbe\x49\x52\x80\x0d\xb3\x8b\x19\x28\xe1\xb7\x0c\xf3\xfd\x86\x82\x4c\x58\x67\xbc\x20\x1c\xc0\xac\xfb\x2c\x91\xdb\x3b\x2f\x55\x5b\x16\x9a\x2b\x32\x7a\x85\x13\x71\xa5\x6e\x75\x73\x2c\x11\xbb\x30\x8e\x76\x8c\x9f\x3c\xa1\x9f\x97\xfc\x85\xdc\x14\x6b\x3d\xab\x43\x80\x07\xe6\x46\x47\x5d\xf4\x64\x46\x1f\x2c\x91\x5c\xe1\x94\xa9\x47\x26\xd7\x47\xfd\xd5\x4a\x63\x07\x17\xa4\x52\x99\x69\x99\x8d\x66\x9c\xf1\x07\xe0\x53\x91\x29\x12\x11\x2f\x56\x14\x03\xa0\x62\x19\x70\x59\x34\x3f\x94\x64\xec\x07\xe2\xdc\x07\x83\x6f\x39\x4e\x8d\xe5\xaa\x19\xcf\x5e\xfc\xc8\x3c\x7b\x31\x06\x75\xdc\xa9\xe1\x4d\xa1\x10\xdb\x5e\x8e\xa8\x7b\xfc\xa6\x8b\xbe\x4d\xff\xa1\x26\x63\xd9\xc2\x4f\xd8\x7a\x2a\xd3\x74\xab\x2c\xae\xf3\xb3\x94\x65\x78\x02\x8a\x69\x39\x3d\x08\x73\x67\x18\x07\x55\x69\xba\x97\x30\x06\x74\x8e\x02\x3c\xb9\x93\xb9\xb4\x24\x46\x3c\x5c\xd4\xc6\x48\x08\x30\x13\x3c\x85\xe4\x9c\xa6\x60\xb3\x10\xd1\x94\xca\xd4\xed\x2f\xa5\x6b\x1a\x52\xd2\x86\x90\xb6\xf8\x88\xad\x90\x72\x72\xcb\x91\x98\xac\xf2\xeb\xb6\x1e\xc4\xa4\xeb\xe9\x71\xb8\x35\xaf\x49\x73\x38\x42\xa7\x6f\xe2\x98\xdd\xd2\x66\xee\x91\x76\xcc\xd4\x31\x5b\x3c\xfe\x12\x39\x60\x2f\x80\x01\x8c\xe3\x84\x81\x2b\x04\x50\x34\x67\x77\xbd\x81\xa1\x43\x4a\x13\x91\xfd\xa2\x85\x0c\xc6\x46\x41\x1e\x35\x34\x28\x12\x6f\x53\x0f\xae\xbb\xd3\x2a\xb0\xf4\x29\xec\x76\x36\x35\xcf\x43\x38\x6d\xc9\xb8\x49\x36\xbb\x37\x5e\xad\x40\x00\x23\x24\x99\x5f\x81\xa6\xbf\xc7\x62\x9a\x84\x76\x72\xea\x42\xa4\x66\xb0\x5a\x29\xae\xec\xea\xca\xc0\xb7\x40\x06\x58\xf2\xeb\x55\xbb\xf4\xf6\x21\x64\x86\xbd\x07\x4a\x76\xd7\x46\x40\xea\x97\x68\xd5\x51\x27\xf1\x22\xda\x3e\x25\xca\x1d\xb1\x2d\x5e\x17\xfc\xb2\x83\xea\xc0\xbf\x21\x2a\x8d\x70\xf0\x40\xc1\xb5\x49\x60\xe3\x73\x59\xd0\x4c\x7f\xd9\xe5\x80\x4e\xfa\x3d\xe1\xd5\x6d\x8f\x7f\xae\xe3\xb7\xea\x1f\xb9\x33\x1a\xa2\xdb\xd7\x09\xf9\x0a\x89\x6d\x8b\x13\xd1\x00\xc5\x21\x8f\x24\xec\x7b\xa2\xa0\xc1\xae\xa8\xb1\xe9\x69\x64\x8b\xa5\xaa\xd0\xfc\x61\xf9\xd6\x48\xeb\x77\x73\xfb\xea\xe3\x19\x95\x77\x57\x19\xb7\x40\xe6\x3e\x80\x29\x5d\x1b\xb8\x29\x76\xc8\x39\xe7\x35\xd2\xde\xa1\x3b\xcb\x71\x1b\x33\x87\xe2\x63\x20\xc0\x11\xf0\x54\xd1\xdc\x2e\x66\x55\x68\xa0\x45\xca\xc7\xc9\x81\x62\xee\x47\x98\x29\x77\xeb\x81\xc7\xe8\x2a\xec\x29\x98\x9f\x6a\x0c\xaa\x2d\x90\xbf\x2f\x5d\xa7\x54\xaa\x35\xb5\x03\xc7\xe2\x66\x68\xb5\xd6\xae\xd7\x77\xcd\xb3\x58\x4e\xe6\xd5\x8d\xd0\x2a\xfd\xd6\x66\xb4\x06\x39\x30\x67\xc7\xba\x6c\x58\x6d\x5a\x69\xbf\xe5\xe2\x77\xab\xe0\xe2\x64\x9a\x28\xb4\x1e\xaf\x72\xb8\x2a\x43\xd5\x24\xb1\x53\x65\xa4\x84\x95\xba\x47\x76\x79\xe4\x50\x5e\x8c\xdb\xea\xea\xcd\xb8\x67\xd6\x03\x68\x36\x23\xb7\x94\x99\x15\xf8\x96\x4a\x5f\x9c\xa1\xf4\x51\xe1\xe0\x6f\x45\x90\x75\xfa\xdb\x30\x25\x3f\x0a\x95\x7f\x6f\x4c\xa5\x77\x95\x56\xc1\xa2\x6d\x54\x43\x54\x63\x40\xeb\x83\xad\x66\xc6\xae\xc6\xbc\x95\x2a\x7d\x3c\x4d\x9c\x48\x89\xe8\x56\xce\x6e\xd5\xee\xc7\x92\xdd\xb7\xf5\x6a\x64\xaf\x5a\xed\xd8\xa8\x4e\x35\x87\x59\xfc\xec\x6c\x9d\xd5\x3b\x63\xc4\x56\xd5\xe7\xab\x56\x4d\x65\xee\x72\x5e\x6a\x4d\xef\x76\x31\x88\x43\xfb\x76\x46\xf6\xb4\x28\xf6\x1b\x30\x7b\x7f\xb5\x12\xe3\x6d\x42\x81\x85\x80\xa2\xc0\xf9\x6f\xc7\xd4\xda\xed\x2d\xf1\xba\xd9\x86\x47\x66\xae\x5b\x8d\x3b\x74\x85\x55\x55\x17\x4c\x55\xa7\xcc\x7c\xd2\x56\xa6\x2e\x2d\xd7\x86\x14\xe2\x86\xa6\xca\xb4\x2a\xb3\x53\xa1\x4b\xab\x62\x84\xe6\x1a\xd5\x33\xef\xb3\x99\x52\xad\x1d\xe4\xfe\x75\xaa\xaf\x56\xf5\xe4\xc7\x76\xd4\xaa\x6f\x52\xc7\x3d\xe1\x5b\xd0\xac\x8d\x90\x70\xa9\xd6\x6d\x64\x17\xad\xb9\x44\x1f\xe5\xec\x82\xee\xaf\x9b\xfd\x78\xe0\x67\xde\xbe\x31\xc6\x0f\x6b\x4c\xf6\xb7\xc3\x5b\x6f\xb5\x9e\x9d\xae\xf1\xad\x3b\xcb\xce\x1a\x95\xcb\xb4\xe4\x49\xce\x6c\x67\x51\xbe\xd2\xc2\x96\x47\xc6\x7e\x15\x1b\x6d\xa5\x48\x4d\xd9\xdf\x41\x39\x78\x2a\x8d\x7f\x54\xce\x67\x17\xdb\x0c\x80\x6d\xdf\xb1\x41\x91\xfa\x1c\x5e\xa3\xd7\x71\x5e\xa5\xde\x91\x0f\x44\x6d\xf2\x22\x0e\x24\xe2\xb2\xce\xbc\x58\x5e\x57\x5d\x7d\xde\xe9\xf8\xcf\x90\x28\x4c\xfe\x25\x9e\xdd\x01\x48\xe9\x22\x42\x00\xc6\x72\x47\x4f\xa0\x07\x22\x04\x63\x0a\xe2\x04\xa8\xea\x63\xba\x98\x31\xaa\x0a\x99\x09\x02\x90\x20\xfe\x52\x96\x5c\x51\x00\xe7\xf3\x19\x46\x61\xbe\x37\xd9\x77\xcd\xbe\x38\x73\xf6\xef\x7f\xbb\xa6\x5f\x6c\x57\x0e\xd2\x13\xc1\x1a\x23\x54\xad\x7c\x7e\xea\xbf\x53\xa8\x5d\xe6\x0f\xb4\x3d\x48\x43\x63\x28\x69\xdc\x00\x5c\x7a\xdc\xdc\xaf\xb6\xbf\x3c\x50\xba\x28\x2c\x85\xf1\xb9\x34\xe8\x55\xf1\x96\xd2\xfd\x4e\x67\x0a\xe9\x7b\x3e\x1d\xe3\x23\x59\x91\xdd\x29\xde\x5c\x80\xb3\x9b\x1c\xc4\x4d\x09\xa9\x90\x1c\xc9\xf6\xea\xac\x62\x0a\xe5\x08\x60\xf0\x37\xb3\x6a\x3e\xbd\x81\x07\x4f\xc0\x23\xb3\x4c\xfe\x91\xea\x95\x1e\x78\x2c\x30\xab\xf3\x87\xad\x85\x37\x4e\x57\x4a\xae\xaf\xbb\xdd\x8e\xf5\x5c\x89\x58\xa2\xf6\x83\x25\xba\x32\x92\x33\xbe\xf9\x21\xd7\x1f\xe4\x7c\xeb\xb6\x6f\x64\xd2\x17\x24\x9e\x68\xeb\xa7\xcc\xa1\xec\x9d\x98\xb4\xc2\x65\x83\xb5\x95\x4e\xc5\x8b\x59\xfe\x18\x47\xf3\xb3\x3b\x61\x76\xc7\xef\x1f\xe8\xf8\x7d\x2a\xb8\x8e\xab\x14\xe5\xcd\x22\x35\x27\xef\xf3\x46\x1b\x1d\xbe\xdf\xc2\xd1\xfa\x9d\xd6\x29\xde\xea\xb4\xe1\x97\x23\xbc\x0e\x34\x95\x2e\x55\xd6\x8f\x29\x1b\xe7\xa7\xac\x2d\x2d\x67\x9d\xcc\x76\x95\x87\x9b\xcc\xa6\xdb\x3b\xcd\x64\xc2\xfd\x5e\x8e\x2f\x15\xb0\xda\xce\x79\x25\xed\xdc\x7f\xdb\xe3\x6a\x0a\xc6\xdb\xb6\xa7\xd6\xcc\xfe\x0f\x74\xb8\xeb\xac\x62\x50\xf5\x4d\x33\x47\x03\xd7\xcd\x08\xbb\xfa\x63\x7b\x9d\xcd\xfd\x16\xed\x36\xae\xc6\xdd\x55\x22\x6f\xab\x12\xd9\xd4\x47\xbb\xda\xe3\x6f\x5c\x7b\xdc\xe2\xfb\x0f\xbb\x72\xe5\x5d\xb9\xf2\xae\x5c\x79\x57\xae\xbc\x2b\x57\x16\xb9\x35\xaf\xba\xe4\x5d\x61\xf2\xae\x30\xf9\x3e\x0a\x93\x37\x2b\x4d\xfe\x06\xc5\xc9\x9e\xfa\xed\xfe\x2b\x94\x1f\xba\x46\xb9\x7d\x91\x72\x9b\x7a\x85\x6f\x50\xa9\xec\x51\x26\xb5\xf5\x72\x65\x3f\x53\x75\x3f\x1b\xd6\xf7\xc4\xba\x66\xb5\x50\x5b\x2b\x5d\xf6\xe2\xe4\xae\x7c\x79\x57\xbe\xbc\x2b\x5f\xde\x95\x2f\xef\xca\x97\x77\xe5\xcb\xbb\xf2\xe5\x5d\xf9\xf2\xae\x7c\x79\x57\xbe\xbc\x2b\x5f\xde\x95\x2f\xef\xca\x97\x1f\xae\x7c\x39\xcf\xd0\x37\xbe\x6b\xd6\x50\x65\x6a\xa7\xfa\x63\x9b\xcb\x5f\xab\x00\xdd\xcf\x4e\x7c\xf9\xd3\xd1\x85\x2d\xf9\x8a\xda\x0a\x37\x82\x45\x70\xc2\xfc\x9e\x52\xb1\x8d\x37\x3e\x52\x57\xa3\xd1\x93\x2f\x0b\x38\x2b\x81\x29\xcc\xa6\x9f\x86\x4f\x47\x10\x40\x1a\x0f\xe1\xed\x99\xc7\x09\x33\x89\xa9\x6e\x22\xb1\xd1\xda\xc8\x6f\xc0\x56\x22\xd3\xca\x81\x70\xcf\xd5\x86\x8e\x44\x35\xae\x1b\x38\x14\xcd\xdd\x89\x3a\xb6\xd9\xc1\xb7\x72\x2a\x1a\x0c\xd5\x30\xf9\xd8\xd2\xc1\xf0\x75\x31\x1a\xf1\xa8\x2c\x0f\x6d\x9d\x8d\xba\x61\xcb\x09\xba\xaa\x5c\xf5\x16\x1c\x8f\x96\x08\x15\x5c\x10\x7f\xef\xc8\xc7\x30\xd7\xe1\xe4\x6f\xa0\x6b\xa9\xb3\x7a\x57\x65\xb9\x6d\x7a\x8f\xe8\x16\xe2\xbf\x66\x13\xe3\x72\x32\x9c\xd6\xbb\xda\xd0\x17\xe7\x38\xab\x02\xac\xb1\x4c\x6f\xad\x16\xa8\xd6\x6a\x68\xe0\xab\xcd\x92\x03\x7e\x8d\xe1\xd3\xed\x8d\x46\x44\xc5\xfb\x82\x39\xd2\x8d\x91\x1d\x85\x4d\x6c\xd1\xd6\x63\xda\x4a\x4c\x1f\x38\xb4\xad\xe1\xda\x56\x23\x5c\xdf\xb1\xbe\x97\xe4\x61\x13\xde\x6c\xd5\xfc\xb8\x46\x7c\xb0\x44\x62\x73\x3c\xee\x21\x9d\x58\x83\x44\x63\x2b\xe3\xa4\xe6\xbb\x8b\x06\x1b\xb0\xbf\x61\x50\x58\x34\x1a\xb6\x4f\xca\x7d\x07\xa7\x3a\x7d\xce\x2f\x16\x0e\xe4\xed\x0e\x2b\xee\x0e\x2b\xee\x0e\x2b\x7e\x57\xc7\x86\x36\x3f\xac\xa8\xd8\x26\xdc\x0e\xf9\x85\xbb\x9a\xb3\x3c\x37\xf4\xb8\xad\x77\x62\xfb\xce\x5d\xe3\xf3\x18\x0d\x8f\x63\x68\x9a\x38\x3b\x85\x67\xc1\xc3\xe3\x18\xa4\xa5\x97\xdf\x47\x89\x7f\xfc\x62\x71\xfb\x97\xfb\x8a\xa3\xdb\x5a\x3d\x40\x89\x78\xfb\xcf\x23\x16\x7f\x34\x47\xd7\x22\xe5\x05\xbf\xb8\xe8\x6d\x55\xcb\x57\x7d\xb9\xac\xe6\xfe\xd8\x06\x6f\x56\xca\xab\x9f\x39\xb5\x61\xb3\x49\xf5\xae\xbd\xba\x66\xec\x2a\xae\xb1\x2d\xb6\x62\x11\x8c\x53\x72\xb8\xa5\xf9\xa2\xbb\x33\x6e\xb3\x53\x61\x71\x76\xdf\x7e\xde\xfa\xb7\x9f\x7d\xfc\xc7\xfa\x0f\x40\xbb\x75\xb9\xf9\x69\xe8\xa2\x74\x6a\x8e\xbc\x45\x74\xc6\xd5\xe2\x7f\x60\x49\x23\xae\xf5\x8f\x3d\x1b\xa2\x69\x9c\xb1\xdb\xe0\x23\xd0\x25\x46\x5d\x3c\xbd\x1c\x82\x3d\xe7\x58\xbe\x1f\x8a\xce\xbf\x13\xed\x04\x35\xb4\x5c\x4e\xa0\x6f\x5f\x89\x95\x40\xcf\x03\x18\xd3\xd2\x21\x31\x7d\x4e\x5a\x7d\x55\xba\xf5\xe9\x49\xeb\xf7\xa6\x1f\xfa\xf8\xe4\x0f\xf4\x25\x6a\xc3\x31\x28\xa2\x51\xe1\x1d\xf8\x15\x17\xea\x9b\x8c\xc7\x71\x98\x9d\x39\xcf\x3c\x44\xe1\x3a\x2e\x62\x9c\xc4\xda\xa3\xea\xfd\xc4\xf2\xb1\x72\x73\x5b\xd5\xff\x4b\xd9\xd5\x88\xa9\xf5\xfe\x47\xff\x54\x76\xed\x57\xb2\x01\x8c\x43\xf1\x9b\x62\x17\x1f\x39\x85\x34\x65\x6c\x4e\xc7\x87\x87\x94\xc1\xe0\x26\x59\x22\x32\x99\x25\x5f\x47\x41\x12\x1d\x7e\x59\x20\xca\x8d\x0c\x3d\xfc\xf3\xd3\xbf\xfe\xe5\x2f\xff\xf9\xd7\xff\x3e\x4c\xf9\x75\x30\x87\xd7\x38\x86\xfc\xed\xc1\x57\xcc\xa6\xe2\xaf\x8c\x3c\x41\xdd\x0d\xba\x3b\x48\xe2\x03\x78\x70\x2d\xee\x2a\x38\xa0\xe9\xfc\x1c\x3c\xf8\x87\xbb\x2b\x85\xc4\x51\x6f\x5d\x73\x0e\x6f\xf7\xe1\xee\xdd\x87\xbb\x77\x1f\xee\xde\x7d\xb8\x7b\xf7\xe1\xee\xcd\x3e\xdc\x6d\xf1\x4c\x19\x81\x31\x85\x81\xb0\x3c\x27\x31\x77\x8a\x42\x5d\xe5\xa9\x4e\x4a\xc3\xa7\x8d\xf9\x12\x96\x0a\x5d\x2c\x2d\xf3\xda\x1c\x09\xe5\x93\x06\x37\xcb\x69\xe9\xfd\xdf\x67\x2b\x73\x7c\xa4\x07\x1a\x8e\x31\xea\xdd\xe2\xb4\xe3\x39\x5c\x96\x96\xfe\x71\x1c\xf2\x47\xf6\xf1\x5d\x2e\x72\xf4\xcc\x15\x88\xf0\x77\x59\x6c\x4e\xc1\x3e\xca\xc2\xf3\x51\xf6\xf8\xc5\x02\xcf\x42\x44\x86\x20\x7a\xae\x83\x71\xa1\xc0\xdb\x79\x82\xd4\x1d\xe7\x10\x32\xf8\x2c\xd3\x7b\xc2\xf7\x29\xc7\x44\xd1\xb3\x41\xd7\xae\xed\x34\x41\x51\x81\x99\x24\xec\xe4\x76\xce\x31\x7f\xc6\xff\xfd\x0d\xce\x68\xfa\x7f\x91\xcd\xca\x86\xbb\xe2\xf8\xa4\xcd\xfb\x3a\x4b\xfc\xc7\xe3\x04\x3c\xb7\x10\xe0\x9c\xaa\xe8\x79\x03\x62\x9e\x6b\xc4\x3c\xd7\x88\x79\x5e\x4b\xcc\xf3\xe6\xc4\x74\x70\x39\x82\x49\x97\xc1\xef\x04\xcb\x2b\xc0\x68\x76\x07\x58\xfa\x4a\x3c\x1d\x83\x0b\xcd\xed\x2a\xf5\x12\xd9\x0a\x99\xb2\x38\x5b\x30\xdd\xb7\x39\x4b\x83\x23\xf9\x53\x7f\x8d\x18\xff\xe1\x20\x55\xf0\x22\x05\x48\x3e\xb6\xa6\x9b\xe4\x8f\x26\x16\xb2\x6d\x55\x8a\x31\x17\x1b\x67\x5b\x95\x16\x35\xc4\x4a\x34\x16\xd7\xb3\xa9\xbf\xab\x09\xee\xb8\x2e\x4d\x2b\xec\x54\xd2\x83\x2a\x79\x92\x89\x99\x33\x82\x26\xf8\xd6\xc5\x9f\xe7\x1e\xfc\x79\xde\x80\x3f\xcf\x1b\xf0\xe7\xb9\x9b\x3f\xfc\xaf\x35\x00\x5d\x00\x1c\xb7\xd3\x95\x85\xaf\xe4\x7b\xa9\xcb\x75\x2b\x6d\x46\x66\x32\x0e\x0f\x1d\x1a\x46\x3d\xa6\x00\x96\x94\x33\x80\xfc\x69\x8a\x15\x8f\x32\xb8\x31\xce\x22\x40\x20\x3e\xce\x4b\x47\x52\xb9\xbb\xf4\x97\x4b\x0f\x0f\x40\xdf\x23\x6c\xd1\xb6\x08\x0a\xf5\x37\x46\xb8\xab\x02\x58\xc5\x87\xa6\xb7\x2a\xa6\x9f\xce\x32\x9f\x8e\x41\x34\x94\x36\xdc\x74\xf9\x97\x70\xd6\xfa\xfe\xc6\xba\x91\x2a\x95\x55\x21\xa7\x95\x79\x4b\x11\xbc\x41\x80\x2e\x08\xca\x2b\x7e\xf2\x49\xa2\xca\x39\x0a\x64\xa2\x00\x50\x34\x87\x04\xb2\x84\x80\x60\x0a\x09\x0c\x18\x22\xd4\xd8\xc6\x7f\x4c\x19\x39\x23\xc9\x1c\x11\x86\x11\x3d\xcd\xd3\x0a\x34\xaf\xb7\x72\xbc\x37\x7d\x0f\x1d\xa8\x76\x4b\x25\x45\x73\x01\x28\x21\x0c\x85\xef\xd0\x1d\xad\x1a\xd1\x0e\x26\x23\x93\xf7\xd2\xb6\xe3\xdc\x70\xc4\xa8\x69\x12\x52\xd5\x8c\x8d\xd2\xdc\x49\x7f\xb5\xca\xf9\xf5\x29\x11\x1a\xa6\x7c\xa9\x80\x39\x68\x2f\x1a\xf5\x84\xe5\x17\xc1\x9c\x84\xde\x1b\x58\xe6\x4b\x0f\x43\xd2\xbc\x5c\x0e\x26\xbf\x5c\x29\x9d\x9f\x9f\x72\x78\x3f\xa5\x99\x8e\xf6\xf8\x0d\x0c\x59\xa9\x2b\xff\x3b\x3c\x04\x30\x0c\x01\x8e\xad\x82\x54\x2c\x8c\x32\xf2\x4f\x35\xc9\xa7\xfa\x82\xff\x79\x76\xb3\xe9\x56\x2e\x47\xb5\x08\x9e\xe7\xf5\xa8\x6a\x81\x7e\xf3\x0b\x52\xa3\xd2\x0d\xa9\xf5\x32\x60\x81\x32\xea\x99\xf7\xa7\x82\xc2\x8f\x21\x00\x0d\x35\x10\x8f\x8b\x6f\x86\x60\x99\x87\xc6\xf9\x2c\xaa\x8e\x4b\x38\xbb\xb8\xb9\x04\x47\x60\xe9\x23\x89\x9b\x5c\xe7\xb9\x81\x74\x5a\x73\xac\xda\x46\x41\x31\x29\xdc\x5a\x42\xdf\x9c\x9f\x9a\xa2\x55\x84\x6c\x15\x5c\x2a\x05\xd7\x82\x65\x51\x52\x69\x73\x49\xb5\x74\xa9\x96\xd4\x52\x87\x56\x92\x6a\x81\xf2\xb0\x92\x5a\x39\xc1\xad\x84\x37\x7b\xa4\x50\x48\xfd\x04\xdd\x21\x93\x8e\x98\x3d\xc5\x25\x83\xe0\x19\x64\x76\x5f\x8c\xb2\x84\x20\xa1\x9c\x33\x87\x8c\x25\xf6\x86\x64\x11\x30\xe5\x95\x39\x76\xda\x22\x9f\x94\xf1\x10\x88\x6c\x9e\xd3\x7f\xcb\x23\xd9\x25\x24\xc0\x22\xee\xb6\x67\xe6\x3e\x60\x69\xfd\xfc\x1a\x47\xf9\x0a\x8a\x86\x60\xcf\x02\xa2\x62\xdb\x2f\x9f\xf1\x7d\x8e\xfb\x91\x0d\x03\xdb\xd6\xa4\xa1\x3a\xd4\x59\xac\xe4\x5f\x28\x48\x37\xd0\xd2\xcc\x67\x51\x8d\xd0\xcc\x70\xce\x21\xa1\x0e\x07\x6c\x42\x92\x08\x64\xf0\x40\x96\xde\x97\x3a\x8a\xcf\x76\x1c\x82\x09\x9e\xcd\x24\x24\x1c\x4b\x97\x99\x77\x51\xbe\x8c\x4d\x49\x9a\x08\xba\xd0\x93\x49\xfd\x06\xa4\x94\xc6\xb9\xb1\x01\xf1\x04\xd1\x6a\x5b\x40\xb9\xa1\x01\x14\xb9\x47\x1c\x87\x35\xda\xbb\x0a\x12\x9e\x80\x65\x5a\x64\x10\x55\x24\xde\x45\xb9\xc1\xde\x1e\x58\x8e\xce\x4d\xb9\x9a\x43\xc2\x34\xc7\x97\x8e\xce\xe7\x33\xcc\xfa\xfb\xcb\xd1\xb9\xf2\xfd\x02\x38\x3a\xcf\x7c\xea\xec\x4a\x12\xb5\xf5\x2f\xba\x0f\x38\xc8\xd5\x4a\xca\x55\x00\x47\xb9\x8f\x9a\xab\x1a\xfb\xa6\xd8\xed\x5c\x8a\x8c\xb3\xb3\x80\x3f\x06\x3f\x3d\x79\x42\x7f\xea\x0d\x01\x47\x6b\xa0\x65\x23\x8b\xea\x53\x4d\x29\x1e\x8a\xf9\x14\xae\x73\x09\xa6\xe8\x98\x94\x6e\xec\x99\x2b\x77\x95\x53\xe2\x53\x34\x2d\x5a\xef\xa9\xe2\x68\x81\xe6\x05\xe7\x16\x06\xeb\xf5\x65\x8d\x6a\xf7\x56\xb4\x8d\xd4\xb1\xba\x34\xdc\xa9\x7e\xa9\xa9\x7f\x67\x98\x8a\xd4\x73\xb9\x99\x4b\x11\xd3\x19\x0e\x90\xad\x8b\xd2\xc8\xb4\x52\x25\xd3\x7e\x44\xc1\xc5\xa5\x87\x5a\x1e\x80\xfe\xc5\xa5\x4f\x45\x9d\xb3\xd8\x41\x1e\x78\x85\x37\xa8\x0a\x10\x17\xdf\x88\x8a\xad\x11\xb1\xff\x31\x04\x51\x6e\x3d\xb3\xfd\x8f\xb6\xb5\x20\x1e\xd5\x20\x51\xd3\xd2\x0f\x9b\xf1\xcf\x2f\xa9\x75\xf1\xe2\x02\x5f\x0a\xa1\x76\xbc\x37\xb3\xf2\x6e\x30\x69\x01\xc9\xff\x07\x00\x00\xff\xff\xc7\x2a\x96\x5b\x94\xc9\x00\x00") +var _tableGoTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x3d\x6b\x73\xdb\x38\x92\x9f\xa5\x5f\x81\xe8\x12\x8f\xe4\x95\xe5\x24\xb7\x75\xb7\xab\x59\x6f\x95\x93\x38\x89\x2b\x9b\x8c\x2f\xce\xcc\x5c\x95\xcb\x95\x85\x49\xc8\xc2\x5a\x24\x15\x00\x52\xec\xd3\xea\xbf\x5f\xe1\x41\x12\x20\x01\x12\xa4\x64\x27\x99\x92\xab\xf2\x30\x09\x34\xba\x1b\x8d\x7e\xa1\x01\xae\x56\x07\xe0\x71\x94\x84\x68\xf6\x01\x46\x08\x8c\x8f\xc0\x1c\xd2\x00\xce\xf0\xff\x21\x30\xfa\xdf\x57\x2f\x5e\x26\xf1\x04\x5f\x8f\xce\x83\x29\x8a\xa0\x68\xb2\x5e\x77\x45\xa7\xdb\xf0\x4a\xbe\xe4\x9d\xf2\xa6\xd9\xfb\x19\x8a\x5f\x26\xd1\x3c\xa1\x98\xa1\x63\xc6\x08\xe5\xed\x66\x28\xd6\x7a\x8e\x8c\x06\xf8\x6a\xc1\x10\xcd\xfa\x4f\x21\x2d\xf7\xbf\x66\x36\xc0\x4f\xb3\x4e\x73\x92\xfc\x0b\x05\x0c\x85\xa7\x71\x88\x6e\x11\xfd\x1d\xb3\xa9\x6d\x10\x4e\xa7\x67\x53\x8d\x50\x8d\xb4\x33\xff\x81\x04\xcd\xbe\xa3\x69\xd4\x37\x18\x42\xb1\xc5\xb7\x47\xce\x2f\xca\x08\x8e\xaf\xcf\x48\x32\x47\x84\x61\x44\x4f\x73\xd6\x0a\xc0\x95\xef\xed\x9c\x39\xaf\x01\x29\xd8\x51\x09\x57\xe3\x41\x1d\x30\x45\x78\x65\x33\x41\xed\x1c\x06\x37\xf0\x1a\x81\xf0\x2e\x86\x51\x12\x5e\x75\xbb\x38\x9a\x27\x84\x81\x7e\x17\x80\x5e\x90\xc4\x0c\xdd\xb2\x1e\xff\xff\x24\x12\xff\xca\x1f\x8e\x07\x9e\x80\x84\x34\x9a\x91\x5a\xcc\xd7\xeb\x6c\x84\x9e\x64\x05\x35\xc7\x44\x71\xc8\x1b\x71\x7c\x56\x2b\x30\x7a\x9f\x84\x8b\x19\x52\xcb\x6f\xb5\x1a\xbd\x49\x7e\x59\xb0\xf9\x82\x9d\x41\x36\x5d\xaf\x0f\xc5\xfa\xa5\xab\xd5\xe8\x37\x44\x28\x4e\xe2\xf3\xc5\x64\x82\x6f\xd7\xeb\x5e\xda\xff\x4c\x52\xaf\x00\x1c\xf2\x47\x39\x00\xa0\x1a\x5e\x63\x36\x5d\x5c\x8d\x82\x24\x3a\x84\x5f\x29\xff\x73\x40\xc3\x9b\x83\xeb\x84\xff\xb7\xc4\x92\x38\x61\xfa\x4a\x3e\x9e\xcd\x92\xaf\xbf\x2c\x11\xf9\x4a\x72\x0a\xab\x41\xf2\x3f\x88\x10\x2b\xe1\x95\x7d\x29\x22\x4b\x1c\xa0\xc3\x74\x2e\x6b\xb0\x2f\x36\xcf\xfe\x03\xd3\x19\x6b\x0b\x00\x4f\x60\x20\x3a\x2b\x9e\x30\x02\x63\x0a\x03\x86\x93\x98\x9e\xc4\xf0\x6a\x86\xc2\xc2\x22\x69\x36\x0e\xba\x9d\x13\x44\xf9\x8c\xa6\x83\x58\xd9\x73\x9d\x1c\x24\x73\x14\xc3\x39\x3e\xa4\x8c\x08\xf9\x1d\x74\xbb\x4b\x48\xc0\x67\x20\x96\xf0\x24\x62\xa3\x57\x90\xa1\x4f\x38\x42\xab\x75\xb7\x7b\x78\x08\x56\x2b\x5d\xed\xaf\xd7\x9f\x38\xb6\x80\x20\x3e\x20\x8a\x19\x05\x6c\x8a\xc0\x82\x22\x72\x10\x08\xe4\x17\x44\x34\x98\x67\xf2\x0c\x92\x89\x68\x53\x04\x04\x18\x6f\x38\xea\xb2\xbb\x79\xf9\xa5\x1c\x85\x32\xb2\x08\x18\x58\x75\x01\x78\x25\x48\x7d\xf5\xe2\xf8\xec\x34\x95\x02\x83\xb9\x23\xad\x41\x17\x80\x33\x82\x26\xf8\x16\xe8\x3f\x72\xf9\x74\x01\x10\xb0\xc5\x38\xe5\x77\x1f\x11\x0c\x5f\xc2\x39\x0c\x30\xbb\xfb\x35\xc6\x8c\x02\x80\x63\xf6\x5f\x7f\xee\x02\xf0\x3b\x97\x57\xf3\x9d\x7c\x25\xf9\x14\x86\x57\x45\x22\xce\x08\x8e\x20\xb9\x7b\x87\xee\x8a\xfc\x9a\xcb\x37\xe0\x06\xdd\x71\xf6\xc0\x32\x73\x70\x9c\x91\xac\x58\x54\x3d\x80\xc6\x2a\x3e\xfd\x87\xfb\x60\x96\x24\x73\x90\x2c\x11\x31\x46\x83\xb9\xf2\x81\x71\x08\x60\x18\x72\x84\x22\x00\x29\x98\x60\x34\x0b\x39\x51\x02\x45\x05\x70\xff\x50\x48\x11\x87\x49\x60\x7c\x8d\xc0\xe3\xcf\x43\xf0\x78\x7e\xc3\x75\xaa\xb6\xaa\x33\x5c\xdf\xa1\x3b\xe9\x02\x00\xd5\x4f\x73\x13\x1e\xcf\x6f\x46\x99\xf2\x4b\x09\x5d\xad\xc0\x75\xf2\xe9\x6e\x8e\x5e\x27\x24\x7b\xa9\x2f\x06\x6b\xaf\x7f\x66\x4b\x73\x39\xe6\x8a\xcb\xd6\xa8\xf7\x4f\x73\x2d\xac\xbb\xdd\xd5\x2a\xa5\xe2\x9a\x62\x17\x09\x6f\x66\xc9\x15\x9c\x9d\xa3\x20\x89\x43\x48\xee\x94\x0a\xe7\x10\xd2\x69\xce\x27\x61\xbd\x7e\x73\x7e\x6a\x12\x79\x4d\xf1\x48\xf4\x49\x71\x2d\xcc\x3d\xc7\xb6\xd4\xe6\xcd\xf9\xa9\x3e\xcd\xcd\x06\x30\xe6\x3e\x25\xf0\x46\x98\x3d\xd1\xb8\x34\x27\x07\x3a\xb8\x1b\xda\x66\x52\x6c\xbd\x4a\x93\x62\x69\x24\x26\x25\xd5\xe2\xf9\xcc\xe4\xff\x77\xac\x25\x9d\x8b\x96\xe5\x02\x29\xa0\x2c\x21\x28\xf4\x5b\x38\x3a\xcb\xa4\x4d\x1c\x15\x9b\x70\x71\x99\x2c\xe2\x00\xf4\x99\x5d\x3b\x0d\x40\x0c\x23\xd4\x1f\x28\xe5\x21\x60\x71\xd5\x3e\xca\xf5\xcb\xa3\x23\xd0\xeb\x89\x17\x00\x10\xc4\x16\x24\xd6\x5f\x77\x01\xe0\xd3\xa1\x5e\x70\xd5\x7b\x3e\x27\x38\x66\x93\x7e\xef\xc9\x13\x7a\xb0\x5a\x81\x10\xd2\x29\x22\x62\x96\xf4\xe1\x69\x6f\x08\xd8\x48\xaa\xb8\x81\x07\x9e\x01\x41\x90\xa1\x7e\xc0\x6e\x81\xf2\x5f\x46\x2f\xe5\xbf\x03\x80\x08\x49\x48\x8a\xfb\xe7\x21\xff\x9d\xcb\x0d\xd3\xf5\xe9\xe8\xa5\x00\x20\xa0\x49\x4f\x46\x74\xe6\x00\x87\x60\x2f\x9d\x74\xbd\xd5\x69\x3c\x5f\x30\x49\x77\x26\x00\xaf\xd0\x04\xc7\x58\x98\xbc\x31\xb8\xb8\xdc\xcf\xfa\x59\x5a\xac\xba\xc0\xf1\x23\xfc\x3d\x98\x7a\xf9\x50\x97\x2e\x6a\x5b\xcd\xba\x07\x65\x83\xa5\x56\x0b\x07\x24\xd6\x8b\x04\x9d\x75\xca\x11\x31\x04\x79\x0c\xe0\x57\x3a\x92\x9e\x5b\xbf\x97\xe1\xc4\xe5\x7b\x30\x2c\x77\xe1\x2b\xa9\xd8\x45\x99\xaf\x57\x2f\x2a\x97\x59\x09\xea\x7a\x58\x49\x4e\x66\xf6\xb3\x96\xd9\xe2\x37\x99\x9e\x3d\x3e\x99\xa1\x08\xc5\xac\x9a\xe3\x9a\xea\x57\x7a\xa5\x5e\xf5\x37\x63\x9f\x4d\x55\x0c\xdc\xb4\xea\xc8\xe1\x09\x40\x5f\x04\x84\x77\xe8\x8e\x73\x13\xf4\xde\x1e\x9f\xbf\xed\xe9\x53\xaf\xde\x8c\xd5\xaf\xda\xf0\x3a\x47\x78\x93\xb7\x90\x4e\x3d\x07\x46\x33\x8a\x6c\xa3\x7f\x3c\xfe\xf0\xe6\xa4\xe5\xf0\x1f\x39\xa7\x7d\xc7\xcf\x27\xbb\x85\x60\x14\x5b\xf0\xf0\xe8\xcd\xf9\xa9\x2d\xfa\xf6\x30\x8d\x8e\x89\x51\x61\x97\x80\xfb\x34\x6d\x67\x87\x62\xca\xa7\xad\x8d\x97\x8c\xb6\x32\xec\x0a\x4a\x06\x3f\x33\xaf\xa5\x55\x5b\xb2\xbe\xba\x94\xaa\xb0\x0f\x27\xf1\x58\xd3\x8b\xf9\x53\x9d\x80\xfc\xa9\x55\x3b\x88\x71\xf2\x36\x23\xb3\x79\x61\x5c\x6d\x46\x37\x5e\xee\x15\xac\xd5\x96\xbf\xc5\xad\xc8\x3a\x78\x83\xbe\x37\xad\x60\x62\xef\xa3\x1f\xc0\x7d\xe8\x08\x13\x0d\x7f\x6d\xd1\x0c\x19\x5f\x8d\x51\xc0\x46\xd7\x1d\xa0\x5a\x7f\x78\xf4\x5f\x1b\x8b\x60\x89\x79\x28\x8a\xc2\x4f\x53\x92\x2c\xae\xa7\xf3\x05\x2b\xac\x87\x72\x03\x5d\x6a\x4a\x41\xd8\x58\x32\xe0\x94\x87\x5b\x7d\x36\x2a\xbd\x37\xa8\x2f\xc7\x69\x63\xa3\x77\xf9\xbd\x6d\x25\x6d\xac\x4e\xb5\x16\x9b\x71\xa4\x3d\x37\x5a\x73\x42\x91\x94\x79\xaa\xc6\xca\x64\x23\xe9\xf7\x8a\xb6\xeb\xc1\xcf\xc2\x65\x7c\x74\x04\x62\x3c\x33\x5d\x5d\x44\x88\xe9\xe2\xc6\x78\xe6\xe1\xad\x52\xb8\x44\xc5\x57\x36\xdf\x75\x08\x22\x97\xef\xae\xbb\xb5\x21\x64\x30\x73\x6b\x51\x1c\x24\x61\x19\x7a\x34\x90\xfe\x6f\x3d\x25\xf9\xfc\xe2\x49\x6d\x56\x4b\xb9\xd3\x45\x6f\xfa\x6c\xc1\x4e\x19\x8a\x2a\x3c\x69\xd5\x42\xf3\xa2\x3d\xa6\x02\x00\xde\x47\x29\x0c\x41\xb5\x98\x9f\x6e\x81\x0a\x4d\x42\xb9\x42\xfa\x5e\x10\x05\xe0\x24\xcb\x61\x19\x2a\x9f\x8e\x41\x04\xe7\x17\x32\xc8\xba\xdc\x97\xff\xa6\x6b\xa3\x6d\x5a\x82\xff\xf4\xfe\x63\xb5\x02\x8b\xf9\x1c\x11\x7b\xf6\xa0\x6c\x92\x6d\xad\xb2\xc5\x66\xd7\x0a\x2f\x93\x38\x14\x11\x4d\x4e\x9d\x09\xd7\xa2\x65\x34\xa2\xb0\x27\x51\x07\xd6\x28\x27\x4d\xc0\x72\x40\xc2\xfb\x02\xc7\x1f\x5e\xa5\x61\xb7\xbd\x4b\x16\x50\x7d\x8e\x13\xf6\x19\xdd\x62\xca\x68\xbf\x9a\x51\x03\xc7\xc8\xee\x41\x14\xff\x07\xc3\x6e\x67\x3d\xe8\x76\x8a\x0b\xaf\xc3\x9f\x40\x44\xc8\x10\x24\x82\x76\x44\xc8\xa8\x2f\x73\xc0\xa3\x13\xbe\xb0\x07\x3f\xf3\x37\xbc\x65\x87\x7e\xc5\x2c\x98\x8a\xe6\xa3\x97\x49\xc8\xe3\x71\xf1\xbc\x13\x40\x9a\x27\xf1\x79\x37\xfe\x36\x9b\x0e\x38\x7b\x39\x45\xc1\xcd\x6b\x88\x67\x28\x3c\xb9\x0d\xd0\x5c\x78\x6d\xa2\x67\x47\xad\x17\xd9\xad\xa8\x2c\x8e\x67\x04\xc1\xf0\xee\x44\xb0\x46\x0e\xd5\xe9\xb4\x93\xc3\xb4\x6f\x4d\x8a\x6c\xcc\xa7\x0c\x4f\x00\xce\xb7\xb9\xea\xf2\x63\xab\x15\x08\xd2\xb6\xbf\xc1\xd9\xa2\xa6\x43\x2f\xea\xc9\x4e\x4a\x25\xac\x56\xb9\x20\x7c\x4a\xde\x73\xea\xbd\xa0\x8c\x52\x30\x62\x1d\x0c\x35\xe6\xa8\x95\x21\x7e\x5f\xbb\xe7\xe7\x23\xa2\xc9\x82\x04\xe8\x43\xc2\x5e\x27\x8b\xd8\x35\x33\x93\x88\x49\x41\x98\xf4\x7b\x22\x8b\x0c\x12\x02\x30\x77\xca\xc5\x6e\xc3\x84\x77\x1d\x83\x27\x4f\x64\xa2\x44\xa9\x9e\xae\x1a\x9a\xff\xd1\x34\x22\xff\x5d\xb3\x4f\x45\x09\x4e\xf3\x86\x56\x85\xff\x02\xb2\x60\xfa\x7b\xa6\xf1\x3d\x0c\xdb\x31\x21\xf0\xee\x97\x89\xa7\x7d\xa3\xe0\xe2\xb2\xd6\xc4\xf1\xd5\x32\x43\x71\x3f\xa2\x03\xf0\x77\x10\xc1\xdb\x54\xd8\x04\x76\x5c\xd5\x52\xb1\x24\x2c\xbc\xab\xc0\x08\x10\x14\x20\xbc\x44\x21\x78\xf2\x24\x04\x58\x40\x61\x89\xa0\x61\x08\xbe\x4e\x71\x30\x05\x98\x82\x6b\x91\xf2\x21\x80\x4d\xa1\xcc\x1a\x47\xf0\x16\x47\x8b\x08\x24\x13\xde\xaf\x37\x4c\x31\x1b\xda\x11\x1b\x70\xf6\x1b\x14\x1c\x1d\x81\xa7\x3a\xba\x7c\x4e\x44\x9b\x2b\xde\x89\x2f\xab\x08\xde\xa0\xbe\x1e\xdb\x88\x09\xf8\x88\xbe\x2c\x10\x65\xd9\x80\x83\x6e\x67\xc2\x85\x82\xf7\x90\x2b\x53\xb2\xc1\xcb\x1d\xa0\x17\xf8\x72\x20\xf5\x50\x41\x33\x19\x92\x23\x44\x49\xe0\x75\x81\x2f\xc1\x91\x66\x17\x75\x94\x44\xb7\xb3\x05\x53\xbf\x8e\x4d\xf3\xa9\x37\xea\x48\xbb\x28\x4d\x22\x97\xd6\xa1\x1c\x63\xdd\xed\xb0\x58\x6d\xba\xa7\xe2\x2c\xc9\x53\xca\x32\x59\x30\x47\xe6\x2e\x97\xd0\x1a\x4b\x6e\x36\x54\x06\xbd\xd3\xe9\x28\xfc\xc4\x6c\x19\x96\xd8\x35\x03\x92\x12\x81\xef\x18\x08\xe6\x68\xb4\x14\xbd\x45\xfb\x92\x36\x71\xe1\x0b\x79\xd9\x13\xf4\xf1\x39\x59\x67\x81\x54\xb2\x60\xa3\x5f\xe3\x39\x49\x02\x44\x29\x0a\xa5\xa4\x2b\xd0\x7b\x7b\x42\x10\x6c\x4d\x2e\x04\x6e\x97\x7c\xb1\x48\x49\x53\xa2\x75\x64\x05\xa8\x5a\xe7\x03\xcb\x1e\x04\xc1\x9b\x6c\x72\x1a\xb9\xb7\x21\x9a\x21\xf6\x9d\xe9\x81\x4a\x9c\x1c\x9a\x40\xf6\xd9\xb6\x2e\x00\xe0\x41\xb5\x41\xe7\x06\xdd\x65\x2b\xa7\xb4\xdb\x3b\x7a\x0f\x09\x9d\xc2\xd9\x7b\x38\xef\x57\xee\xbe\x71\x48\xad\x5d\x80\x07\xb1\xff\xaf\x49\x12\x89\xf9\xad\x33\xe1\x5c\xf5\xf5\x0a\xbe\x00\x77\xc7\xd2\x66\xa7\xf4\x2c\xc1\x31\x9f\xe7\x1a\x14\xf6\x33\x3f\x40\xc0\x1c\xd5\x92\x69\xf8\x0d\x86\xcf\xb0\xf6\xd3\xc5\xde\xca\xf8\x95\x10\x5d\x8b\x3e\x36\x5e\x48\x45\xf6\x0e\xdd\x8d\x01\x97\x92\x9d\x42\xfe\x41\x15\xb2\x5e\x25\x23\xa3\x38\xe1\xd0\x9a\x41\xa6\x50\x25\xe5\xc7\xaf\x13\x22\x72\xbd\x86\xb0\xd7\xad\xe9\x1a\x03\x70\x8d\x98\x97\xda\x2f\xef\xba\x1b\xbb\x5d\x42\xbd\xd8\x69\xc9\x76\x79\x97\x90\x08\x31\x2d\xf4\xf4\xda\xe0\x2d\x76\x19\x76\xcd\xb0\x6e\x00\xfa\xfb\x0e\x9b\x34\x94\x36\x49\x44\x62\xdf\x5e\xc1\x7e\x0f\xf1\x55\x29\xbc\xca\x66\xa6\x89\x22\xb4\x07\xcb\xb9\xb0\x0f\xb5\x80\x86\x3a\xf4\xcf\x1b\x54\x97\xd3\x51\x2d\x32\x8d\x23\x34\xa0\xfc\x51\x7a\xb0\x36\xc7\xd3\xe9\xbc\x4c\x62\x8a\x29\x43\x31\xfb\x88\x60\x28\xdb\xbd\x48\x92\x59\x9f\x91\x05\x4a\x03\x7f\xaf\x34\x9a\x27\xc5\x7a\x42\xeb\x21\x33\x0a\x9e\x11\xab\xc0\x75\x5b\x61\x6b\x99\xf0\xac\x08\x50\xf9\x4e\x04\xd1\x11\x9f\xc4\xcc\x83\xd2\x92\x99\xa2\xb7\x3d\xb7\x91\x12\x21\xdb\x6f\xa2\x7d\x8a\x95\x3f\x45\x6d\x22\xd6\x9c\x53\x3d\x0d\xbb\xb6\x8c\x9a\x48\xc1\x76\x01\xef\xe6\x4e\xfd\xe6\x89\x5c\xae\x70\x90\x35\xbe\x4b\xb9\x33\x04\x7b\x51\x45\xfe\x3a\x63\xb3\x1a\x57\x3d\xde\x8b\x86\xa9\xab\x2f\xf5\x46\x51\x7e\x95\xda\x12\x86\xe3\x3c\x80\xb1\x4f\x6a\x20\x80\x71\xf1\x15\xb5\x07\x03\x98\xaf\x4b\x3e\x7f\xe7\xb6\x3e\x62\xd5\x0e\xc1\x24\x06\x7c\xc4\x7e\x04\xdc\x3a\x7a\x06\x69\xc9\x10\x81\xab\x24\x99\x0d\xd4\xdf\x79\x0e\x9d\xa3\x27\x20\x73\xa6\xe6\x9a\xe2\x3c\x7d\x5c\x4a\xfd\xca\x5c\xa2\x33\xff\xeb\xd4\x0e\x8f\x04\x75\xa3\x57\x98\x72\x58\x66\x33\xd5\xf7\x1f\x38\xc2\x6c\x9c\xa7\x11\x65\x0f\xf1\x74\xa8\x92\xf4\x5c\x97\x8b\xa7\xe7\x0c\x12\x86\xe3\xeb\xe3\x09\x77\x58\xb5\x39\x56\x9a\x47\xac\x3d\xce\x59\x88\x63\xfa\x21\x89\xcb\x25\xba\x8d\x0c\x3f\x47\x07\xdd\x06\xb3\x05\xc5\x4b\x24\xc6\x7e\xe7\x6b\xfd\x2c\xf8\xca\xbc\xaa\x6d\x57\xc2\xa8\x19\x4a\x15\x8a\x9c\x2d\x91\xd1\xc0\xf1\x75\x8e\x07\xa0\x1c\xa8\xa8\xfd\xe3\x0e\x2a\x9f\xca\x54\xc7\x64\xda\x8f\xab\x99\x74\x89\x15\x56\xdd\xe1\x21\x88\x16\x94\x81\x39\x49\x96\x38\x44\x20\x89\x67\x77\x22\xba\x53\xd5\x82\x41\x12\x53\x86\xd9\x82\xa3\x2d\x9e\x0b\x9e\x8a\xae\x99\xd4\x8c\x4e\x8a\x4c\x01\x47\x46\x52\xbf\x5c\x0a\x24\x8c\xaa\x6d\x27\x59\x53\x4a\x8d\x2a\x51\xac\x99\x71\x97\xc5\xb7\x6c\x3b\xa7\x9b\x06\xae\xb2\x36\x3d\x8c\x70\x53\x71\x6e\x58\xcc\xd5\xca\x74\x22\xce\xe0\x75\x0d\x1e\x3d\x8b\x9c\xf4\xd6\xeb\xba\xea\x20\x73\xb7\xa7\x86\x0e\x9b\x10\x5f\x38\xdb\x5f\x56\x8c\x58\xdc\x62\x76\xbc\x5c\xab\x65\xcb\x12\x06\x67\x1f\x51\x90\x90\x90\x9e\xa5\x3e\x3f\x9f\x62\x51\x66\xdb\x7f\x3a\x50\xba\x1f\xc7\x31\x22\x27\x84\x48\x05\xd5\x05\x76\x47\x87\x2b\x27\xce\x50\x5a\x72\x75\x32\xb9\x1c\x4a\x25\x99\x2c\x18\xd8\x37\x94\x9a\x2c\x79\x97\x2a\x52\x4c\x4a\xae\x16\xd5\x12\x14\x69\x90\x61\xb5\x9d\xa1\x22\xda\x51\xb9\x0d\xf7\x5a\xce\xc8\x39\xb2\xac\x67\x01\x99\x2f\x2d\xc7\x8a\xcd\x55\x01\x9c\x51\x94\xb1\x13\x80\x42\xba\x03\xab\xf4\x8f\x1a\x72\xa2\xab\x4d\x54\x42\x49\xb7\xa1\x46\xc3\xd1\xef\x10\x0b\x2e\xda\xcc\x66\x89\x1c\x7d\xc3\xd1\x82\x68\x8e\x6c\xfe\x2f\xa6\xff\x80\x94\x09\xb7\x42\xd4\x21\xa5\x13\xb0\xb7\x07\x30\xf7\x65\xb8\x73\x23\x68\x19\x1c\x3c\xcb\x89\xa1\xd3\x64\x31\x0b\xf9\x24\xe3\x78\x21\x7c\x93\x49\xdc\xdf\x13\xed\x2e\xf0\xe5\x50\x87\x3a\xf8\x19\x3c\x2a\x34\xcf\xb1\xb7\xa0\x98\x22\x66\x15\xce\x3f\xfd\x49\xbd\x3d\x3c\x14\xa5\x9f\x53\x24\xcd\x13\x48\x26\x80\xc8\xb6\x60\x0a\x97\x08\x5c\x21\x14\x73\x77\x88\x0b\x34\x4b\xc0\x24\x1e\x82\x30\x89\x7f\x62\xe2\x19\x80\xf1\x5d\x94\x10\x94\x76\x19\x59\x67\x49\x0b\xa4\xed\x0b\xe5\xe8\x08\xec\xeb\xed\x3d\xc8\x5a\x1b\x95\xa9\x64\x81\xd2\x1d\x63\x31\xb4\x9a\x47\x9b\x6b\x94\xbe\x34\xf7\xf7\xf9\x7c\x97\xe2\xed\xd4\xcc\xbe\x85\x54\x54\xaa\x70\xed\x5f\xab\xb3\x45\xf1\x0f\x57\x8e\x8b\xf4\x0c\x4d\xea\x74\xe2\xec\x17\x3f\x20\x53\x48\xa7\x7c\xc8\xb4\x6b\x4d\xb7\xfc\xc0\x13\x49\x91\xf5\xec\xf9\xac\xd4\xd3\x3c\x79\x65\xcf\x2e\xa4\x55\x40\xde\x6e\x86\x56\x2e\x94\x16\xed\x7d\xb4\x8e\x27\x4a\xf8\x2c\xa8\x14\x18\x73\x3f\x48\x66\xf5\x55\x29\x8e\x6f\x6d\xa3\x09\x14\xcb\x78\x64\x47\xf4\xee\x3b\x51\x23\x46\x89\x93\x58\xf4\x2c\x0f\x93\xbe\xb1\x61\x61\x86\xfb\x96\x16\x1a\xed\x1f\x8a\x23\xe4\xc4\x3b\x06\x5f\xe7\x5b\x8e\xaa\x80\xd2\x02\x43\x95\x53\x5a\x23\x8b\xaa\x64\x13\x7d\x71\xc7\x1f\x95\x57\xd7\x7a\x7d\x06\x09\x45\xaf\xf1\x8c\x21\x42\xfb\x5f\x16\x88\x07\x31\x73\xc3\x3a\xfe\x4f\xf6\x50\x8b\x44\xde\x34\x1a\x44\xf4\x1e\x28\x7d\xc2\xcd\xd4\xe7\x21\x98\x88\x31\x65\x26\x25\xb7\x59\x42\x99\xbd\xce\x5f\xe5\x06\x4c\xc5\xe8\x5a\xb7\x82\x67\x62\x29\x10\x29\xc7\xaf\x76\xee\x67\x2e\x8b\x8c\xf7\xaf\x4a\xa1\x53\x4d\x64\x9b\xa9\xdd\x9c\x81\x23\x57\x6d\xcb\x85\x5e\x84\x52\x84\xd4\xbb\x04\x47\xba\xbb\x48\x55\xfd\x5f\x53\x8c\x06\x03\x63\x57\x5b\xbc\x7f\xa5\x15\x9f\xe7\xc1\x89\x47\x31\xba\xc5\x27\xd6\xc0\xbe\x49\x52\x80\x0d\xb3\x8b\x19\x28\xe1\xb7\x0c\xf3\xfd\x86\x82\x4c\x58\x67\xbc\x20\x1c\xc0\xac\xfb\x2c\x91\xdb\x3b\x2f\x55\x5b\x16\x9a\x2b\x32\x7a\x85\x13\x71\xa5\x6e\x75\x73\x2c\x11\xbb\x30\x8e\x76\x8c\x9f\x3c\xa1\x9f\x97\xfc\x85\xdc\x14\x6b\x3d\xab\x43\x80\x07\xe6\x46\x47\x5d\xf4\x64\x46\x1f\x2c\x91\x5c\xe1\x94\xa9\x47\x26\xd7\x47\xfd\xd5\x4a\x63\x07\x17\xa4\x52\x99\x69\x99\x8d\x66\x9c\xf1\x07\xe0\x53\x91\x29\x12\x11\x2f\x56\x14\x03\xa0\x62\x19\x70\x59\x34\x3f\x94\x64\xec\x07\xe2\xdc\x07\x83\x6f\x39\x4e\x8d\xe5\xaa\x19\xcf\x5e\xfc\xc8\x3c\x7b\x31\x06\x75\xdc\xa9\xe1\x4d\xa1\x10\xdb\x5e\x8e\xa8\x7b\xfc\xa6\x8b\xbe\x4d\xff\xa1\x26\x63\xd9\xc2\x4f\xd8\x7a\x2a\xd3\x74\xab\x2c\xae\xf3\xb3\x94\x65\x78\x02\x8a\x69\x39\x3d\x08\x73\x67\x18\x07\x55\x69\xba\x97\x30\x06\x74\x8e\x02\x3c\xb9\x93\xb9\xb4\x24\x46\x3c\x5c\xd4\xc6\x48\x08\x30\x13\x3c\x85\xe4\x9c\xa6\x60\xb3\x10\xd1\x94\xca\xd4\xed\x2f\xa5\x6b\x1a\x52\xd2\x86\x90\xb6\xf8\x88\xad\x90\x72\x72\xcb\x91\x98\xac\xf2\xeb\xb6\x1e\xc4\xa4\xeb\xe9\x71\xb8\x35\xaf\x49\x73\x38\x42\xa7\x6f\xe2\x98\xdd\xd2\x66\xee\x91\x76\xcc\xd4\x31\x5b\x3c\xfe\x12\x39\x60\x2f\x80\x01\x8c\xe3\x84\x81\x2b\x04\x50\x34\x67\x77\xbd\x81\xa1\x43\x4a\x13\x91\xfd\xa2\x85\x0c\xc6\x46\x41\x1e\x35\x34\x28\x12\x6f\x53\x0f\xae\xbb\xd3\x2a\xb0\xf4\x29\xec\x76\x36\x35\xcf\x43\x38\x6d\xc9\xb8\x49\x36\xbb\x37\x5e\xad\x40\x00\x23\x24\x99\x5f\x81\xa6\xbf\xc7\x62\x9a\x84\x76\x72\xea\x42\xa4\x66\xb0\x5a\x29\xae\xec\xea\xca\xc0\xb7\x40\x06\x58\xf2\xeb\x55\xbb\xf4\xf6\x21\x64\x86\xbd\x07\x4a\x76\xd7\x46\x40\xea\x97\x68\xd5\x51\x27\xf1\x22\xda\x3e\x25\xca\x1d\xb1\x2d\x5e\x17\xfc\xb2\x83\xea\xc0\xbf\x21\x2a\x8d\x70\xf0\x40\xc1\xb5\x49\x60\xe3\x73\x59\xd0\x4c\x7f\xd9\xe5\x80\x4e\xfa\x3d\xe1\xd5\x6d\x8f\x7f\xae\xe3\xb7\xea\x1f\xb9\x33\x1a\xa2\xdb\xd7\x09\xf9\x0a\x89\x6d\x8b\x13\xd1\x00\xc5\x21\x8f\x24\xec\x7b\xa2\xa0\xc1\xae\xa8\xb1\xe9\x69\x64\x8b\xa5\xaa\xd0\xfc\x61\xf9\xd6\x48\xeb\x77\x73\xfb\xea\xe3\x19\x95\x77\x57\x19\xb7\x40\xe6\x3e\x80\x29\x5d\x1b\xb8\x29\x76\xc8\x39\xe7\x35\xd2\xde\xa1\x3b\xcb\x71\x1b\x33\x87\xe2\x63\x20\xc0\x11\xf0\x54\xd1\xdc\x2e\x66\x55\x68\xa0\x45\xca\xc7\xc9\x81\x62\xee\x47\x98\x29\x77\xeb\x81\xc7\xe8\x2a\xec\x29\x98\x9f\x6a\x0c\xaa\x2d\x90\xbf\x2f\x5d\xa7\x54\xaa\x35\xb5\x03\xc7\xe2\x66\x68\xb5\xd6\xae\xd7\x77\xcd\xb3\x58\x4e\xe6\xd5\x8d\xd0\x2a\xfd\xd6\x66\xb4\x06\x39\x30\x67\xc7\xba\x6c\x58\x6d\x5a\x69\xbf\xe5\xe2\x77\xab\xe0\xe2\x64\x9a\x28\xb4\x1e\xaf\x72\xb8\x2a\x43\xd5\x24\xb1\x53\x65\xa4\x84\x95\xba\x47\x76\x79\xe4\x50\x5e\x8c\xdb\xea\xea\xcd\xb8\x67\xd6\x03\x68\x36\x23\xb7\x94\x99\x15\xf8\x96\x4a\x5f\x9c\xa1\xf4\x51\xe1\xe0\x6f\x45\x90\x75\xfa\xdb\x30\x25\x3f\x0a\x95\x7f\x6f\x4c\xa5\x77\x95\x56\xc1\xa2\x6d\x54\x43\x54\x63\x40\xeb\x83\xad\x66\xc6\xae\xc6\xbc\x95\x2a\x7d\x3c\x4d\x9c\x48\x89\xe8\x56\xce\x6e\xd5\xee\xc7\x92\xdd\xb7\xf5\x6a\x64\xaf\x5a\xed\xd8\xa8\x4e\x35\x87\x59\xfc\xec\x6c\x9d\xd5\x3b\x63\xc4\x56\xd5\xe7\xab\x56\x4d\x65\xee\x72\x5e\x6a\x4d\xef\x76\x31\x88\x43\xfb\x76\x46\xf6\xb4\x28\xf6\x1b\x30\x7b\x7f\xb5\x12\xe3\x6d\x42\x81\x85\x80\xa2\xc0\xf9\x6f\xc7\xd4\xda\xed\x2d\xf1\xba\xd9\x86\x47\x66\xae\x5b\x8d\x3b\x74\x85\x55\x55\x17\x4c\x55\xa7\xcc\x7c\xd2\x56\xa6\x2e\x2d\xd7\x86\x14\xe2\x86\xa6\xca\xb4\x2a\xb3\x53\xa1\x4b\xab\x62\x84\xe6\x1a\xd5\x33\xef\xb3\x99\x52\xad\x1d\xe4\xfe\x75\xaa\xaf\x56\xf5\xe4\xc7\x76\xd4\xaa\x6f\x52\xc7\x3d\xe1\x5b\xd0\xac\x8d\x90\x70\xa9\xd6\x6d\x64\x17\xad\xb9\x44\x1f\xe5\xec\x82\xee\xaf\x9b\xfd\x78\xe0\x67\xde\xbe\x31\xc6\x0f\x6b\x4c\xf6\xb7\xc3\x5b\x6f\xb5\x9e\x9d\xae\xf1\xad\x3b\xcb\xce\x1a\x95\xcb\xb4\xe4\x49\xce\x6c\x67\x51\xbe\xd2\xc2\x96\x47\xc6\x7e\x15\x1b\x6d\xa5\x48\x4d\xd9\xdf\x41\x39\x78\x2a\x8d\x7f\x54\xce\x67\x17\xdb\x0c\x80\x6d\xdf\xb1\x41\x91\xfa\x1c\x5e\xa3\xd7\x71\x5e\xa5\xde\x91\x0f\x44\x6d\xf2\x22\x0e\x24\xe2\xb2\xce\xbc\x58\x5e\x57\x5d\x7d\xde\xe9\xf8\xcf\x90\x28\x4c\xfe\x25\x9e\xdd\x01\x48\xe9\x22\x42\x00\xc6\x72\x47\x4f\xa0\x07\x22\x04\x63\x0a\xe2\x04\xa8\xea\x63\xba\x98\x31\xaa\x0a\x99\x09\x02\x90\x20\xfe\x52\x96\x5c\x51\x00\xe7\xf3\x19\x46\x61\xbe\x37\xd9\x77\xcd\xbe\x38\x73\xf6\xef\x7f\xbb\xa6\x5f\x6c\x57\x0e\xd2\x13\xc1\x1a\x23\x54\xad\x7c\x7e\xea\xbf\x53\xa8\x5d\xe6\x0f\xb4\x3d\x48\x43\x63\x28\x69\xdc\x00\x5c\x7a\xdc\xdc\xaf\xb6\xbf\x3c\x50\xba\x28\x2c\x85\xf1\xb9\x34\xe8\x55\xf1\x96\xd2\xfd\x4e\x67\x0a\xe9\x7b\x3e\x1d\xe3\x23\x59\x91\xdd\x29\xde\x5c\x80\xb3\x9b\x1c\xc4\x4d\x09\xa9\x90\x1c\xc9\xf6\xea\xac\x62\x0a\xe5\x08\x60\xf0\x37\xb3\x6a\x3e\xbd\x81\x07\x4f\xc0\x23\xb3\x4c\xfe\x91\xea\x95\x1e\x78\x2c\x30\xab\xf3\x87\xad\x85\x37\x4e\x57\x4a\xae\xaf\xbb\xdd\x8e\xf5\x5c\x89\x58\xa2\xf6\x83\x25\xba\x32\x92\x33\xbe\xf9\x21\xd7\x1f\xe4\x7c\xeb\xb6\x6f\x64\xd2\x17\x24\x9e\x68\xeb\xa7\xcc\xa1\xec\x9d\x98\xb4\xc2\x65\x83\xb5\x95\x4e\xc5\x8b\x59\xfe\x18\x47\xf3\xb3\x3b\x61\x76\xc7\xef\x1f\xe8\xf8\x7d\x2a\xb8\x8e\xab\x14\xe5\xcd\x22\x35\x27\xef\xf3\x46\x1b\x1d\xbe\xdf\xc2\xd1\xfa\x9d\xd6\x29\xde\xea\xb4\xe1\x97\x23\xbc\x0e\x34\x95\x2e\x55\xd6\x8f\x29\x1b\xe7\xa7\xac\x2d\x2d\x67\x9d\xcc\x76\x95\x87\x9b\xcc\xa6\xdb\x3b\xcd\x64\xc2\xfd\x5e\x8e\x2f\x15\xb0\xda\xce\x79\x25\xed\xdc\x7f\xdb\xe3\x6a\x0a\xc6\xdb\xb6\xa7\xd6\xcc\xfe\x0f\x74\xb8\xeb\xac\x62\x50\xf5\x4d\x33\x47\x03\xd7\xcd\x08\xbb\xfa\x63\x7b\x9d\xcd\xfd\x16\xed\x36\xae\xc6\xdd\x55\x22\x6f\xab\x12\xd9\xd4\x47\xf7\x5f\x7b\xbc\x85\xd3\x5a\x1e\xc5\x9f\x8d\x13\xca\x3c\x70\x74\xa5\x5a\x6b\x0e\x5e\x18\x55\xd0\xd6\xf4\x40\xe3\x5a\x6a\x4b\x96\xef\x8f\x51\x5a\xdd\xe2\xf3\x16\xbb\x6a\xec\x1f\xa2\x1a\xfb\x5e\xf6\xa8\x8a\x83\xec\x4a\xbe\xdb\x1c\x15\xad\x46\xdc\x53\xed\x6d\x5c\xed\xbd\xab\x56\xdf\x55\xab\xdf\x6f\xb5\xba\x48\xad\x7a\x95\xa5\xef\xea\xd2\x77\x75\xe9\xf7\x51\x97\xbe\x59\x65\xfa\x37\xa8\x4d\xf7\xd4\x6f\xf7\x5f\xa0\xfe\xd0\x25\xea\xed\x6b\xd4\xdb\x44\x17\xdf\xa0\x50\xdd\xa3\x4a\x6e\xeb\xd5\xea\x7e\xa6\xea\x7e\xea\x15\xee\x89\x75\xcd\x4a\xe1\xb6\x56\xb9\xee\xc5\xc9\x5d\xf5\xfa\xae\x7a\x7d\x57\xbd\xbe\xab\x5e\xdf\x55\xaf\xef\xaa\xd7\x77\xd5\xeb\xbb\xea\xf5\x5d\xf5\xfa\xae\x7a\x7d\x57\xbd\xbe\xab\x5e\xdf\x55\xaf\x3f\x5c\xf5\x7a\xbe\xb9\xd0\xf8\xaa\x61\x43\x95\xa9\x42\x85\x8f\x6d\xee\xfe\xad\x02\x74\x3f\x85\x18\xe5\x2f\x87\x17\x2a\x32\x2a\x4a\x6b\xdc\x08\x16\xc1\x09\xf3\x7b\x4a\xc5\x36\xe7\xf8\x48\xdd\x8c\x47\x4f\xbe\x2c\xe0\xac\x04\xa6\x30\x9b\x7e\x1a\x3e\x1d\x41\x00\x69\x3c\x84\xb7\x67\x1e\x27\xcc\x24\xa6\xba\x89\xc4\x46\x6b\x23\x3f\x01\x5c\x89\x4c\x2b\x07\xc2\x3d\x57\x1b\x3a\x12\xd5\xb8\x6e\xe0\x50\x34\x77\x27\xea\xd8\x66\x07\xdf\xca\xa9\x68\x30\x54\xc3\xe4\x63\x4b\x07\xc3\xd7\xc5\x68\xc4\xa3\xb2\x3c\xb4\x75\x36\xea\x86\x2d\x27\xe8\xaa\x72\xd5\x5b\x70\x3c\x5a\x22\x54\x70\x41\xfc\xbd\x23\x1f\xc3\x5c\x87\x93\xbf\x81\xae\xa5\xce\xea\x5d\x95\xe5\xb6\xe9\x35\xb2\x5b\x88\xff\x9a\x4d\x8c\xcb\xc9\x70\x5a\xef\x6a\x43\x5f\x9c\xe3\xac\x08\xb4\xc6\x32\xbd\xb5\x5a\xa0\x5a\xab\xa1\x81\xaf\x36\x4b\x0e\xf8\x35\x86\x4f\xb7\x37\x1a\x11\x15\xef\x0b\xe6\x48\x37\x46\x76\x14\x36\xb1\x45\x5b\x8f\x69\x2b\x31\x7d\xe0\xd0\xb6\x86\x6b\x5b\x8d\x70\x7d\xc7\xfa\x5e\x92\x87\x4d\x78\xb3\x55\xf3\xe3\x1a\xf1\xc1\x12\x89\xcd\xf1\xb8\x87\x74\x62\x0d\x12\x8d\xad\x8c\x93\x9a\xef\x2e\x1a\x6c\xc0\xfe\x86\x41\x61\xd1\x68\xd8\xbe\x28\xf8\x1d\x1c\xea\xf5\x39\xbe\x5a\x38\x8f\xb9\x3b\xab\xba\x3b\xab\xba\x3b\xab\xfa\x5d\x9d\x1a\xdb\xfc\xac\xaa\x62\x9b\x70\x3b\xe4\x07\x0e\x6b\x8e\x72\xdd\xd0\xe3\xb6\xde\x89\xed\x33\x87\x8d\x8f\xe3\x34\x3c\x8d\xa3\x69\xe2\xec\x10\xa6\x05\x0f\x8f\x53\xb0\x96\x5e\x7e\xdf\xa4\xfe\xf1\x8b\xe9\xed\x1f\x6e\x2c\x8e\x6e\x6b\xf5\x00\x25\xf4\xed\xbf\x8e\x59\xfc\xd1\x1c\x5d\x8b\x94\x17\xfc\xe2\xa2\xb7\x55\x2d\x5f\xf5\xe5\xb2\x9a\xfb\x63\x1b\xbc\x59\x29\xaf\x7e\xe4\xd8\x86\xcd\x26\xd5\xbb\xf6\xea\x9a\xb1\xab\xb8\xc6\xb6\xd8\x8a\x45\x30\x4e\xc9\xe1\x96\xe6\x8b\xee\xce\xb8\xcd\x4e\x85\xc5\xd9\x7d\xfa\x7b\xeb\x9f\xfe\xf6\xf1\x1f\xeb\xbf\xff\xed\xd6\xe5\xe6\x97\xc1\x8b\xd2\xa9\x39\xf2\x16\xd1\x19\x57\x8b\xff\x81\x25\x8d\xb8\xd6\xbf\xf5\x6d\x88\xa6\x71\xc4\x72\x83\x6f\x80\x97\x18\x75\xf1\xf4\x72\x08\xf6\x9c\x63\xf9\x7e\x27\x3c\xff\x4c\xb8\x13\xd4\xd0\x72\x37\x85\xbe\x7d\x25\x56\x02\x3d\x0f\x60\x4c\x4b\x67\x04\xf5\x39\x69\xf5\x51\xf1\xd6\x87\x67\xad\x9f\x1b\x7f\xe8\xd3\xb3\x3f\xd0\x87\xc8\x0d\xc7\xa0\x88\x46\x85\x77\xe0\x57\x5c\xa8\x6f\x32\x1e\xc7\x61\x76\xe5\x40\xe6\x21\x0a\xd7\x71\x11\xe3\x24\xd6\x1e\x55\xef\x27\x96\x6f\x15\x30\xb7\x55\xfd\x3f\x94\x5e\x8d\x98\x5a\xef\x7f\xf4\x2f\xa5\xd7\x7e\x24\x1d\xc0\x38\x14\xbf\x29\x76\xf1\x91\x53\x48\x53\xc6\xe6\x74\x7c\x78\x48\x19\x0c\x6e\x92\x25\x22\x93\x59\xf2\x75\x14\x24\xd1\xe1\x97\x05\xa2\xdc\xc8\xd0\xc3\x3f\x3f\xfd\xeb\x5f\xfe\xf2\x9f\x7f\xfd\xef\xc3\x94\x5f\x07\x73\x78\x8d\x63\xc8\xdf\x1e\x7c\xc5\x6c\x2a\xfe\xca\xc8\x13\xd4\xdd\xa0\xbb\x83\x24\x3e\x80\x07\xd7\xe2\xaa\x8a\x03\x9a\xce\xcf\xc1\x83\x7f\xb7\xbd\x52\x48\x1c\xf5\xd6\x35\x47\x08\x77\xdf\x6d\xdf\x7d\xb7\x7d\xf7\xdd\xf6\xdd\x77\xdb\x77\xdf\x6d\xdf\xec\xbb\xed\x16\xcf\x94\x11\x18\x53\x18\x08\xcb\x73\x12\x73\xa7\x28\xd4\x55\x9e\xea\xa4\x34\x7c\xda\x98\x2f\x61\xa9\xd0\xc5\xd2\x32\x6f\x4d\x92\x50\x3e\x69\x70\xb3\x9c\x96\xde\xff\x7d\xb6\x32\xc7\x47\x7a\xa0\xe1\x18\xa3\xde\x2d\x4e\x3b\x9e\xc3\x65\x69\xe9\x1f\xc7\x21\x7f\x64\x1f\xdf\xe5\x22\x47\xcf\x5c\x81\x08\x7f\x97\xc5\xe6\x14\xec\xa3\x2c\x3c\x1f\x65\x8f\x5f\x2c\xf0\x2c\x44\x64\x08\xa2\xe7\x3a\x18\x17\x0a\xbc\x9d\x27\x48\xdd\x71\x0e\x21\x83\xcf\x32\xbd\x27\x7c\x9f\x72\x4c\x14\x3d\x1b\x74\xed\xda\x4e\x13\x14\x15\x98\x49\xc2\x4e\x6e\xe7\x1c\xf3\x67\xfc\xdf\xdf\xe0\x8c\xa6\xff\x17\xd9\xac\x6c\xb8\x2b\x8e\x4f\xda\xbc\xaf\xb3\xc4\x7f\x3c\x4e\xc0\x73\x0b\x01\xce\xa9\x8a\x9e\x37\x20\xe6\xb9\x46\xcc\x73\x8d\x98\xe7\xb5\xc4\x3c\x6f\x4e\x4c\x07\x97\x23\x98\x74\x19\xfc\x4e\xb0\xbc\x01\x8e\x66\x57\xc0\xa5\xaf\xc4\xd3\x31\xb8\xd0\xdc\xae\x52\x2f\x91\xad\x90\x29\x8b\xb3\x05\xd3\x7d\x9b\xb3\x34\x38\x92\x3f\xf5\xb7\xc8\xf1\x1f\x0e\x52\x05\x2f\x52\x80\xe4\x63\x6b\xba\x49\xfe\x68\x62\x21\xdb\x56\xa5\x18\x73\xb1\x71\xb6\x55\x69\x51\x43\xac\x44\x63\x71\x3b\x9f\xfa\xbb\x9a\xe0\x8e\xeb\xce\xbc\xc2\x4e\x25\x3d\xa8\x92\x27\x99\x98\x39\x23\x68\x82\x6f\x5d\xfc\x79\xee\xc1\x9f\xe7\x0d\xf8\xf3\xbc\x01\x7f\x9e\xbb\xf9\xc3\xff\x5a\x03\xd0\x05\xc0\x71\x39\x61\x59\xf8\x4a\xbe\x97\xba\x5b\xb9\xd2\x66\x64\x26\xe3\xf0\xd0\xa1\x61\xd4\x63\x0a\x60\x49\x39\x03\xc8\x9f\xa6\x58\xf1\x28\x83\x1b\xe3\x2c\x02\x04\xe2\xdb\xcc\x74\x24\x95\xbb\x4b\x7f\xb9\xf4\xf0\x00\xf4\x3d\xc2\x16\x6d\x8b\xa0\x50\x7f\x63\x84\xbb\x2a\x80\x55\x7c\x68\x7a\xa9\x66\x7a\xb3\x90\xf9\x74\x0c\xa2\xa1\xb4\xe1\xa6\xcb\xbf\x84\xb3\xd6\xd7\x77\xd6\x8d\x54\xa9\xac\x0a\x39\xad\xcc\x5b\x8a\xe0\x0d\x02\x74\x41\x50\x5e\xf1\x93\x4f\x12\x55\xce\x51\x20\x13\x05\x80\xa2\x39\x24\x90\x25\x04\x04\x53\x48\x60\xc0\x10\xa1\xc6\x36\xfe\x63\xca\xc8\x19\x49\xe6\x88\x30\x8c\xe8\x69\x9e\x56\xa0\x79\xbd\x95\xe3\xbd\xe9\x7b\xe8\x40\xb5\x4b\x4a\x29\x9a\x0b\x40\x09\x61\x28\x7c\x87\xee\x68\xd5\x88\x76\x30\x19\x99\xbc\x97\xb6\x1d\xe7\x86\x23\x46\x4d\x93\x90\xaa\x66\x6c\x94\xe6\x4e\xfa\xab\x55\xce\xaf\x4f\x89\xd0\x30\xe5\x4b\x05\xcc\x41\x7b\xd1\xa8\x27\x2c\xbf\x08\xe6\x24\xf4\xde\xc0\x32\x5f\x7a\x18\x92\xe6\xe5\x72\x30\xf9\xe5\x53\xe9\xfc\xfc\x94\xc3\xfb\x29\xcd\x74\xb4\xc7\x6f\x60\xc8\x4a\x5d\xf9\xdf\xe1\x21\x80\x61\x08\x70\x6c\x15\xa4\x62\x61\x94\x91\x7f\xaa\x49\x3e\xd5\x17\xfc\xcf\xb3\x8b\x6d\xb7\x72\x37\xae\x45\xf0\x3c\x6f\xc7\x55\x0b\xf4\x9b\xdf\x8f\x1b\x95\x2e\xc8\xad\x97\x01\x0b\x94\x51\xcf\xbc\x3e\x17\x14\x7e\x0c\x01\x68\xa8\x81\x78\x5c\x7c\x33\x04\xcb\x3c\x34\xce\x67\x51\x75\x5c\xc2\xd9\xc5\xcd\x25\x38\x02\x4b\x1f\x49\xdc\xe4\x36\xd7\x0d\xa4\xd3\x9a\x63\xd5\x36\x0a\x8a\x49\xe1\xd6\x12\xfa\xe6\xfc\xd4\x14\xad\x22\x64\xab\xe0\x52\x29\xb8\x16\x2c\x8b\x92\x4a\x9b\x4b\xaa\xa5\x4b\xb5\xa4\x96\x3a\xb4\x92\x54\x0b\x94\x87\x95\xd4\xca\x09\x6e\x25\xbc\xd9\x23\x85\x42\xea\x27\xe8\x0e\x99\x74\xc4\xec\x29\x2e\x19\x04\xcf\x20\xb3\xfb\x62\x94\x25\x04\x09\xe5\x9c\x39\x64\x2c\xb1\x37\x24\x8b\x80\x29\xaf\xcc\xb1\xd3\x16\xf9\xa4\x8c\x87\x40\x64\xf3\x9c\xfe\x5b\x1e\xc9\x2e\x21\x01\x16\x71\xb7\x3d\x33\xf7\x01\x4b\xeb\xe7\xd7\x38\xca\x57\x50\x34\x04\x7b\x16\x10\x15\xdb\x7e\xf9\x8c\xef\x73\xdc\x8f\x6c\x18\xd8\xb6\x26\x0d\xd5\xa1\xce\x62\x25\xff\x42\x41\xba\x81\x96\x66\x3e\x8b\x6a\x84\x66\x86\x73\x0e\x09\x75\x38\x60\x13\x92\x44\x20\x83\x07\xb2\xf4\xbe\xd4\x51\x7c\xb6\xe3\x10\x4c\xf0\x6c\x26\x21\xe1\x58\xba\xcc\xbc\x8b\xf2\x65\x6c\x4a\xd2\x44\xd0\x85\x9e\x4c\xea\x37\x20\xa5\x34\xce\x8d\x0d\x88\x27\x88\x56\xdb\x02\xca\x0d\x0d\xa0\xc8\x3d\xe2\x38\xac\xd1\xde\x55\x90\xf0\x04\x2c\xd3\x22\x83\xa8\x22\xf1\x2e\xca\x0d\xf6\xf6\xc0\x72\x74\x6e\xca\xd5\x1c\x12\xa6\x39\xbe\x74\x74\x3e\x9f\x61\xd6\xdf\x5f\x8e\xce\x95\xef\x17\xc0\xd1\x79\xe6\x53\x67\x57\x92\xa8\xad\x7f\xd1\x7d\xc0\x41\xae\x56\x52\xae\x02\x38\xca\x7d\xd4\x5c\xd5\xd8\x37\xc5\x6e\xe7\x52\x64\x9c\x9d\x05\xfc\x31\xf8\xe9\xc9\x13\xfa\x53\x6f\x08\x38\x5a\x03\x2d\x1b\x59\x54\x9f\x6a\x4a\xf1\x50\xcc\xa7\x70\x9d\x4b\x30\x45\xc7\xa4\x74\x63\xcf\x5c\xb9\xab\x9c\x12\x9f\xa2\x69\xd1\x7a\x4f\x15\x47\x0b\x34\x2f\x38\xb7\x30\x58\xaf\x2f\x6b\x54\xbb\xb7\xa2\x6d\xa4\x8e\xd5\x9d\xf1\x4e\xf5\x4b\x4d\xfd\x3b\xc3\x54\xa4\x9e\xcb\xcd\x5c\x8a\x98\xce\x70\x80\x6c\x5d\x94\x46\xa6\x95\x2a\x99\xf6\x23\x0a\x2e\x2e\x3d\xd4\xf2\x00\xf4\x2f\x2e\x7d\x2a\xea\x9c\xc5\x0e\xf2\xc0\x2b\xbc\x41\x55\x80\xb8\xf8\x46\x54\x6c\x8d\x88\xfd\x8f\x21\x88\x72\xeb\x99\xed\x7f\xb4\xad\x05\xf1\xa8\x06\x89\x9a\x96\x7e\xd8\x8c\x7f\x7e\x89\xaf\x8b\x17\x17\xf8\x52\x08\xb5\xe3\xbd\x99\x95\x77\x83\x49\x0b\x48\xfe\x3f\x00\x00\xff\xff\xde\x47\x28\xa9\x93\xcb\x00\x00") func tableGoTmplBytes() ([]byte, error) { return bindataRead( @@ -169,7 +169,7 @@ func tableGoTmpl() (*asset, error) { } info := bindataFileInfo{name: "table.go.tmpl", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xad, 0x60, 0x1a, 0x7d, 0x29, 0x9e, 0x65, 0xb2, 0xd8, 0xc4, 0x4a, 0x82, 0x94, 0xf, 0xb8, 0x2a, 0xe3, 0xe4, 0x23, 0xc0, 0x3b, 0x61, 0xc1, 0xf0, 0xe2, 0x79, 0x35, 0xf4, 0x3d, 0xb3, 0x77, 0x41}} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x1b, 0xf9, 0xf3, 0xfa, 0x25, 0xe7, 0xc2, 0x19, 0xd3, 0x13, 0x2c, 0x50, 0x2, 0xfa, 0xe2, 0x6a, 0xe9, 0x64, 0x5d, 0xa7, 0x73, 0xd8, 0x19, 0xf5, 0xfc, 0x26, 0x82, 0x34, 0x51, 0xd8, 0x30, 0x9a}} return a, nil } diff --git a/server/gendb/table.go.tmpl b/server/gendb/table.go.tmpl index 9f80b5a1..070a7cd3 100644 --- a/server/gendb/table.go.tmpl +++ b/server/gendb/table.go.tmpl @@ -680,8 +680,13 @@ func (t {{ $modelName}}Table) get{{ $modelName }}sBy{{ $computedIndexName }}(ctx {{- end }} {{- range $attributeName := modelAttributeNamesForKeyType $xdbConfig $gsi.KeySchema "HASH" }} {{- $dynamoDBType := dynamoDBTypeForAttribute $xdbConfig $attributeName }} + {{- $attrGoType := goTypeForAttribute $xdbConfig $attributeName }} {{- if eq $dynamoDBType "S" }} + {{- if eq $attrGoType "strfmt.DateTime" }} + if toDynamoTimeString(input.{{ pascalize $attributeName }}) == "" { + {{- else }} if input.{{ pascalize $attributeName }} == "" { + {{- end }} return fmt.Errorf("Hash key input.{{ pascalize $attributeName }} cannot be empty") } {{- end }} @@ -695,9 +700,12 @@ func (t {{ $modelName}}Table) get{{ $modelName }}sBy{{ $computedIndexName }}(ctx ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{ ":{{ camelize $hashKey.AttributeName }}": &dynamodb.AttributeValue{ {{- $dynamoDBType := dynamoDBTypeForAttribute $xdbConfig $hashKey.AttributeName }} + {{- $attrGoType := goTypeForAttribute $xdbConfig $hashKey.AttributeName }} {{- if eq $dynamoDBType "S" }} {{- if isComposite $xdbConfig $hashKey.AttributeName }} S: aws.String({{ compositeValue $xdbConfig $hashKey.AttributeName "input" }}), + {{- else if eq $attrGoType "strfmt.DateTime" }} + S: aws.String(toDynamoTimeString(input.{{ pascalize $hashKey.AttributeName }})), {{- else if attributeIsEnum $xdbConfig $hashKey.AttributeName }} S: aws.String(string(input.{{ pascalize $hashKey.AttributeName }})), {{- else }} From 26ac4e42dd01a86c040aa325e5a7a870a6b5dd82 Mon Sep 17 00:00:00 2001 From: Daniel Xu Date: Mon, 4 Mar 2024 14:27:17 -0800 Subject: [PATCH 2/2] bump VERSION --- VERSION | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 4951bd05..38158186 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,6 @@ -v9.1.5 +v9.1.6 + +v9.1.6 Fix dynamodb codegen for datetime HASH keys in GSIs v9.1.5 Fix import for transaction codegen