Skip to content
This repository was archived by the owner on Apr 2, 2023. It is now read-only.

Implement Cloud SQL for Postgres Support and Sample App #250

Merged
merged 6 commits into from
Nov 3, 2021
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
6 changes: 6 additions & 0 deletions .cloudbuild/graal-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ steps:
'-Dpassword=root'
]

- name: ghcr.io/graalvm/graalvm-ce:java11-21.2.0
args: [
'./native-image-samples/native-image-samples-client-library/cloud-sql-postgres-sample/target/com.example.postgressampleapplication',
'-Dinstance=cloud-graalvm-support-ci:us-central1:test-postgres',
]

- name: ghcr.io/graalvm/graalvm-ce:java11-21.2.0
args: [ './native-image-samples/native-image-samples-client-library/pubsub-sample/target/com.example.pubsubsampleapplication' ]

Expand Down
8 changes: 8 additions & 0 deletions .cloudbuild/std-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ steps:
'native-image-samples/native-image-samples-client-library/cloud-sql-sample/target/cloud-sql-sample-0.9.0-SNAPSHOT.jar'
]

- name: openjdk:11-jdk
entrypoint: java
args: [
'-jar',
'-Dinstance=cloud-graalvm-support-ci:us-central1:test-postgres',
'native-image-samples/native-image-samples-client-library/cloud-sql-postgres-sample/target/cloud-sql-postgres-sample-0.9.0-SNAPSHOT.jar'
]

- name: openjdk:11-jdk
entrypoint: java
args: ['-jar', 'native-image-samples/native-image-samples-client-library/firestore-sample/target/firestore-sample-0.9.0-SNAPSHOT.jar']
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Cloud SQL for Postgres Sample Application with Native Image

This application uses the [Google Cloud SQL JDBC Socket Factory](https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory) and is compatible with Native Image compilation.

The application runs some simple Cloud SQL queries to demonstrate compatibility.

## Setup Instructions

1. Follow the [GCP Project and Native Image Setup Instructions](../../README.md).

2. Cloud SQL database setup:

