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
1 change: 1 addition & 0 deletions .github/workflows/cpp-libpq-integ-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ jobs:
env:
CLUSTER_ENDPOINT: ${{ secrets.CPP_LIBPQ_CLUSTER_ENDPOINT }}
REGION: ${{ secrets.CPP_LIBPQ_CLUSTER_REGION }}
CLUSTER_USER: admin
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also do a second pass that runs with non-admin user also?

Copy link
Copy Markdown
Contributor Author

@leszek-bq leszek-bq May 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently don't do that for other languages. This could be next week addition.
This would not change code examples.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's create Taskei to ensure we don't forget this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll create a taskei ticket to track this for all languages.

LD_LIBRARY_PATH: /lib/x86_64-linux-gnu:/home/runner/work/aurora-dsql-samples/aurora-dsql-samples/cpp/libpq/aws-sdk-install/lib
run: |
./libpq_example
100 changes: 78 additions & 22 deletions cpp/libpq/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,52 @@
# Aurora DSQL Libpq code examples
# Libpq with Aurora DSQL

## Overview

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

Aurora DSQL is a distributed SQL database service that provides high availability and scalability for
your PostgreSQL-compatible applications. Libpq is a popular PostgreSQL library that allows
you to interact with PostgreSQL databases using c/cpp 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 `myschema` schema needs to be created prior to running the example and the **non-admin user** needs to be granted access to the schema.

The code automatically detects the user type and adjusts its behavior accordingly.
The example contains comments explaining the code and the operations being performed.

## ⚠️ 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

* 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.
* 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.

## Prerequisites

#### C++ compiler
A c++ compiler that supports c++11 standard or newer.
Expand All @@ -21,17 +63,19 @@ If you're building the SDK from source and you only need it for dsql you may use
For example:

```
cmake ../aws-sdk-cpp -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DBUILD_ONLY="dsql"
```
cmake <your_path>/aws-sdk-cpp -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=<your_path_to_aws-sdk-install> -DBUILD_ONLY="dsql"

