Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/dotnet-npgsql-integ-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
dotnet-version: '9.0.x'

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
Expand All @@ -48,10 +48,10 @@ jobs:
aws-region: us-east-1

- name: Configure and run integration for npgsql
working-directory: ./dotnet/npgsql/ExampleTest.Tests
working-directory: ./dotnet/npgsql
env:
CLUSTER_USER: "admin"
CLUSTER_ENDPOINT: ${{ secrets.DOTNET_NPGSQL_CLUSTER_ENDPOINT }}
REGION: ${{ secrets.DOTNET_NPGSQL_CLUSTER_REGION }}
run: |
dotnet restore
dotnet test
dotnet run --framework net9.0
140 changes: 103 additions & 37 deletions dotnet/npgsql/Example.cs
Original file line number Diff line number Diff line change
@@ -1,57 +1,123 @@
using Npgsql;
using System.Diagnostics;
using Amazon;
using Amazon.DSQL.Util;
using Amazon.Runtime;
using Amazon.Runtime.Credentials;
using Npgsql;

class Example
namespace example;

internal static class Example
{
public static async Task Run(string clusterEndpoint)
private static async Task<NpgsqlConnection> CreateConnection(
string clusterUser, string clusterEndpoint, string regionName)
{
RegionEndpoint region = RegionEndpoint.USEast1;

// Connect to a PostgreSQL database.
const string username = "admin";

// The token expiration time is optional, and the default value 900 seconds
AWSCredentials awsCredentials = FallbackCredentialsFactory.GetCredentials();
string password = DSQLAuthTokenGenerator.GenerateDbConnectAdminAuthToken(awsCredentials, region, clusterEndpoint);

const string database = "postgres";
var connString = "Host=" + clusterEndpoint + ";Username=" + username + ";Password=" + password + ";Database=" + database + ";Port=" + 5432 + ";SSLMode=VerifyFull;";

var conn = new NpgsqlConnection(connString);
var region = RegionEndpoint.GetBySystemName(regionName);

AWSCredentials awsCredentials = await DefaultAWSCredentialsIdentityResolver.GetCredentialsAsync();

// Generate a fresh password token for each connection, to ensure the token is not expired when the connection
// is established
string password;
string schema;
if (clusterUser == "admin")
{
password = await DSQLAuthTokenGenerator.GenerateDbConnectAdminAuthTokenAsync(
awsCredentials, region, clusterEndpoint);
schema = "public";
}
else
{
password =
await DSQLAuthTokenGenerator.GenerateDbConnectAuthTokenAsync(awsCredentials, region, clusterEndpoint);
schema = "myschema";
}

var connBuilder = new NpgsqlConnectionStringBuilder
{
Host = clusterEndpoint,
Port = 5432,
SslMode = SslMode.VerifyFull,
Database = "postgres",
Username = clusterUser,
Password = password,
IncludeErrorDetail = true
};

var conn = new NpgsqlConnection(connBuilder.ConnectionString);
await conn.OpenAsync();

// Create a table.
using var create = new NpgsqlCommand("CREATE TABLE IF NOT EXISTS owner (id UUID PRIMARY KEY, name VARCHAR(30) NOT NULL, city VARCHAR(80) NOT NULL, telephone VARCHAR(20))", conn);
try
{
using var setSearchPath = new NpgsqlCommand($"SET search_path = {schema}", conn);
setSearchPath.ExecuteNonQuery();
}
catch
{
await conn.CloseAsync();
throw;
}

return conn;
}

private static async Task ExerciseConnection(NpgsqlConnection conn)
{
using var create =
new NpgsqlCommand(@"
CREATE TABLE IF NOT EXISTS owner (
id UUID NOT NULL DEFAULT gen_random_uuid(),
name VARCHAR(30) NOT NULL,
city VARCHAR(80) NOT NULL,
telephone VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (id)
)",
conn);
create.ExecuteNonQuery();

// Create an owner.
var uuid = Guid.NewGuid();
using var insert = new NpgsqlCommand("INSERT INTO owner(id, name, city, telephone) VALUES(@id, @name, @city, @telephone)", conn);
insert.Parameters.AddWithValue("id", uuid);
// Insert some data
using var insert = new NpgsqlCommand(
"INSERT INTO owner(name, city, telephone) VALUES(@name, @city, @telephone)", conn);
insert.Parameters.AddWithValue("name", "John Doe");
insert.Parameters.AddWithValue("city", "Anytown");

insert.Parameters.AddWithValue("telephone", "555-555-0190");

insert.Parameters.AddWithValue("telephone", "555-555-1999");
insert.ExecuteNonQuery();

// Read the owner.
using var select = new NpgsqlCommand("SELECT * FROM owner where id=@id", conn);
select.Parameters.AddWithValue("id", uuid);
using var reader = await select.ExecuteReaderAsync();
System.Diagnostics.Debug.Assert(reader.HasRows, "no owner found");
using var select = new NpgsqlCommand("SELECT * FROM owner where name=@name", conn);
select.Parameters.AddWithValue("name", "John Doe");
await using var reader = await select.ExecuteReaderAsync();

System.Diagnostics.Debug.WriteLine(reader.Read());
Debug.Assert(reader.HasRows, "no owner found");
await reader.ReadAsync();

reader.Close();
// Verify the result we got is what we inserted before
Debug.Assert(!reader.IsDBNull(reader.GetOrdinal("id")), "id is null");
Debug.Assert(reader.GetString(reader.GetOrdinal("name")) == "John Doe", "name doesn't match");
Debug.Assert(reader.GetString(reader.GetOrdinal("city")) == "Anytown", "city doesn't match");
Debug.Assert(reader.GetString(reader.GetOrdinal("telephone")) == "555-555-1999", "telephone doesn't match");
await reader.CloseAsync();

using var delete = new NpgsqlCommand("DELETE FROM owner where id=@id", conn);
select.Parameters.AddWithValue("id", uuid);
// Clean up the table after the example. If we run the example again we do not have to worry about data inserted
// by previous runs
using var delete = new NpgsqlCommand("DELETE FROM owner where name=@name", conn);
select.Parameters.AddWithValue("name", "John Doe");
select.ExecuteNonQuery();
}

public static async Task Main()
{
var clusterUser = Environment.GetEnvironmentVariable("CLUSTER_USER");
Debug.Assert(clusterUser != null, "CLUSTER_USER must be set");

var clusterEndpoint = Environment.GetEnvironmentVariable("CLUSTER_ENDPOINT");
Debug.Assert(clusterEndpoint != null, "CLUSTER_ENDPOINT must be set");

var region = Environment.GetEnvironmentVariable("REGION");
Debug.Assert(region != null, "REGION must be set");

await using var conn = await CreateConnection(clusterUser!, clusterEndpoint!, region!);
await ExerciseConnection(conn);

// Close the connection.
conn.Close();
Console.WriteLine("Connection exercised successfully");
}
}
}
28 changes: 0 additions & 28 deletions dotnet/npgsql/ExampleTest.Tests/ExampleTest.Tests.csproj

