Skip to content

Commit e9b8115

Browse files
committed
Add aws_rdsdata_query resource
1 parent a60118b commit e9b8115

File tree

5 files changed

+326
-3
lines changed

5 files changed

+326
-3
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package rdsdata
5+
6+
import (
7+
"context"
8+
9+
"github.com/aws/aws-sdk-go-v2/service/rdsdata"
10+
rdsdatatypes "github.com/aws/aws-sdk-go-v2/service/rdsdata/types"
11+
"github.com/hashicorp/terraform-plugin-framework/resource"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
14+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
15+
"github.com/hashicorp/terraform-plugin-framework/types"
16+
"github.com/hashicorp/terraform-provider-aws/internal/framework"
17+
"github.com/hashicorp/terraform-provider-aws/names"
18+
)
19+
20+
// @FrameworkResource("aws_rdsdata_query", name="Query")
21+
func newResourceQuery(context.Context) (resource.ResourceWithConfigure, error) {
22+
return &resourceQuery{}, nil
23+
}
24+
25+
type resourceQuery struct {
26+
framework.ResourceWithModel[resourceQueryModel]
27+
}
28+
29+
func (r *resourceQuery) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
30+
resp.Schema = schema.Schema{
31+
Attributes: map[string]schema.Attribute{
32+
names.AttrID: framework.IDAttribute(),
33+
names.AttrDatabase: schema.StringAttribute{
34+
Optional: true,
35+
PlanModifiers: []planmodifier.String{
36+
stringplanmodifier.RequiresReplace(),
37+
},
38+
},
39+
names.AttrResourceARN: schema.StringAttribute{
40+
Required: true,
41+
PlanModifiers: []planmodifier.String{
42+
stringplanmodifier.RequiresReplace(),
43+
},
44+
},
45+
"secret_arn": schema.StringAttribute{
46+
Required: true,
47+
PlanModifiers: []planmodifier.String{
48+
stringplanmodifier.RequiresReplace(),
49+
},
50+
},
51+
"sql": schema.StringAttribute{
52+
Required: true,
53+
PlanModifiers: []planmodifier.String{
54+
stringplanmodifier.RequiresReplace(),
55+
},
56+
},
57+
"records": schema.StringAttribute{
58+
Computed: true,
59+
},
60+
"number_of_records_updated": schema.Int64Attribute{
61+
Computed: true,
62+
},
63+
},
64+
Blocks: map[string]schema.Block{
65+
names.AttrParameters: schema.ListNestedBlock{
66+
NestedObject: schema.NestedBlockObject{
67+
Attributes: map[string]schema.Attribute{
68+
names.AttrName: schema.StringAttribute{
69+
Required: true,
70+
},
71+
names.AttrValue: schema.StringAttribute{
72+
Required: true,
73+
},
74+
"type_hint": schema.StringAttribute{
75+
Optional: true,
76+
},
77+
},
78+
},
79+
},
80+
},
81+
}
82+
}
83+
84+
type resourceQueryModel struct {
85+
framework.WithRegionModel
86+
ID types.String `tfsdk:"id"`
87+
Database types.String `tfsdk:"database"`
88+
ResourceARN types.String `tfsdk:"resource_arn"`
89+
SecretARN types.String `tfsdk:"secret_arn"`
90+
SQL types.String `tfsdk:"sql"`
91+
Parameters []resourceQueryParameterModel `tfsdk:"parameters"`
92+
Records types.String `tfsdk:"records"`
93+
NumberOfRecordsUpdated types.Int64 `tfsdk:"number_of_records_updated"`
94+
}
95+
96+
type resourceQueryParameterModel struct {
97+
Name types.String `tfsdk:"name"`
98+
Value types.String `tfsdk:"value"`
99+
TypeHint types.String `tfsdk:"type_hint"`
100+
}
101+
102+
func (r *resourceQuery) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
103+
var data resourceQueryModel
104+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
105+
if resp.Diagnostics.HasError() {
106+
return
107+
}
108+
109+
conn := r.Meta().RDSDataClient(ctx)
110+
111+
input := rdsdata.ExecuteStatementInput{
112+
ResourceArn: data.ResourceARN.ValueStringPointer(),
113+
SecretArn: data.SecretARN.ValueStringPointer(),
114+
Sql: data.SQL.ValueStringPointer(),
115+
FormatRecordsAs: rdsdatatypes.RecordsFormatTypeJson,
116+
}
117+
118+
if !data.Database.IsNull() {
119+
input.Database = data.Database.ValueStringPointer()
120+
}
121+
122+
if len(data.Parameters) > 0 {
123+
// Convert resource parameter model to data source parameter model for compatibility
124+
var params []dataSourceQueryParameterModel
125+
for _, p := range data.Parameters {
126+
params = append(params, dataSourceQueryParameterModel(p))
127+
}
128+
input.Parameters = expandSQLParameters(params)
129+
}
130+
131+
output, err := conn.ExecuteStatement(ctx, &input)
132+
if err != nil {
133+
resp.Diagnostics.AddError("executing RDS Data API statement", err.Error())
134+
return
135+
}
136+
137+
data.ID = types.StringValue(data.ResourceARN.ValueString() + ":" + data.SQL.ValueString())
138+
data.Records = types.StringPointerValue(output.FormattedRecords)
139+
data.NumberOfRecordsUpdated = types.Int64Value(output.NumberOfRecordsUpdated)
140+
141+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
142+
}
143+
144+
func (r *resourceQuery) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
145+
// No-op: query results are stored in state and don't need to be refreshed
146+
}
147+
148+
func (r *resourceQuery) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
149+
// No-op: all changes require replacement
150+
}
151+
152+
func (r *resourceQuery) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
153+
// No-op: no API call needed, just remove from state
154+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package rdsdata_test
5+
6+
import (
7+
"testing"
8+
9+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
10+
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
11+
"github.com/hashicorp/terraform-provider-aws/names"
12+
)
13+
14+
func TestAccRDSDataQueryResource_basic(t *testing.T) {
15+
ctx := acctest.Context(t)
16+
rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
17+
resourceName := "aws_rdsdata_query.test"
18+
19+
resource.ParallelTest(t, resource.TestCase{
20+
PreCheck: func() { acctest.PreCheck(ctx, t) },
21+
ErrorCheck: acctest.ErrorCheck(t, names.RDSDataServiceID),
22+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
23+
Steps: []resource.TestStep{
24+
{
25+
Config: testAccQueryResourceConfig_basic(rName),
26+
Check: resource.ComposeTestCheckFunc(
27+
resource.TestCheckResourceAttrSet(resourceName, names.AttrID),
28+
resource.TestCheckResourceAttr(resourceName, "records", "[{\"1\":1}]"),
29+
resource.TestCheckResourceAttr(resourceName, "number_of_records_updated", "0"),
30+
),
31+
},
32+
},
33+
})
34+
}
35+
36+
func TestAccRDSDataQueryResource_withParameters(t *testing.T) {
37+
ctx := acctest.Context(t)
38+
rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
39+
resourceName := "aws_rdsdata_query.test"
40+
41+
resource.ParallelTest(t, resource.TestCase{
42+
PreCheck: func() { acctest.PreCheck(ctx, t) },
43+
ErrorCheck: acctest.ErrorCheck(t, names.RDSDataServiceID),
44+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
45+
Steps: []resource.TestStep{
46+
{
47+
Config: testAccQueryResourceConfig_withParameters(rName),
48+
Check: resource.ComposeTestCheckFunc(
49+
resource.TestCheckResourceAttrSet(resourceName, names.AttrID),
50+
resource.TestCheckResourceAttrSet(resourceName, "records"),
51+
resource.TestCheckResourceAttr(resourceName, "number_of_records_updated", "0"),
52+
),
53+
},
54+
},
55+
})
56+
}
57+
58+
func testAccQueryResourceConfig_basic(rName string) string {
59+
return acctest.ConfigCompose(testAccQueryDataSourceConfig_base(rName), `
60+
resource "aws_rdsdata_query" "test" {
61+
depends_on = [aws_rds_cluster_instance.test]
62+
resource_arn = aws_rds_cluster.test.arn
63+
secret_arn = aws_secretsmanager_secret_version.test.arn
64+
sql = "SELECT 1"
65+
}
66+
`)
67+
}
68+
69+
func testAccQueryResourceConfig_withParameters(rName string) string {
70+
return acctest.ConfigCompose(testAccQueryDataSourceConfig_base(rName), `
71+
resource "aws_rdsdata_query" "test" {
72+
depends_on = [aws_rds_cluster_instance.test]
73+
resource_arn = aws_rds_cluster.test.arn
74+
secret_arn = aws_secretsmanager_secret_version.test.arn
75+
sql = "SELECT * FROM information_schema.tables WHERE table_name = :table_name"
76+
database = aws_rds_cluster.test.database_name
77+
78+
parameters {
79+
name = "table_name"
80+
value = "test_table"
81+
}
82+
}
83+
`)
84+
}