# Note: Follow build and installation instructions on the official website.
# This example is meant to point to the -DBUILD_ONLY="dsql" flag.
```

#### Libpq library and Postgres include files

- The path to the Libpq library and include files will need to be specified for compilation
- The path to the Libpq library will need to be specified for execution
- Obtaining Libpq library
- It is installed with postgres installation. Therefore, if postgres is installed on the system the libpq is present in ../postgres_install_dir/lib, ../postgres_install_dir/include
- It is installed when psql client program is installed, similarily as with postgres installation
- It is installed when psql client program is installed, similarly as with postgres installation
- On some systems libpq can be installed through package manager (if the package exists for the system) e.g.
```
sudo yum install libpq-devel
Expand All @@ -55,9 +99,6 @@ cmake ../aws-sdk-cpp -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/local/
- On some systems the SSL libraries can be installed using package managers
- They can be downloaded from the [official website](https://openssl-library.org/source/index.html)


## Run the examples

### Build the example program

#### Edit the Makefile file
Expand All @@ -69,11 +110,10 @@ The Makefile is located in the libpq/src directory.
Update the following variables with the paths to the aws-sdk-cpp include and library files on your computer:

```
AWS_INC_DIR=-I ../aws-sdk-install/include
AWS_LIB_DIR=-L ../aws-sdk-install/lib
AWS_INC_DIR=-I <your_path_to_aws-sdk-install>/include
AWS_LIB_DIR=-L <your_path_to_aws-sdk-install>/lib
```


##### Linux

Edit the variables specifying path to the postgres include files and path to the location of the libpq library.
Expand All @@ -96,16 +136,26 @@ pg_config --libdir

##### Mac

The Mac related variables are in the 'Mac' section of the make file.
If necessary, adjust the directory locations.
For example:
The Mac related variables are in the 'Mac' section of the Makefile.
Edit the variables specifying path to the postgres include files and path to the location of the libpq library as well as the compiler include directory.

**Note:** These are examples only. Replace them with your path.

```
COMPILER_INC_DIR_MAC=-I /Library/Developer/CommandLineTools/SDKs/MacOSX14.5.sdk/usr/include/c++/v1
(x86)
PG_INC_DIR_MAC=-I /usr/local/opt/libpq/include
LIBPQ_DIR_MAC=-L /usr/local/opt/libpq/lib
OR
(brew)
PG_INC_DIR_MAC=-I /opt/homebrew/opt/postgresql@16/include
LIBPQ_DIR_MAC=-L /opt/homebrew/opt/postgresql@16/lib

COMPILER_INC_DIR_MAC=-I /Library/Developer/CommandLineTools/SDKs/MacOSX14.x.sdk/usr/include/c++/v1

# or could be

# could be
COMPILER_INC_DIR_MAC=-I /Library/Developer/CommandLineTools/SDKs/MacOSX15.x.sdk/usr/include/c++/v1

COMPILER_INC_DIR_MAC=-I /Library/Developer/CommandLineTools/SDKs/MacOSX15.5.sdk/usr/include/c++/v1
```

#### Build the program
Expand All @@ -124,7 +174,7 @@ make libpq_example
make libpq_example_mac
```

This should result in the libpq_example executable program
This should result in the **libpq_example** executable program

### Run the example program

Expand All @@ -140,19 +190,25 @@ Replace the paths in the commands below with the path on your computer.

```
export LD_LIBRARY_PATH="/usr/local/pgsql/lib:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH="<your_path>/aws-sdk-install/lib:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH="<your_path_to_aws-sdk-install>/lib:$LD_LIBRARY_PATH"
```

##### Mac

```
export DYLD_FALLBACK_LIBRARY_PATH=<your_path>/aws-sdk-install/lib
export DYLD_FALLBACK_LIBRARY_PATH=<your_path_to_aws-sdk-install>/lib
```

#### Set environment variables specifying cluster endpoint and region

```
# e.g. 'admin' or a custom user
export CLUSTER_USER=<your cluster user>

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

# e.g. "us-east-1"
export REGION="<your cluster region>"
```

Expand All @@ -170,7 +226,7 @@ From the libpq/src directory run:

Aurora DSQL requires SSL when connecting to it. Therefore, the libpq library must have been built with SSL support.

If this is not the case, you may see the following error messgage while executing the libpq_example program:
If this is not the case, you may see the following error message while executing the libpq_example program:

>
>Error while connecting to the database server: sslmode value "require" invalid when SSL support is not compiled in.
Expand Down
48 changes: 35 additions & 13 deletions cpp/libpq/src/libpq_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ using namespace Aws;
using namespace Aws::DSQL;
using namespace Aws::DSQL::Model;

std::string generateDBAuthToken(const std::string endpoint, const std::string region) {
std::string generateDBAuthToken(const std::string clusterUser, const std::string clusterEndpoint, const std::string region) {
Aws::SDKOptions options;
Aws::InitAPI(options);
DSQLClientConfiguration clientConfig;
Expand All @@ -17,8 +17,9 @@ std::string generateDBAuthToken(const std::string endpoint, const std::string re
std::string token = "";

// The token expiration time is optional, and the default value 900 seconds
// If you aren not using admin role to connect, use GenerateDBConnectAuthToken instead
const auto presignedString = client.GenerateDBConnectAdminAuthToken(endpoint, region);
const auto presignedString = clusterUser == "admin" ? client.GenerateDBConnectAdminAuthToken(clusterEndpoint, region) :
client.GenerateDBConnectAuthToken(clusterEndpoint, region);

if (presignedString.IsSuccess()) {
token = presignedString.GetResult();
} else {
Expand All @@ -29,36 +30,42 @@ std::string generateDBAuthToken(const std::string endpoint, const std::string re
return token;
}

PGconn* connectToCluster(std::string clusterEndpoint, std::string region) {
std::string password = generateDBAuthToken(clusterEndpoint, region);

PGconn* connectToCluster(std::string clusterUser, std::string clusterEndpoint, std::string region) {
std::string dbname = "postgres";
std::string user = "admin";
std::string sslmode = "require";
int port = 5432;

if (password.empty()) {
// Generate a fresh password token for each connection, to ensure the token is not expired
// when the connection is established
std::string password_token = generateDBAuthToken(clusterUser, clusterEndpoint, region);

if (password_token.empty()) {
std::cerr << "Failed to generate token." << std::endl;
return NULL;
}

char conninfo[4096];
sprintf(conninfo, "dbname=%s user=%s host=%s port=%i sslmode=%s password=%s",
dbname.c_str(), user.c_str(), clusterEndpoint.c_str(), port, sslmode.c_str(), password.c_str());
dbname.c_str(), clusterUser.c_str(), clusterEndpoint.c_str(), port, sslmode.c_str(), password_token.c_str());

PGconn *conn = PQconnectdb(conninfo);

if (PQstatus(conn) != CONNECTION_OK) {
std::cerr << "Error while connecting to the database server: " << PQerrorMessage(conn) << std::endl;
PQfinish(conn);
return NULL;
return NULL;
}

std::cout << std::endl << "Connection Established: " << std::endl;
std::cout << "Port: " << PQport(conn) << std::endl;
// std::cout << "Host: " << PQhost(conn) << std::endl;
std::cout << "DBName: " << PQdb(conn) << std::endl;

if (clusterUser != "admin")
{
PGresult *result = PQexec(conn, "SET search_path = myschema");
PQclear(result);
}

return conn;
}

Expand Down Expand Up @@ -91,7 +98,7 @@ int example(PGconn *conn) {
}

// Read the data we inserted
std::string select = "SELECT * FROM owner";
std::string select = "SELECT * FROM owner where name='John Doe'";

PGresult *selectResponse = PQexec(conn, select.c_str());
ExecStatusType selectStatus = PQresultStatus(selectResponse);
Expand All @@ -116,19 +123,28 @@ int example(PGconn *conn) {
}
std::cout << std::endl;
}
if (rows == 0) {
std::cerr << "No rows returned" << std::endl;
retVal = -1;
}
}
else {
std::cerr << "Select failed - " << PQerrorMessage(conn) << std::endl;
retVal = -1;
}
PQclear(selectResponse);

PGresult *clearResponse = PQexec(conn, "DELETE FROM owner where name='John Doe'");
PQclear(clearResponse);


return retVal;
}

int main(int argc, char *argv[]) {
std::string region = "";
std::string clusterEndpoint = "";
std::string clusterUser = "";

if (const char* env_var = std::getenv("CLUSTER_ENDPOINT")) {
clusterEndpoint = env_var;
Expand All @@ -142,10 +158,16 @@ int main(int argc, char *argv[]) {
std::cout << "Please set the REGION environment variable" << std::endl;
return -1;
}
if (const char* env_var = std::getenv("CLUSTER_USER")) {
clusterUser = env_var;
} else {
std::cout << "Please set the CLUSTER_USER environment variable" << std::endl;
return -1;
}

int testStatus = 0;

PGconn *conn = connectToCluster(clusterEndpoint, region);
PGconn *conn = connectToCluster(clusterUser, clusterEndpoint, region);

if (conn == NULL) {
std::cerr << "Failed to get connection." << std::endl;
Expand Down
Loading