This file was deleted.

9 changes: 0 additions & 9 deletions dotnet/npgsql/ExampleTest.Tests/ExampleTest.cs

This file was deleted.

93 changes: 77 additions & 16 deletions dotnet/npgsql/README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,94 @@
# Aurora DSQL npgsql code examples
# Npgsql with Aurora DSQL

## Overview

The code examples in this topic show you how to use the .NET work with Aurora DSQL.
This code example demonstrates how to use Npgsql with Amazon Aurora SQL (DSQL). The example shows you how to connect to
an Aurora DSQL cluster and perform basic database operations.

## Run the examples
Aurora DSQL is a distributed SQL database service that provides high availability and scalability for your
PostgreSQL-compatible applications. Npgsql is a popular PostgreSQL adapter for .NET that allows you to interact with
PostgreSQL databases using C# code.

## About the code example

The example demonstrates a flexible connection approach that works for both admin and non-admin users:

* When connecting as an **admin user**, the example uses the `public` schema and generates an admin authentication
token.
* When connecting as a **non-admin user**, the example uses a custom `myschema` schema and generates a standard
authentication token.

The code automatically detects the user type and adjusts its behavior accordingly.

## ⚠️ Important

* Running this code might result in charges to your AWS account.
* We recommend that you grant your code least privilege. At most, grant only the
minimum permissions required to perform the task. For more information, see
[Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege).
* This code is not tested in every AWS Region. For more information, see
[AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services).