internal/service/rdsdata/service_package_gen.go

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

website/docs/d/rdsdata_query.html.markdown

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ subcategory: "RDS Data"
33
layout: "aws"
44
page_title: "AWS: aws_rdsdata_query"
55
description: |-
6-
Executes SQL queries against RDS Aurora Serverless clusters using the RDS Data API.
6+
Executes SQL queries against RDS clusters using the RDS Data API.
77
---
88

99
# Data Source: aws_rdsdata_query
1010

11-
Executes SQL queries against RDS Aurora Serverless clusters using the RDS Data API. This data source allows you to run SQL statements and retrieve results in JSON format.
11+
Executes SQL queries against RDS clusters using the RDS Data API. This data source allows you to run SQL statements and retrieve results in JSON format.
12+
13+
~> **Note:** This data source is ideal for SELECT queries that need to be executed multiple times during Terraform operations. For one-time operations like DDL statements, INSERT, UPDATE, or DELETE operations, consider using the [`aws_rdsdata_query` resource](/docs/providers/aws/r/rdsdata_query.html) instead.
1214

1315
## Example Usage
1416

website/docs/r/rdsdata_query.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
subcategory: "RDS (Relational Database)"
3+
layout: "aws"
4+
page_title: "AWS: aws_rdsdata_query"
5+
description: |-
6+
Executes a SQL query against an RDS cluster using the RDS Data API.
7+
---
8+
9+
# Resource: aws_rdsdata_query
10+
11+
Executes a SQL query against an RDS cluster using the RDS Data API. The query is executed once during resource creation, and any changes to the query or parameters will trigger a replacement.
12+
13+
~> **Note:** For queries that need to be executed multiple times or for retrieving data (SELECT queries), consider using the [`aws_rdsdata_query` data source](/docs/providers/aws/d/rdsdata_query.html) instead. Use this resource for one-time operations like DDL statements, INSERT, UPDATE, or DELETE operations.
14+
15+
## Example Usage
16+
17+
### Basic Usage
18+
19+
```terraform
20+
resource "aws_rdsdata_query" "example" {
21+
resource_arn = aws_rds_cluster.example.arn
22+
secret_arn = aws_secretsmanager_secret.example.arn
23+
sql = "SELECT * FROM users WHERE active = true"
24+
database = "mydb"
25+
}
26+
```
27+
28+
### With Parameters
29+
30+
```terraform
31+
resource "aws_rdsdata_query" "example" {
32+
resource_arn = aws_rds_cluster.example.arn
33+
secret_arn = aws_secretsmanager_secret.example.arn
34+
sql = "INSERT INTO users (name, email) VALUES (:name, :email)"
35+
database = "mydb"
36+
37+
parameters {
38+
name = "name"
39+
value = "John Doe"
40+
}
41+
42+
parameters {
43+
name = "email"
44+
45+
}
46+
}
47+
```
48+
49+
## Argument Reference
50+
51+
This resource supports the following arguments:
52+
53+
* `resource_arn` - (Required) The Amazon Resource Name (ARN) of the RDS cluster.
54+
* `secret_arn` - (Required) The ARN of the secret that enables access to the DB cluster.
55+
* `sql` - (Required) The SQL statement to execute.
56+
* `database` - (Optional) The name of the database.
57+
* `parameters` - (Optional) Parameters for the SQL statement. See [parameters](#parameters) below.
58+
* `region` - (Optional) The AWS region.
59+
60+
### parameters
61+
62+
* `name` - (Required) The name of the parameter.
63+
* `value` - (Required) The value of the parameter.
64+
* `type_hint` - (Optional) A hint that specifies the correct object type for the parameter value.
65+
66+
## Attribute Reference
67+
68+
This resource exports the following attributes in addition to the arguments above:
69+
70+
* `id` - The resource identifier.
71+
* `records` - The records returned by the SQL statement in JSON format.
72+
* `number_of_records_updated` - The number of records updated by the statement.
73+
74+
## Import
75+
76+
You cannot import this resource.

0 commit comments

Comments
 (0)