1531 lines
66 KiB
Go
1531 lines
66 KiB
Go
package tests
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"floci-sdk-test-go/internal/testutil"
|
|
|
|
awsV2 "github.com/aws/aws-sdk-go-v2/aws"
|
|
"github.com/aws/aws-sdk-go-v2/service/rds"
|
|
"github.com/aws/aws-sdk-go-v2/service/rds/types"
|
|
"github.com/aws/aws-sdk-go-v2/service/rdsdata"
|
|
rdsdatatypes "github.com/aws/aws-sdk-go-v2/service/rdsdata/types"
|
|
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
|
|
awsV1 "github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
"github.com/aws/aws-sdk-go/service/rdsdataservice"
|
|
"github.com/aws/smithy-go"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestRdsDataApiGoSdkV1(t *testing.T) {
|
|
ctx := context.Background()
|
|
rdsSvc := testutil.RDSClient()
|
|
secretsSvc := testutil.SecretsManagerClient()
|
|
dataSvc := rdsDataV1Client(t)
|
|
|
|
clusterID := "go-rds-data-mysql-" + strconv.FormatInt(time.Now().UnixNano(), 36)
|
|
database := "app"
|
|
username := "admin"
|
|
password := "secret123"
|
|
secretName := "go-rds-data/" + clusterID
|
|
var secretArn string
|
|
var resourceArn string
|
|
|
|
t.Cleanup(func() {
|
|
if secretArn != "" {
|
|
_, _ = secretsSvc.DeleteSecret(ctx, &secretsmanager.DeleteSecretInput{
|
|
SecretId: awsV2.String(secretArn),
|
|
ForceDeleteWithoutRecovery: awsV2.Bool(true),
|
|
})
|
|
}
|
|
if resourceArn != "" {
|
|
_, _ = rdsSvc.DeleteDBCluster(ctx, &rds.DeleteDBClusterInput{
|
|
DBClusterIdentifier: awsV2.String(clusterID),
|
|
SkipFinalSnapshot: awsV2.Bool(true),
|
|
})
|
|
}
|
|
})
|
|
|
|
create, err := rdsSvc.CreateDBCluster(ctx, &rds.CreateDBClusterInput{
|
|
DBClusterIdentifier: awsV2.String(clusterID),
|
|
Engine: awsV2.String("aurora-mysql"),
|
|
EngineVersion: awsV2.String("8.0.mysql_aurora.3.08.0"),
|
|
MasterUsername: awsV2.String(username),
|
|
MasterUserPassword: awsV2.String(password),
|
|
DatabaseName: awsV2.String(database),
|
|
EngineMode: awsV2.String("provisioned"),
|
|
Tags: []types.Tag{{Key: awsV2.String("test"), Value: awsV2.String("rds-data")}},
|
|
})
|
|
if err != nil {
|
|
t.Skipf("RDS MySQL cluster unavailable in this environment: %v", err)
|
|
}
|
|
require.NotNil(t, create.DBCluster)
|
|
resourceArn = awsV2.ToString(create.DBCluster.DBClusterArn)
|
|
require.NotEmpty(t, resourceArn)
|
|
|
|
secret, err := secretsSvc.CreateSecret(ctx, &secretsmanager.CreateSecretInput{
|
|
Name: awsV2.String(secretName),
|
|
SecretString: awsV2.String(fmt.Sprintf(`{"username":%q,"password":%q}`, username, password)),
|
|
})
|
|
require.NoError(t, err)
|
|
secretArn = awsV2.ToString(secret.ARN)
|
|
require.NotEmpty(t, secretArn)
|
|
|
|
executeEventually(t, dataSvc, &rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String(`create table if not exists data_api_items (
|
|
id varchar(64) primary key,
|
|
title varchar(255),
|
|
score bigint,
|
|
payload blob null,
|
|
active boolean null,
|
|
ratio double null,
|
|
json_text text null,
|
|
rfc3339_text varchar(64) null,
|
|
observed_at datetime(6) null,
|
|
stamped_at timestamp(6) null,
|
|
due_date date null,
|
|
due_time time null
|
|
)`),
|
|
})
|
|
|
|
tx, err := dataSvc.BeginTransaction(&rdsdataservice.BeginTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, awsV1.StringValue(tx.TransactionId))
|
|
|
|
insertOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: tx.TransactionId,
|
|
Sql: awsV1.String(`insert into data_api_items
|
|
(id, title, score, payload, active, ratio, json_text, rfc3339_text, observed_at, stamped_at, due_date, due_time)
|
|
values (
|
|
'commit-1',
|
|
'first item',
|
|
7,
|
|
UNHEX('010203'),
|
|
true,
|
|
1.5,
|
|
'{"ok":true}',
|
|
'2021-03-04T05:06:07Z',
|
|
'2021-03-04 05:06:07.891000',
|
|
'2021-03-04 05:06:07.891000',
|
|
'2021-03-04',
|
|
'05:06:07'
|
|
)`),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(insertOut.NumberOfRecordsUpdated))
|
|
assert.Nil(t, insertOut.Records, "AWS omits records for a statement that returns no result set")
|
|
|
|
inTxRead, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: tx.TransactionId,
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where id = 'commit-1'"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(inTxRead.Records[0][0].LongValue))
|
|
|
|
_, err = dataSvc.CommitTransaction(&rdsdataservice.CommitTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
TransactionId: tx.TransactionId,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: tx.TransactionId,
|
|
Sql: awsV1.String("select 1"),
|
|
})
|
|
assertAwsErrorCode(t, err, "TransactionNotFoundException")
|
|
|
|
selectOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select title, score, payload, null as nothing, observed_at, stamped_at, due_date, due_time, active, ratio, json_text, rfc3339_text from data_api_items where id = 'commit-1'"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, selectOut.ColumnMetadata, 12)
|
|
assert.Equal(t, "title", awsV1.StringValue(selectOut.ColumnMetadata[0].Name))
|
|
assert.Equal(t, "title", awsV1.StringValue(selectOut.ColumnMetadata[0].Label))
|
|
assert.NotEmpty(t, awsV1.StringValue(selectOut.ColumnMetadata[0].TypeName))
|
|
assert.Equal(t, "nothing", awsV1.StringValue(selectOut.ColumnMetadata[3].Label))
|
|
assert.Equal(t, "data_api_items", awsV1.StringValue(selectOut.ColumnMetadata[1].TableName))
|
|
require.Len(t, selectOut.Records, 1)
|
|
require.Len(t, selectOut.Records[0], 12)
|
|
assert.Equal(t, "first item", awsV1.StringValue(selectOut.Records[0][0].StringValue))
|
|
assert.Equal(t, int64(7), awsV1.Int64Value(selectOut.Records[0][1].LongValue))
|
|
assert.Equal(t, []byte{1, 2, 3}, selectOut.Records[0][2].BlobValue)
|
|
assert.True(t, awsV1.BoolValue(selectOut.Records[0][3].IsNull))
|
|
assert.Equal(t, "2021-03-04 05:06:07.891", awsV1.StringValue(selectOut.Records[0][4].StringValue))
|
|
assert.Equal(t, "2021-03-04 05:06:07.891", awsV1.StringValue(selectOut.Records[0][5].StringValue))
|
|
assert.Equal(t, "2021-03-04", awsV1.StringValue(selectOut.Records[0][6].StringValue))
|
|
assert.Equal(t, "05:06:07", awsV1.StringValue(selectOut.Records[0][7].StringValue))
|
|
assert.True(t, awsV1.BoolValue(selectOut.Records[0][8].BooleanValue))
|
|
assert.Equal(t, 1.5, awsV1.Float64Value(selectOut.Records[0][9].DoubleValue))
|
|
assert.Equal(t, `{"ok":true}`, awsV1.StringValue(selectOut.Records[0][10].StringValue))
|
|
assert.Equal(t, "2021-03-04T05:06:07Z", awsV1.StringValue(selectOut.Records[0][11].StringValue))
|
|
|
|
updateOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("update data_api_items set score = 8 where id = 'commit-1' and score = 7"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(updateOut.NumberOfRecordsUpdated))
|
|
|
|
staleUpdateOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("update data_api_items set score = 9 where id = 'commit-1' and score = 7"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), awsV1.Int64Value(staleUpdateOut.NumberOfRecordsUpdated))
|
|
|
|
upsertOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score, payload) values ('commit-1', 'upserted', 11, UNHEX('0A0B')) on duplicate key update title = values(title), score = values(score), payload = values(payload)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.GreaterOrEqual(t, awsV1.Int64Value(upsertOut.NumberOfRecordsUpdated), int64(1))
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values ('page-1', 'alpha', 1), ('page-2', 'beta', 2), ('page-3', 'alphabet', 3)"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
predicateOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select id from data_api_items where (title like 'alpha%' or id in (select id from data_api_items where score = 11)) order by score desc limit 2 offset 0"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, predicateOut.Records, 2)
|
|
assert.Equal(t, "commit-1", awsV1.StringValue(predicateOut.Records[0][0].StringValue))
|
|
assert.Equal(t, "page-3", awsV1.StringValue(predicateOut.Records[1][0].StringValue))
|
|
|
|
lessThanAndNotInOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where score < 3 and id not in ('commit-1')"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "count", awsV1.StringValue(lessThanAndNotInOut.ColumnMetadata[0].Name))
|
|
assert.Equal(t, int64(2), awsV1.Int64Value(lessThanAndNotInOut.Records[0][0].LongValue))
|
|
|
|
emptyOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select id from data_api_items where id = 'missing'"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "id", awsV1.StringValue(emptyOut.ColumnMetadata[0].Name))
|
|
assert.Empty(t, emptyOut.Records)
|
|
assert.Equal(t, int64(0), awsV1.Int64Value(emptyOut.NumberOfRecordsUpdated))
|
|
|
|
replaceTx, err := dataSvc.BeginTransaction(&rdsdataservice.BeginTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
})
|
|
require.NoError(t, err)
|
|
deleteOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: replaceTx.TransactionId,
|
|
Sql: awsV1.String("delete from data_api_items where id = 'page-2'"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(deleteOut.NumberOfRecordsUpdated))
|
|
replaceInsertOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: replaceTx.TransactionId,
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values ('page-4', 'replacement', 4)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(replaceInsertOut.NumberOfRecordsUpdated))
|
|
_, err = dataSvc.CommitTransaction(&rdsdataservice.CommitTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
TransactionId: replaceTx.TransactionId,
|
|
})
|
|
require.NoError(t, err)
|
|
replaceCountOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where id in ('page-2', 'page-4')"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(replaceCountOut.Records[0][0].LongValue))
|
|
|
|
rollbackTx, err := dataSvc.BeginTransaction(&rdsdataservice.BeginTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
})
|
|
require.NoError(t, err)
|
|
rollbackInsertOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values ('rollback-1', 'rolled back', 9)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(rollbackInsertOut.NumberOfRecordsUpdated))
|
|
_, err = dataSvc.RollbackTransaction(&rdsdataservice.RollbackTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
Sql: awsV1.String("select 1"),
|
|
})
|
|
assertAwsErrorCode(t, err, "TransactionNotFoundException")
|
|
|
|
countOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where id in ('commit-1', 'rollback-1')"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "count", awsV1.StringValue(countOut.ColumnMetadata[0].Name))
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(countOut.Records[0][0].LongValue))
|
|
|
|
batchOut, err := dataSvc.BatchExecuteStatement(&rdsdataservice.BatchExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values (:id, :title, :score)"),
|
|
ParameterSets: [][]*rdsdataservice.SqlParameter{
|
|
{
|
|
{Name: awsV1.String("id"), Value: &rdsdataservice.Field{StringValue: awsV1.String("batch-1")}},
|
|
{Name: awsV1.String("title"), Value: &rdsdataservice.Field{StringValue: awsV1.String("batch one")}},
|
|
{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(1)}},
|
|
},
|
|
{
|
|
{Name: awsV1.String("id"), Value: &rdsdataservice.Field{StringValue: awsV1.String("batch-2")}},
|
|
{Name: awsV1.String("title"), Value: &rdsdataservice.Field{StringValue: awsV1.String("batch two")}},
|
|
{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(2)}},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, batchOut.UpdateResults, 2)
|
|
assert.Empty(t, batchOut.UpdateResults[0].GeneratedFields)
|
|
|
|
batchCountOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where id in ('batch-1', 'batch-2')"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), awsV1.Int64Value(batchCountOut.Records[0][0].LongValue))
|
|
|
|
_, err = dataSvc.BatchExecuteStatement(&rdsdataservice.BatchExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select id from data_api_items where score = :score"),
|
|
ParameterSets: [][]*rdsdataservice.SqlParameter{
|
|
{{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(1)}}},
|
|
{{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(2)}}},
|
|
},
|
|
})
|
|
assertAwsErrorCode(t, err, "BadRequestException")
|
|
|
|
noSetsOut, err := dataSvc.BatchExecuteStatement(&rdsdataservice.BatchExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values ('batch-no-sets', 'no sets', 99)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Empty(t, noSetsOut.UpdateResults)
|
|
|
|
noSetsCountOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where id = 'batch-no-sets'"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), awsV1.Int64Value(noSetsCountOut.Records[0][0].LongValue))
|
|
|
|
_, err = dataSvc.BatchExecuteStatement(&rdsdataservice.BatchExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values (:id, :title, :score)"),
|
|
ParameterSets: [][]*rdsdataservice.SqlParameter{{{Name: awsV1.String("id")}}},
|
|
})
|
|
assertAwsErrorCode(t, err, "BadRequestException")
|
|
|
|
_, err = dataSvc.ExecuteSql(&rdsdataservice.ExecuteSqlInput{
|
|
DbClusterOrInstanceArn: awsV1.String(resourceArn),
|
|
AwsSecretStoreArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
SqlStatements: awsV1.String("select 1"),
|
|
})
|
|
assertAwsErrorCode(t, err, "BadRequestException")
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values (:id, :title, :score)"),
|
|
Parameters: []*rdsdataservice.SqlParameter{
|
|
{Name: awsV1.String("id"), Value: &rdsdataservice.Field{StringValue: awsV1.String("param-1")}},
|
|
{Name: awsV1.String("title"), Value: &rdsdataservice.Field{StringValue: awsV1.String("parameterized")}},
|
|
{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(42)}},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
paramOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select title, score from data_api_items where id = :id and score = :score"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
Parameters: []*rdsdataservice.SqlParameter{
|
|
{Name: awsV1.String("id"), Value: &rdsdataservice.Field{StringValue: awsV1.String("param-1")}},
|
|
{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(42)}},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, paramOut.Records, 1)
|
|
assert.Equal(t, "parameterized", awsV1.StringValue(paramOut.Records[0][0].StringValue))
|
|
assert.Equal(t, int64(42), awsV1.Int64Value(paramOut.Records[0][1].LongValue))
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values ('commit-1', 'duplicate', 1)"),
|
|
})
|
|
assertAwsErrorCode(t, err, "DatabaseErrorException")
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select from data_api_items"),
|
|
})
|
|
assertAwsErrorCode(t, err, "DatabaseErrorException")
|
|
}
|
|
|
|
// TestRdsDataApiGoSdkPostgresV1 mirrors TestRdsDataApiGoSdkV1 against an
|
|
// aurora-postgresql cluster to verify RDS Data API parity across engines
|
|
// (transactions, result field mapping, upsert, and error codes).
|
|
func TestRdsDataApiGoSdkPostgresV1(t *testing.T) {
|
|
ctx := context.Background()
|
|
rdsSvc := testutil.RDSClient()
|
|
secretsSvc := testutil.SecretsManagerClient()
|
|
dataSvc := rdsDataV1Client(t)
|
|
|
|
clusterID := "go-rds-data-postgres-" + strconv.FormatInt(time.Now().UnixNano(), 36)
|
|
database := "app"
|
|
username := "admin"
|
|
password := "secret123"
|
|
secretName := "go-rds-data-postgres/" + clusterID
|
|
var secretArn string
|
|
var resourceArn string
|
|
|
|
t.Cleanup(func() {
|
|
if secretArn != "" {
|
|
_, _ = secretsSvc.DeleteSecret(ctx, &secretsmanager.DeleteSecretInput{
|
|
SecretId: awsV2.String(secretArn),
|
|
ForceDeleteWithoutRecovery: awsV2.Bool(true),
|
|
})
|
|
}
|
|
if resourceArn != "" {
|
|
_, _ = rdsSvc.DeleteDBCluster(ctx, &rds.DeleteDBClusterInput{
|
|
DBClusterIdentifier: awsV2.String(clusterID),
|
|
SkipFinalSnapshot: awsV2.Bool(true),
|
|
})
|
|
}
|
|
})
|
|
|
|
create, err := rdsSvc.CreateDBCluster(ctx, &rds.CreateDBClusterInput{
|
|
DBClusterIdentifier: awsV2.String(clusterID),
|
|
Engine: awsV2.String("aurora-postgresql"),
|
|
MasterUsername: awsV2.String(username),
|
|
MasterUserPassword: awsV2.String(password),
|
|
DatabaseName: awsV2.String(database),
|
|
EngineMode: awsV2.String("provisioned"),
|
|
Tags: []types.Tag{{Key: awsV2.String("test"), Value: awsV2.String("rds-data-postgres")}},
|
|
})
|
|
if err != nil {
|
|
t.Skipf("RDS PostgreSQL cluster unavailable in this environment: %v", err)
|
|
}
|
|
require.NotNil(t, create.DBCluster)
|
|
resourceArn = awsV2.ToString(create.DBCluster.DBClusterArn)
|
|
require.NotEmpty(t, resourceArn)
|
|
|
|
secret, err := secretsSvc.CreateSecret(ctx, &secretsmanager.CreateSecretInput{
|
|
Name: awsV2.String(secretName),
|
|
SecretString: awsV2.String(fmt.Sprintf(`{"username":%q,"password":%q}`, username, password)),
|
|
})
|
|
require.NoError(t, err)
|
|
secretArn = awsV2.ToString(secret.ARN)
|
|
require.NotEmpty(t, secretArn)
|
|
|
|
executeEventually(t, dataSvc, &rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String(`create table if not exists data_api_items (
|
|
id varchar(64) primary key,
|
|
title varchar(255),
|
|
score bigint,
|
|
payload bytea null,
|
|
active boolean null,
|
|
ratio double precision null,
|
|
json_text text null,
|
|
rfc3339_text varchar(64) null,
|
|
observed_at timestamp(6) null,
|
|
stamped_at timestamp(6) null,
|
|
due_date date null,
|
|
due_time time null
|
|
)`),
|
|
})
|
|
|
|
tx, err := dataSvc.BeginTransaction(&rdsdataservice.BeginTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, awsV1.StringValue(tx.TransactionId))
|
|
|
|
insertOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: tx.TransactionId,
|
|
Sql: awsV1.String(`insert into data_api_items
|
|
(id, title, score, payload, active, ratio, json_text, rfc3339_text, observed_at, stamped_at, due_date, due_time)
|
|
values (
|
|
'commit-1',
|
|
'first item',
|
|
7,
|
|
decode('010203', 'hex'),
|
|
true,
|
|
1.5,
|
|
'{"ok":true}',
|
|
'2021-03-04T05:06:07Z',
|
|
'2021-03-04 05:06:07.891000',
|
|
'2021-03-04 05:06:07.891000',
|
|
'2021-03-04',
|
|
'05:06:07'
|
|
)`),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(insertOut.NumberOfRecordsUpdated))
|
|
assert.Nil(t, insertOut.Records, "AWS omits records for a statement that returns no result set")
|
|
|
|
inTxRead, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: tx.TransactionId,
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where id = 'commit-1'"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(inTxRead.Records[0][0].LongValue))
|
|
|
|
_, err = dataSvc.CommitTransaction(&rdsdataservice.CommitTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
TransactionId: tx.TransactionId,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: tx.TransactionId,
|
|
Sql: awsV1.String("select 1"),
|
|
})
|
|
assertAwsErrorCode(t, err, "TransactionNotFoundException")
|
|
|
|
selectOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select title, score, payload, null as nothing, observed_at, stamped_at, due_date, due_time, active, ratio, json_text, rfc3339_text from data_api_items where id = 'commit-1'"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, selectOut.ColumnMetadata, 12)
|
|
assert.Equal(t, "title", awsV1.StringValue(selectOut.ColumnMetadata[0].Name))
|
|
assert.Equal(t, "title", awsV1.StringValue(selectOut.ColumnMetadata[0].Label))
|
|
assert.NotEmpty(t, awsV1.StringValue(selectOut.ColumnMetadata[0].TypeName))
|
|
assert.Equal(t, "nothing", awsV1.StringValue(selectOut.ColumnMetadata[3].Label))
|
|
assert.Equal(t, "data_api_items", awsV1.StringValue(selectOut.ColumnMetadata[1].TableName))
|
|
require.Len(t, selectOut.Records, 1)
|
|
require.Len(t, selectOut.Records[0], 12)
|
|
assert.Equal(t, "first item", awsV1.StringValue(selectOut.Records[0][0].StringValue))
|
|
assert.Equal(t, int64(7), awsV1.Int64Value(selectOut.Records[0][1].LongValue))
|
|
assert.Equal(t, []byte{1, 2, 3}, selectOut.Records[0][2].BlobValue)
|
|
assert.True(t, awsV1.BoolValue(selectOut.Records[0][3].IsNull))
|
|
assert.Equal(t, "2021-03-04 05:06:07.891", awsV1.StringValue(selectOut.Records[0][4].StringValue))
|
|
assert.Equal(t, "2021-03-04 05:06:07.891", awsV1.StringValue(selectOut.Records[0][5].StringValue))
|
|
assert.Equal(t, "2021-03-04", awsV1.StringValue(selectOut.Records[0][6].StringValue))
|
|
assert.Equal(t, "05:06:07", awsV1.StringValue(selectOut.Records[0][7].StringValue))
|
|
assert.True(t, awsV1.BoolValue(selectOut.Records[0][8].BooleanValue))
|
|
assert.Equal(t, 1.5, awsV1.Float64Value(selectOut.Records[0][9].DoubleValue))
|
|
assert.Equal(t, `{"ok":true}`, awsV1.StringValue(selectOut.Records[0][10].StringValue))
|
|
assert.Equal(t, "2021-03-04T05:06:07Z", awsV1.StringValue(selectOut.Records[0][11].StringValue))
|
|
|
|
// Error code and message checked against Aurora PostgreSQL 17.7 on 2026-09-13.
|
|
for _, unsupported := range []struct{ sql, typeName string }{
|
|
{"select point(1, 2)", "POINT"},
|
|
{"select interval '1 day 2 hours'", "INTERVAL"},
|
|
{"select 12.34::money", "MONEY"},
|
|
{"select box '((0,0),(1,1))'", "BOX"},
|
|
{"select circle '<(0,0),1>'", "CIRCLE"},
|
|
{"select line '{1,2,3}'", "LINE"},
|
|
{"select lseg '[(0,0),(1,1)]'", "LSEG"},
|
|
{"select path '[(0,0),(1,1)]'", "PATH"},
|
|
{"select polygon '((0,0),(1,1),(1,0))'", "POLYGON"},
|
|
} {
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String(unsupported.sql),
|
|
})
|
|
assertAwsErrorCode(t, err, "UnsupportedResultException")
|
|
var unsupportedErr awserr.Error
|
|
require.ErrorAs(t, err, &unsupportedErr)
|
|
assert.Equal(t, "The result contains the unsupported data type "+unsupported.typeName+".", unsupportedErr.Message())
|
|
}
|
|
|
|
textCastOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select point(1, 2)::text as p"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "(1,2)", awsV1.StringValue(textCastOut.Records[0][0].StringValue))
|
|
|
|
updateOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("update data_api_items set score = 8 where id = 'commit-1' and score = 7"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(updateOut.NumberOfRecordsUpdated))
|
|
|
|
staleUpdateOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("update data_api_items set score = 9 where id = 'commit-1' and score = 7"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), awsV1.Int64Value(staleUpdateOut.NumberOfRecordsUpdated))
|
|
|
|
upsertOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String(`insert into data_api_items (id, title, score, payload) values ('commit-1', 'upserted', 11, decode('0A0B', 'hex'))
|
|
on conflict (id) do update set title = excluded.title, score = excluded.score, payload = excluded.payload`),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.GreaterOrEqual(t, awsV1.Int64Value(upsertOut.NumberOfRecordsUpdated), int64(1))
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values ('page-1', 'alpha', 1), ('page-2', 'beta', 2), ('page-3', 'alphabet', 3)"),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
predicateOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select id from data_api_items where (title like 'alpha%' or id in (select id from data_api_items where score = 11)) order by score desc limit 2 offset 0"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, predicateOut.Records, 2)
|
|
assert.Equal(t, "commit-1", awsV1.StringValue(predicateOut.Records[0][0].StringValue))
|
|
assert.Equal(t, "page-3", awsV1.StringValue(predicateOut.Records[1][0].StringValue))
|
|
|
|
lessThanAndNotInOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where score < 3 and id not in ('commit-1')"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "count", awsV1.StringValue(lessThanAndNotInOut.ColumnMetadata[0].Name))
|
|
assert.Equal(t, int64(2), awsV1.Int64Value(lessThanAndNotInOut.Records[0][0].LongValue))
|
|
|
|
emptyOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select id from data_api_items where id = 'missing'"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "id", awsV1.StringValue(emptyOut.ColumnMetadata[0].Name))
|
|
assert.Empty(t, emptyOut.Records)
|
|
assert.Equal(t, int64(0), awsV1.Int64Value(emptyOut.NumberOfRecordsUpdated))
|
|
|
|
replaceTx, err := dataSvc.BeginTransaction(&rdsdataservice.BeginTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
})
|
|
require.NoError(t, err)
|
|
deleteOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: replaceTx.TransactionId,
|
|
Sql: awsV1.String("delete from data_api_items where id = 'page-2'"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(deleteOut.NumberOfRecordsUpdated))
|
|
replaceInsertOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: replaceTx.TransactionId,
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values ('page-4', 'replacement', 4)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(replaceInsertOut.NumberOfRecordsUpdated))
|
|
_, err = dataSvc.CommitTransaction(&rdsdataservice.CommitTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
TransactionId: replaceTx.TransactionId,
|
|
})
|
|
require.NoError(t, err)
|
|
replaceCountOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where id in ('page-2', 'page-4')"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(replaceCountOut.Records[0][0].LongValue))
|
|
|
|
rollbackTx, err := dataSvc.BeginTransaction(&rdsdataservice.BeginTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
})
|
|
require.NoError(t, err)
|
|
rollbackInsertOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values ('rollback-1', 'rolled back', 9)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(rollbackInsertOut.NumberOfRecordsUpdated))
|
|
_, err = dataSvc.RollbackTransaction(&rdsdataservice.RollbackTransactionInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
Sql: awsV1.String("select 1"),
|
|
})
|
|
assertAwsErrorCode(t, err, "TransactionNotFoundException")
|
|
|
|
countOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where id in ('commit-1', 'rollback-1')"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "count", awsV1.StringValue(countOut.ColumnMetadata[0].Name))
|
|
assert.Equal(t, int64(1), awsV1.Int64Value(countOut.Records[0][0].LongValue))
|
|
|
|
batchOut, err := dataSvc.BatchExecuteStatement(&rdsdataservice.BatchExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values (:id, :title, :score)"),
|
|
ParameterSets: [][]*rdsdataservice.SqlParameter{
|
|
{
|
|
{Name: awsV1.String("id"), Value: &rdsdataservice.Field{StringValue: awsV1.String("batch-1")}},
|
|
{Name: awsV1.String("title"), Value: &rdsdataservice.Field{StringValue: awsV1.String("batch one")}},
|
|
{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(1)}},
|
|
},
|
|
{
|
|
{Name: awsV1.String("id"), Value: &rdsdataservice.Field{StringValue: awsV1.String("batch-2")}},
|
|
{Name: awsV1.String("title"), Value: &rdsdataservice.Field{StringValue: awsV1.String("batch two")}},
|
|
{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(2)}},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, batchOut.UpdateResults, 2)
|
|
assert.Empty(t, batchOut.UpdateResults[0].GeneratedFields)
|
|
|
|
batchCountOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where id in ('batch-1', 'batch-2')"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), awsV1.Int64Value(batchCountOut.Records[0][0].LongValue))
|
|
|
|
_, err = dataSvc.BatchExecuteStatement(&rdsdataservice.BatchExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select id from data_api_items where score = :score"),
|
|
ParameterSets: [][]*rdsdataservice.SqlParameter{
|
|
{{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(1)}}},
|
|
{{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(2)}}},
|
|
},
|
|
})
|
|
assertAwsErrorCode(t, err, "BadRequestException")
|
|
|
|
noSetsOut, err := dataSvc.BatchExecuteStatement(&rdsdataservice.BatchExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values ('batch-no-sets', 'no sets', 99)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Empty(t, noSetsOut.UpdateResults)
|
|
|
|
noSetsCountOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select count(*) as count from data_api_items where id = 'batch-no-sets'"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), awsV1.Int64Value(noSetsCountOut.Records[0][0].LongValue))
|
|
|
|
_, err = dataSvc.BatchExecuteStatement(&rdsdataservice.BatchExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values (:id, :title, :score)"),
|
|
ParameterSets: [][]*rdsdataservice.SqlParameter{{{Name: awsV1.String("id")}}},
|
|
})
|
|
assertAwsErrorCode(t, err, "BadRequestException")
|
|
|
|
_, err = dataSvc.ExecuteSql(&rdsdataservice.ExecuteSqlInput{
|
|
DbClusterOrInstanceArn: awsV1.String(resourceArn),
|
|
AwsSecretStoreArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
SqlStatements: awsV1.String("select 1"),
|
|
})
|
|
assertAwsErrorCode(t, err, "BadRequestException")
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values (:id, :title, :score)"),
|
|
Parameters: []*rdsdataservice.SqlParameter{
|
|
{Name: awsV1.String("id"), Value: &rdsdataservice.Field{StringValue: awsV1.String("param-1")}},
|
|
{Name: awsV1.String("title"), Value: &rdsdataservice.Field{StringValue: awsV1.String("parameterized")}},
|
|
{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(42)}},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
paramOut, err := dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("select title, score from data_api_items where id = :id and score = :score"),
|
|
IncludeResultMetadata: awsV1.Bool(true),
|
|
Parameters: []*rdsdataservice.SqlParameter{
|
|
{Name: awsV1.String("id"), Value: &rdsdataservice.Field{StringValue: awsV1.String("param-1")}},
|
|
{Name: awsV1.String("score"), Value: &rdsdataservice.Field{LongValue: awsV1.Int64(42)}},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, paramOut.Records, 1)
|
|
assert.Equal(t, "parameterized", awsV1.StringValue(paramOut.Records[0][0].StringValue))
|
|
assert.Equal(t, int64(42), awsV1.Int64Value(paramOut.Records[0][1].LongValue))
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
Sql: awsV1.String("insert into data_api_items (id, title, score) values ('commit-1', 'duplicate', 1)"),
|
|
})
|
|
assertAwsErrorCode(t, err, "DatabaseErrorException")
|
|
|
|
_, err = dataSvc.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
|
|
ResourceArn: awsV1.String(resourceArn),
|
|
SecretArn: awsV1.String(secretArn),
|
|
Database: awsV1.String(database),
|
|
// Unlike MySQL, PostgreSQL accepts "select from t" as a valid zero-column
|
|
// query, so use a statement that is malformed under any SQL dialect.
|
|
Sql: awsV1.String("select * from data_api_items where"),
|
|
})
|
|
assertAwsErrorCode(t, err, "DatabaseErrorException")
|
|
}
|
|
|
|
func TestRdsDataApiGoSdkV2(t *testing.T) {
|
|
ctx := context.Background()
|
|
rdsSvc := testutil.RDSClient()
|
|
secretsSvc := testutil.SecretsManagerClient()
|
|
dataSvc := rdsDataV2Client()
|
|
|
|
clusterID := "go-rds-data-v2-mysql-" + strconv.FormatInt(time.Now().UnixNano(), 36)
|
|
database := "app"
|
|
username := "admin"
|
|
password := "secret123"
|
|
secretName := "go-rds-data-v2/" + clusterID
|
|
var secretArn string
|
|
var resourceArn string
|
|
|
|
t.Cleanup(func() {
|
|
if secretArn != "" {
|
|
_, _ = secretsSvc.DeleteSecret(ctx, &secretsmanager.DeleteSecretInput{
|
|
SecretId: awsV2.String(secretArn),
|
|
ForceDeleteWithoutRecovery: awsV2.Bool(true),
|
|
})
|
|
}
|
|
if resourceArn != "" {
|
|
_, _ = rdsSvc.DeleteDBCluster(ctx, &rds.DeleteDBClusterInput{
|
|
DBClusterIdentifier: awsV2.String(clusterID),
|
|
SkipFinalSnapshot: awsV2.Bool(true),
|
|
})
|
|
}
|
|
})
|
|
|
|
create, err := rdsSvc.CreateDBCluster(ctx, &rds.CreateDBClusterInput{
|
|
DBClusterIdentifier: awsV2.String(clusterID),
|
|
Engine: awsV2.String("aurora-mysql"),
|
|
EngineVersion: awsV2.String("8.0.mysql_aurora.3.08.0"),
|
|
MasterUsername: awsV2.String(username),
|
|
MasterUserPassword: awsV2.String(password),
|
|
DatabaseName: awsV2.String(database),
|
|
EngineMode: awsV2.String("provisioned"),
|
|
Tags: []types.Tag{{Key: awsV2.String("test"), Value: awsV2.String("rds-data-v2")}},
|
|
})
|
|
if err != nil {
|
|
t.Skipf("RDS MySQL cluster unavailable in this environment: %v", err)
|
|
}
|
|
require.NotNil(t, create.DBCluster)
|
|
resourceArn = awsV2.ToString(create.DBCluster.DBClusterArn)
|
|
require.NotEmpty(t, resourceArn)
|
|
|
|
secret, err := secretsSvc.CreateSecret(ctx, &secretsmanager.CreateSecretInput{
|
|
Name: awsV2.String(secretName),
|
|
SecretString: awsV2.String(fmt.Sprintf(`{"username":%q,"password":%q}`, username, password)),
|
|
})
|
|
require.NoError(t, err)
|
|
secretArn = awsV2.ToString(secret.ARN)
|
|
require.NotEmpty(t, secretArn)
|
|
|
|
executeEventuallyV2(t, ctx, dataSvc, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String(`create table if not exists data_api_v2_items (
|
|
id varchar(64) primary key,
|
|
name varchar(255),
|
|
qty bigint,
|
|
payload blob null,
|
|
active boolean null,
|
|
ratio double null
|
|
)`),
|
|
})
|
|
|
|
tx, err := dataSvc.BeginTransaction(ctx, &rdsdata.BeginTransactionInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, awsV2.ToString(tx.TransactionId))
|
|
|
|
insertOut, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
TransactionId: tx.TransactionId,
|
|
Sql: awsV2.String("insert into data_api_v2_items (id, name, qty, payload, active, ratio) values ('v2-1', 'second client', 12, UNHEX('0A0B'), true, 2.5)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), insertOut.NumberOfRecordsUpdated)
|
|
assert.Nil(t, insertOut.Records, "AWS omits records for a statement that returns no result set")
|
|
|
|
readInTx, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
TransactionId: tx.TransactionId,
|
|
Sql: awsV2.String("select count(*) as count from data_api_v2_items where id = 'v2-1'"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), fieldLongValue(t, readInTx.Records[0][0]))
|
|
|
|
commitOut, err := dataSvc.CommitTransaction(ctx, &rdsdata.CommitTransactionInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
TransactionId: tx.TransactionId,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, awsV2.ToString(commitOut.TransactionStatus))
|
|
|
|
selectOut, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("select name, qty, payload, active, ratio from data_api_v2_items where id = 'v2-1'"),
|
|
IncludeResultMetadata: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, selectOut.ColumnMetadata, 5)
|
|
assert.Equal(t, "name", awsV2.ToString(selectOut.ColumnMetadata[0].Name))
|
|
assert.Equal(t, "name", awsV2.ToString(selectOut.ColumnMetadata[0].Label))
|
|
assert.NotEmpty(t, awsV2.ToString(selectOut.ColumnMetadata[0].TypeName))
|
|
assert.Equal(t, "data_api_v2_items", awsV2.ToString(selectOut.ColumnMetadata[0].TableName))
|
|
require.Len(t, selectOut.Records, 1)
|
|
assert.Equal(t, "second client", fieldStringValue(t, selectOut.Records[0][0]))
|
|
assert.Equal(t, int64(12), fieldLongValue(t, selectOut.Records[0][1]))
|
|
assert.Equal(t, []byte{10, 11}, fieldBlobValue(t, selectOut.Records[0][2]))
|
|
assert.True(t, fieldBooleanValue(t, selectOut.Records[0][3]))
|
|
assert.Equal(t, 2.5, fieldDoubleValue(t, selectOut.Records[0][4]))
|
|
|
|
rollbackTx, err := dataSvc.BeginTransaction(ctx, &rdsdata.BeginTransactionInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
Sql: awsV2.String("insert into data_api_v2_items (id, name, qty) values ('v2-rollback', 'gone', 1)"),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = dataSvc.RollbackTransaction(ctx, &rdsdata.RollbackTransactionInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
Sql: awsV2.String("select 1"),
|
|
})
|
|
assertSmithyErrorCode(t, err, "TransactionNotFoundException")
|
|
|
|
_, err = dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("insert into data_api_v2_items (id, name, qty) values (:id, :name, :qty)"),
|
|
Parameters: []rdsdatatypes.SqlParameter{
|
|
{Name: awsV2.String("id"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "v2-param"}},
|
|
{Name: awsV2.String("name"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "parameterized"}},
|
|
{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 42}},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
paramOut, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("select name, qty from data_api_v2_items where id = :id and qty = :qty"),
|
|
IncludeResultMetadata: true,
|
|
Parameters: []rdsdatatypes.SqlParameter{
|
|
{Name: awsV2.String("id"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "v2-param"}},
|
|
{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 42}},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, paramOut.Records, 1)
|
|
assert.Equal(t, "parameterized", fieldStringValue(t, paramOut.Records[0][0]))
|
|
assert.Equal(t, int64(42), fieldLongValue(t, paramOut.Records[0][1]))
|
|
|
|
batchOut, err := dataSvc.BatchExecuteStatement(ctx, &rdsdata.BatchExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("insert into data_api_v2_items (id, name, qty) values (:id, :name, :qty)"),
|
|
ParameterSets: [][]rdsdatatypes.SqlParameter{
|
|
{
|
|
{Name: awsV2.String("id"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "v2-batch-1"}},
|
|
{Name: awsV2.String("name"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "batch one"}},
|
|
{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 1}},
|
|
},
|
|
{
|
|
{Name: awsV2.String("id"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "v2-batch-2"}},
|
|
{Name: awsV2.String("name"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "batch two"}},
|
|
{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 2}},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, batchOut.UpdateResults, 2)
|
|
|
|
batchCountOut, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("select count(*) as count from data_api_v2_items where id in ('v2-batch-1', 'v2-batch-2')"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), fieldLongValue(t, batchCountOut.Records[0][0]))
|
|
|
|
_, err = dataSvc.BatchExecuteStatement(ctx, &rdsdata.BatchExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("select id from data_api_v2_items where qty = :qty"),
|
|
ParameterSets: [][]rdsdatatypes.SqlParameter{
|
|
{{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 1}}},
|
|
{{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 2}}},
|
|
},
|
|
})
|
|
assertSmithyErrorCode(t, err, "BadRequestException")
|
|
|
|
noSetsOut, err := dataSvc.BatchExecuteStatement(ctx, &rdsdata.BatchExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("insert into data_api_v2_items (id, name, qty) values ('v2-batch-no-sets', 'no sets', 99)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Empty(t, noSetsOut.UpdateResults)
|
|
|
|
noSetsCountOut, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("select count(*) as count from data_api_v2_items where id = 'v2-batch-no-sets'"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), fieldLongValue(t, noSetsCountOut.Records[0][0]))
|
|
|
|
_, err = dataSvc.ExecuteSql(ctx, &rdsdata.ExecuteSqlInput{
|
|
DbClusterOrInstanceArn: awsV2.String(resourceArn),
|
|
AwsSecretStoreArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
SqlStatements: awsV2.String("select 1"),
|
|
})
|
|
assertSmithyErrorCode(t, err, "BadRequestException")
|
|
}
|
|
|
|
// TestRdsDataApiGoSdkPostgresV2 mirrors TestRdsDataApiGoSdkV2 against an
|
|
// aurora-postgresql cluster to verify RDS Data API parity across engines.
|
|
func TestRdsDataApiGoSdkPostgresV2(t *testing.T) {
|
|
ctx := context.Background()
|
|
rdsSvc := testutil.RDSClient()
|
|
secretsSvc := testutil.SecretsManagerClient()
|
|
dataSvc := rdsDataV2Client()
|
|
|
|
clusterID := "go-rds-data-v2-postgres-" + strconv.FormatInt(time.Now().UnixNano(), 36)
|
|
database := "app"
|
|
username := "admin"
|
|
password := "secret123"
|
|
secretName := "go-rds-data-v2-postgres/" + clusterID
|
|
var secretArn string
|
|
var resourceArn string
|
|
|
|
t.Cleanup(func() {
|
|
if secretArn != "" {
|
|
_, _ = secretsSvc.DeleteSecret(ctx, &secretsmanager.DeleteSecretInput{
|
|
SecretId: awsV2.String(secretArn),
|
|
ForceDeleteWithoutRecovery: awsV2.Bool(true),
|
|
})
|
|
}
|
|
if resourceArn != "" {
|
|
_, _ = rdsSvc.DeleteDBCluster(ctx, &rds.DeleteDBClusterInput{
|
|
DBClusterIdentifier: awsV2.String(clusterID),
|
|
SkipFinalSnapshot: awsV2.Bool(true),
|
|
})
|
|
}
|
|
})
|
|
|
|
create, err := rdsSvc.CreateDBCluster(ctx, &rds.CreateDBClusterInput{
|
|
DBClusterIdentifier: awsV2.String(clusterID),
|
|
Engine: awsV2.String("aurora-postgresql"),
|
|
MasterUsername: awsV2.String(username),
|
|
MasterUserPassword: awsV2.String(password),
|
|
DatabaseName: awsV2.String(database),
|
|
EngineMode: awsV2.String("provisioned"),
|
|
Tags: []types.Tag{{Key: awsV2.String("test"), Value: awsV2.String("rds-data-v2-postgres")}},
|
|
})
|
|
if err != nil {
|
|
t.Skipf("RDS PostgreSQL cluster unavailable in this environment: %v", err)
|
|
}
|
|
require.NotNil(t, create.DBCluster)
|
|
resourceArn = awsV2.ToString(create.DBCluster.DBClusterArn)
|
|
require.NotEmpty(t, resourceArn)
|
|
|
|
secret, err := secretsSvc.CreateSecret(ctx, &secretsmanager.CreateSecretInput{
|
|
Name: awsV2.String(secretName),
|
|
SecretString: awsV2.String(fmt.Sprintf(`{"username":%q,"password":%q}`, username, password)),
|
|
})
|
|
require.NoError(t, err)
|
|
secretArn = awsV2.ToString(secret.ARN)
|
|
require.NotEmpty(t, secretArn)
|
|
|
|
executeEventuallyV2(t, ctx, dataSvc, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String(`create table if not exists data_api_v2_items (
|
|
id varchar(64) primary key,
|
|
name varchar(255),
|
|
qty bigint,
|
|
payload bytea null,
|
|
active boolean null,
|
|
ratio double precision null
|
|
)`),
|
|
})
|
|
|
|
tx, err := dataSvc.BeginTransaction(ctx, &rdsdata.BeginTransactionInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, awsV2.ToString(tx.TransactionId))
|
|
|
|
insertOut, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
TransactionId: tx.TransactionId,
|
|
Sql: awsV2.String("insert into data_api_v2_items (id, name, qty, payload, active, ratio) values ('v2-1', 'second client', 12, decode('0A0B', 'hex'), true, 2.5)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), insertOut.NumberOfRecordsUpdated)
|
|
assert.Nil(t, insertOut.Records, "AWS omits records for a statement that returns no result set")
|
|
|
|
readInTx, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
TransactionId: tx.TransactionId,
|
|
Sql: awsV2.String("select count(*) as count from data_api_v2_items where id = 'v2-1'"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), fieldLongValue(t, readInTx.Records[0][0]))
|
|
|
|
commitOut, err := dataSvc.CommitTransaction(ctx, &rdsdata.CommitTransactionInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
TransactionId: tx.TransactionId,
|
|
})
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, awsV2.ToString(commitOut.TransactionStatus))
|
|
|
|
selectOut, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("select name, qty, payload, active, ratio from data_api_v2_items where id = 'v2-1'"),
|
|
IncludeResultMetadata: true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, selectOut.ColumnMetadata, 5)
|
|
assert.Equal(t, "name", awsV2.ToString(selectOut.ColumnMetadata[0].Name))
|
|
assert.Equal(t, "name", awsV2.ToString(selectOut.ColumnMetadata[0].Label))
|
|
assert.NotEmpty(t, awsV2.ToString(selectOut.ColumnMetadata[0].TypeName))
|
|
assert.Equal(t, "data_api_v2_items", awsV2.ToString(selectOut.ColumnMetadata[0].TableName))
|
|
require.Len(t, selectOut.Records, 1)
|
|
assert.Equal(t, "second client", fieldStringValue(t, selectOut.Records[0][0]))
|
|
assert.Equal(t, int64(12), fieldLongValue(t, selectOut.Records[0][1]))
|
|
assert.Equal(t, []byte{10, 11}, fieldBlobValue(t, selectOut.Records[0][2]))
|
|
assert.True(t, fieldBooleanValue(t, selectOut.Records[0][3]))
|
|
assert.Equal(t, 2.5, fieldDoubleValue(t, selectOut.Records[0][4]))
|
|
|
|
rollbackTx, err := dataSvc.BeginTransaction(ctx, &rdsdata.BeginTransactionInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
Sql: awsV2.String("insert into data_api_v2_items (id, name, qty) values ('v2-rollback', 'gone', 1)"),
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = dataSvc.RollbackTransaction(ctx, &rdsdata.RollbackTransactionInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
TransactionId: rollbackTx.TransactionId,
|
|
Sql: awsV2.String("select 1"),
|
|
})
|
|
assertSmithyErrorCode(t, err, "TransactionNotFoundException")
|
|
|
|
_, err = dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("insert into data_api_v2_items (id, name, qty) values (:id, :name, :qty)"),
|
|
Parameters: []rdsdatatypes.SqlParameter{
|
|
{Name: awsV2.String("id"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "v2-param"}},
|
|
{Name: awsV2.String("name"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "parameterized"}},
|
|
{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 42}},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
paramOut, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("select name, qty from data_api_v2_items where id = :id and qty = :qty"),
|
|
IncludeResultMetadata: true,
|
|
Parameters: []rdsdatatypes.SqlParameter{
|
|
{Name: awsV2.String("id"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "v2-param"}},
|
|
{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 42}},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, paramOut.Records, 1)
|
|
assert.Equal(t, "parameterized", fieldStringValue(t, paramOut.Records[0][0]))
|
|
assert.Equal(t, int64(42), fieldLongValue(t, paramOut.Records[0][1]))
|
|
|
|
batchOut, err := dataSvc.BatchExecuteStatement(ctx, &rdsdata.BatchExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("insert into data_api_v2_items (id, name, qty) values (:id, :name, :qty)"),
|
|
ParameterSets: [][]rdsdatatypes.SqlParameter{
|
|
{
|
|
{Name: awsV2.String("id"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "v2-batch-1"}},
|
|
{Name: awsV2.String("name"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "batch one"}},
|
|
{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 1}},
|
|
},
|
|
{
|
|
{Name: awsV2.String("id"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "v2-batch-2"}},
|
|
{Name: awsV2.String("name"), Value: &rdsdatatypes.FieldMemberStringValue{Value: "batch two"}},
|
|
{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 2}},
|
|
},
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, batchOut.UpdateResults, 2)
|
|
|
|
batchCountOut, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("select count(*) as count from data_api_v2_items where id in ('v2-batch-1', 'v2-batch-2')"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(2), fieldLongValue(t, batchCountOut.Records[0][0]))
|
|
|
|
_, err = dataSvc.BatchExecuteStatement(ctx, &rdsdata.BatchExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("select id from data_api_v2_items where qty = :qty"),
|
|
ParameterSets: [][]rdsdatatypes.SqlParameter{
|
|
{{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 1}}},
|
|
{{Name: awsV2.String("qty"), Value: &rdsdatatypes.FieldMemberLongValue{Value: 2}}},
|
|
},
|
|
})
|
|
assertSmithyErrorCode(t, err, "BadRequestException")
|
|
|
|
noSetsOut, err := dataSvc.BatchExecuteStatement(ctx, &rdsdata.BatchExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("insert into data_api_v2_items (id, name, qty) values ('v2-batch-no-sets', 'no sets', 99)"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Empty(t, noSetsOut.UpdateResults)
|
|
|
|
noSetsCountOut, err := dataSvc.ExecuteStatement(ctx, &rdsdata.ExecuteStatementInput{
|
|
ResourceArn: awsV2.String(resourceArn),
|
|
SecretArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
Sql: awsV2.String("select count(*) as count from data_api_v2_items where id = 'v2-batch-no-sets'"),
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), fieldLongValue(t, noSetsCountOut.Records[0][0]))
|
|
|
|
_, err = dataSvc.ExecuteSql(ctx, &rdsdata.ExecuteSqlInput{
|
|
DbClusterOrInstanceArn: awsV2.String(resourceArn),
|
|
AwsSecretStoreArn: awsV2.String(secretArn),
|
|
Database: awsV2.String(database),
|
|
SqlStatements: awsV2.String("select 1"),
|
|
})
|
|
assertSmithyErrorCode(t, err, "BadRequestException")
|
|
}
|
|
|
|
func rdsDataV1Client(t *testing.T) *rdsdataservice.RDSDataService {
|
|
t.Helper()
|
|
sess, err := session.NewSession(&awsV1.Config{
|
|
Region: awsV1.String("us-east-1"),
|
|
Endpoint: awsV1.String(testutil.Endpoint()),
|
|
DisableSSL: awsV1.Bool(true),
|
|
Credentials: credentials.NewStaticCredentials("test", "test", ""),
|
|
})
|
|
require.NoError(t, err)
|
|
return rdsdataservice.New(sess)
|
|
}
|
|
|
|
func rdsDataV2Client() *rdsdata.Client {
|
|
return rdsdata.NewFromConfig(testutil.Config())
|
|
}
|
|
|
|
func executeEventually(t *testing.T, svc *rdsdataservice.RDSDataService, input *rdsdataservice.ExecuteStatementInput) {
|
|
t.Helper()
|
|
require.Eventually(t, func() bool {
|
|
_, err := svc.ExecuteStatement(input)
|
|
return err == nil
|
|
}, 90*time.Second, time.Second)
|
|
}
|
|
|
|
func executeEventuallyV2(t *testing.T, ctx context.Context, svc *rdsdata.Client, input *rdsdata.ExecuteStatementInput) {
|
|
t.Helper()
|
|
require.Eventually(t, func() bool {
|
|
_, err := svc.ExecuteStatement(ctx, input)
|
|
return err == nil
|
|
}, 90*time.Second, time.Second)
|
|
}
|
|
|
|
func assertAwsErrorCode(t *testing.T, err error, code string) {
|
|
t.Helper()
|
|
require.Error(t, err)
|
|
var awsErr awserr.Error
|
|
require.ErrorAs(t, err, &awsErr)
|
|
assert.Equal(t, code, awsErr.Code())
|
|
}
|
|
|
|
func assertSmithyErrorCode(t *testing.T, err error, code string) {
|
|
t.Helper()
|
|
require.Error(t, err)
|
|
var apiErr smithy.APIError
|
|
require.ErrorAs(t, err, &apiErr)
|
|
assert.Equal(t, code, apiErr.ErrorCode())
|
|
}
|
|
|
|
func fieldStringValue(t *testing.T, field rdsdatatypes.Field) string {
|
|
t.Helper()
|
|
value, ok := field.(*rdsdatatypes.FieldMemberStringValue)
|
|
require.Truef(t, ok, "expected stringValue field, got %T", field)
|
|
return value.Value
|
|
}
|
|
|
|
func fieldLongValue(t *testing.T, field rdsdatatypes.Field) int64 {
|
|
t.Helper()
|
|
value, ok := field.(*rdsdatatypes.FieldMemberLongValue)
|
|
require.Truef(t, ok, "expected longValue field, got %T", field)
|
|
return value.Value
|
|
}
|
|
|
|
func fieldBlobValue(t *testing.T, field rdsdatatypes.Field) []byte {
|
|
t.Helper()
|
|
value, ok := field.(*rdsdatatypes.FieldMemberBlobValue)
|
|
require.Truef(t, ok, "expected blobValue field, got %T", field)
|
|
return value.Value
|
|
}
|
|
|
|
func fieldBooleanValue(t *testing.T, field rdsdatatypes.Field) bool {
|
|
t.Helper()
|
|
value, ok := field.(*rdsdatatypes.FieldMemberBooleanValue)
|
|
require.Truef(t, ok, "expected booleanValue field, got %T", field)
|
|
return value.Value
|
|
}
|
|
|
|
func fieldDoubleValue(t *testing.T, field rdsdatatypes.Field) float64 {
|
|
t.Helper()
|
|
value, ok := field.(*rdsdatatypes.FieldMemberDoubleValue)
|
|
require.Truef(t, ok, "expected doubleValue field, got %T", field)
|
|
return value.Value
|
|
}
|