## Run the example

### Prerequisites

* dotnet version >=8.0.0 is needed
* You must have an AWS account, and have your default credentials and AWS Region
configured as described in the
[Globally configuring AWS SDKs and tools](https://docs.aws.amazon.com/credref/latest/refdocs/creds-config-files.html)
guide.
* [.NET 9.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) or later.
* You must have an Aurora DSQL cluster. For information about creating an Aurora DSQL cluster, see the
[Getting started with Aurora DSQL](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/getting-started.html)
guide.
* If connecting as a non-admin user, ensure the user is linked to an IAM role and is granted access to the `myschema`
schema. See the
[Using database roles with IAM roles](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/using-database-and-iam-roles.html)
guide.

### Run the example tests
### Run the code

```sh
# Use the account credentials dedicated for dotnet
export CLUSTER_ENDPOINT="<your cluster endpoint from us-east-1>"
cd ExampleTest.Tests
dotnet test
```
The example demonstrates the following operations:

- Opening a connection to an Aurora DSQL cluster
- Creating a table
- Inserting and querying data

The example is designed to work with both admin and non-admin users:

### Trouble-shooting
- When run as an admin user, it uses the `public` schema
- When run as a non-admin user, it uses the `myschema` schema

Below error on Cloud Desktop can be resolved by running `export DOTNET_NUGET_SIGNATURE_VERIFICATION=false`.
**Note:** running the example will use actual resources in your AWS account and may incur charges.

Set environment variables for your cluster details:

```bash
# e.g. "admin"
export CLUSTER_USER="<your user>"

# e.g. "foo0bar1baz2quux3quuux4.dsql.us-east-1.on.aws"
export CLUSTER_ENDPOINT="<your endpoint>"

# e.g. "us-east-1"
export REGION="<your region>"
```
error NU3018: Package 'System.Runtime.InteropServices 4.3.0' from source 'https://api.nuget.org/v3/index.json': The repository primary signature's signing certificate is not trusted by the trust provider.

Run the example:

```bash
dotnet run --framework net9.0
```

The example contains comments explaining the code and the operations being performed.

## Additional resources

* [Amazon Aurora DSQL Documentation](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/what-is-aurora-dsql.html)
* [Npgsql Documentation](https://www.npgsql.org/)

---

Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
SPDX-License-Identifier: MIT-0
33 changes: 17 additions & 16 deletions dotnet/npgsql/example.csproj
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>

<ItemGroup>
<PackageReference Include="AWSSDK.Core" Version="4.0.0-preview.4" />
<PackageReference Include="AWSSDK.Dsql" Version="3.7.401" />
<PackageReference Include="AWSSDK.Extensions.CrtIntegration" Version="4.0.0-preview.4" />
<PackageReference Include="AWSSDK.Extensions.NETCore.Setup" Version="4.0.0-preview.4" />
<PackageReference Include="Npgsql" Version="8.0.5" />
<PackageReference Include="xunit" Version="2.9.2" />
</ItemGroup>
<!-- We target both for compatibility -->
<TargetFrameworks>netstandard2.0;net9.0</TargetFrameworks>
<LangVersion>10</LangVersion>

<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.Core" Version="4.0.0.3"/>
<PackageReference Include="AWSSDK.DSQL" Version="4.0.1"/>
<PackageReference Include="Npgsql" Version="8.0.5"/>
</ItemGroup>

</Project>
Loading