1. Follow [these instructions](https://cloud.google.com/sql/docs/postgres/create-instance) to create a Cloud SQL instance.
Choose **PostgreSQL** as the database provider because this sample is designed for Cloud SQL with Postgres.

2. Navigate to your Cloud SQL instance from the [instances view page](https://console.cloud.google.com/sql/instances) and find the instance connection name.

It should be of the form:
```
{PROJECT_ID}:{REGION}:{SQL_INSTANCE_NAME}
```

This string will be used to connect your instance.

3. Follow [these instructions](https://cloud.google.com/sql/docs/postgres/create-manage-databases#create) to create a database.
For this example, name the database `test_db`.

## Run with Native Image Compilation

1. Navigate to this directory and compile the application with the Native Image compiler.

```
mvn package -P native
```

2. Run the application. Set the `-Dinstance` property to the instance connection name referenced above.

```
./target/com.example.cloudsqlsampleapplication -Dinstance=<INSTANCE_CONNECTION_NAME>
```

**Additional Properties**

By default the application connects with the `root` user and an empty password to the `test_db` database.
You may override the settings by passing additional arguments with the `-D` prefix to the executable:

| Property | Description |
| --------------- | -------------------------------------------------|
| `-Dusername` | Set the username to use. (Defaults to postgres) |
| `-Dpassword` | Set the password to use. (Defaults to postgres) |
| `-Dinstance` | Specify the instance connection string. |
| `-Ddatabase` | Specify the database to connect to. |

3. The application will run through some basic operations to create a table and read records from it.

```
[main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
Jun 04, 2021 2:28:13 PM com.google.cloud.sql.core.CoreSocketFactory connect
INFO: Connecting to Cloud SQL instance [<your-instance-here>] via SSL socket.
Books in database:
a965870d-c148-4159-9280-81ea507af1f8, The Book
```

## Additional Notes

The Cloud SQL connector library uses the CP1252 charset when logging error messages from the server.

Therefore, you will need to pass `-H:+AddAllCharsets` to the Native Image compiler.

This is specified in the `buildArgs` section of the `native-image-maven-plugin` in the pom.xml of this sample.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<parent>
<artifactId>native-image-samples-client-library</artifactId>
<groupId>com.google.cloud</groupId>
<version>0.9.0-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-sql-postgres-sample</artifactId>

<dependencies>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>native-image-support</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>com.google.cloud.sql</groupId>
<artifactId>postgres-socket-factory</artifactId>
<version>1.3.4</version>
</dependency>

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.1</version>
</dependency>

<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.0</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.0-alpha2</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.example.PostgresSampleApplication</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>

<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>${graalvm.version}</version>
<configuration>
<mainClass>com.example.PostgresSampleApplication</mainClass>
<buildArgs>
--no-fallback
--no-server
<!-- Additional charsets is needed for error logging -->
-H:+AddAllCharsets
</buildArgs>
</configuration>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2020-2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.UUID;
import java.util.stream.Collectors;

/**
* Sample application demonstrating usage of Cloud SQL connector with Native Image.
*/
public class PostgresSampleApplication {

/**
* Entrypoint to the Cloud SQL (with MySQL) sample application.
*/
public static void main(String[] args) throws Exception {

String user = System.getProperty("user", "postgres");
String password = System.getProperty("password", "postgres");
String instanceName = System.getProperty("instance");
String databaseName = System.getProperty("database", "test_db");

HikariConfig config = new HikariConfig();

config.setJdbcUrl(String.format("jdbc:postgresql:///%s", databaseName));
config.addDataSourceProperty("user", user);
config.addDataSourceProperty("password", password);
config.addDataSourceProperty("socketFactory", "com.google.cloud.sql.postgres.SocketFactory");
config.addDataSourceProperty("cloudSqlInstance", instanceName);

HikariDataSource connectionPool = new HikariDataSource(config);

// Create a table.
try (Connection conn = connectionPool.getConnection()) {
String dropTable = "DROP TABLE IF EXISTS Books";
try (PreparedStatement createTableStatement = conn.prepareStatement(dropTable)) {
createTableStatement.execute();
}

String stmt =
"CREATE TABLE Books ("
+ " ID VARCHAR(36) NOT NULL,"
+ " TITLE TEXT NOT NULL"
+ ");";
try (PreparedStatement createTableStatement = conn.prepareStatement(stmt)) {
createTableStatement.execute();
}
}

// Insert a new record
try (Connection conn = connectionPool.getConnection()) {
String stmt = "INSERT INTO Books (ID, TITLE) VALUES (?, ?)";
try (PreparedStatement insertStmt = conn.prepareStatement(stmt)) {
insertStmt.setQueryTimeout(10);
insertStmt.setString(1, UUID.randomUUID().toString());
insertStmt.setString(2, "The Book");
insertStmt.execute();
}
}

// Read records
ArrayList<String> books = new ArrayList<>();
try (Connection conn = connectionPool.getConnection()) {
String stmt = "SELECT * FROM Books";
try (PreparedStatement selectStmt = conn.prepareStatement(stmt)) {
selectStmt.setQueryTimeout(10); // 10s
ResultSet rs = selectStmt.executeQuery();

while (rs.next()) {
books.add(resultSetToString(rs));
}
}
}

System.out.println("Books in database:");
for (String book : books) {
System.out.println(book);
}

if (books.size() == 0) {
throw new RuntimeException("Expected records in database, found none: " + books);
}
}

private static String resultSetToString(ResultSet resultSet) throws SQLException {
ArrayList<String> row = new ArrayList<>();
for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) {
row.add(resultSet.getString(i));
}

return row.stream().collect(Collectors.joining(", "));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<module>bigtable-sample</module>
<module>cloud-functions-sample</module>
<module>cloud-sql-sample</module>
<module>cloud-sql-postgres-sample</module>
<module>datastore-sample</module>
<module>firestore-sample</module>
<module>logging-sample</module>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@
import org.graalvm.nativeimage.hosted.RuntimeReflection;

/**
* Registers GraalVM configuration for the Cloud SQL libraries.
* Registers GraalVM configuration for the Cloud SQL libraries for MySQL and Postgres.
*/
@AutomaticFeature
final class CloudSqlFeature implements Feature {

private static final String CLOUD_SQL_SOCKET_CLASS =
"com.google.cloud.sql.core.CoreSocketFactory";

private static final String POSTGRES_SOCKET_CLASS =
"com.google.cloud.sql.postgres.SocketFactory";

private static final String MYSQL_SOCKET_CLASS =
"com.google.cloud.sql.mysql.SocketFactory";

Expand Down Expand Up @@ -64,6 +67,14 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
);
}

// Register PostgreSQL driver config.
if (access.findClassByName(POSTGRES_SOCKET_CLASS) != null) {
NativeImageUtils.registerClassForReflection(
access, "com.google.cloud.sql.postgres.SocketFactory");
NativeImageUtils.registerClassForReflection(
access, "org.postgresql.PGProperty");
}

// Register MySQL driver config.
if (access.findClassByName(MYSQL_SOCKET_CLASS) != null) {
NativeImageUtils.registerClassForReflection(
Expand Down