Advanced JDBC Features¶
Important
The JDBC interface is experimental and not production-ready at this stage. APIs and behaviors are subject to change.
This guide covers FoundationDB Record Layer-specific JDBC features for working with complex data types like STRUCTs and ARRAYs.
Note
Unified API with Driver-Specific Entry Points: The Record Layer provides a unified API for creating STRUCT and ARRAY values through common interfaces (RelationalStruct, RelationalArray, RelationalStructBuilder, RelationalArrayBuilder). However, you must use the appropriate factory method for your JDBC driver:
Embedded Driver:
EmbeddedRelationalStruct.newBuilder()andEmbeddedRelationalArray.newBuilder()Server Driver:
JDBCRelationalStruct.newBuilder()andJDBCRelationalArray.newBuilder()
Both factory methods return the same builder interfaces, so once you obtain a builder, all subsequent code is identical regardless of the driver. This design allows the API to abstract over different implementation backends (in-memory objects vs. gRPC serialization) while providing a consistent developer experience.
Working with STRUCT Types¶
The Record Layer extends standard JDBC to support STRUCT types, which represent nested record structures similar to protobuf messages. STRUCTs allow you to model hierarchical data within your relational schema.
Reading STRUCT Values¶
To read STRUCT values from a query result, unwrap the ResultSet to RelationalResultSet which provides direct access to STRUCTs:
try (Connection conn = DriverManager.getConnection(url)) {
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT reviewer FROM reviewers WHERE id = 1")) {
// Unwrap to RelationalResultSet to access getStruct()
RelationalResultSet relationalRs = rs.unwrap(RelationalResultSet.class);
if (relationalRs.next()) {
RelationalStruct reviewer = relationalRs.getStruct("reviewer");
// Access STRUCT fields by name
long id = reviewer.getLong("ID");
String name = reviewer.getString("NAME");
String email = reviewer.getString("EMAIL");
System.out.printf("ID: %d, Name: %s, Email: %s%n", id, name, email);
}
}
}
}
Accessing Nested STRUCT Fields¶
STRUCTs can contain other STRUCTs, allowing for deeply nested data structures. Here’s how to read a STRUCT with a nested STRUCT:
private static void queryData() throws SQLException {
try (Connection conn = DriverManager.getConnection(APP_URL)) {
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT * FROM orders WHERE id = 1")) {
if (rs.next()) {
long id = rs.getLong("id");
System.out.printf("Order %d:%n", id);
// Read customer STRUCT
RelationalResultSet relationalRs = rs.unwrap(RelationalResultSet.class);
RelationalStruct customer = relationalRs.getStruct("customer");
if (customer != null) {
String name = customer.getString("name");
String email = customer.getString("email");
System.out.printf(" Customer: %s (%s)%n", name, email);
// Access nested address STRUCT
RelationalStruct address = customer.getStruct("address");
if (address != null) {
String street = address.getString("street");
String city = address.getString("city");
System.out.printf(" Address: %s, %s%n", street, city);
}
}
Creating STRUCT Values¶
Use the appropriate builder depending on your JDBC driver. Both builders provide identical APIs. Here’s an example inserting a STRUCT into a STRUCT column:
try (Connection conn = DriverManager.getConnection(url)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO reviewers (id, reviewer) VALUES (?, ?)";
try (RelationalPreparedStatement pstmt =
conn.prepareStatement(sql).unwrap(RelationalPreparedStatement.class)) {
// Build a STRUCT to insert into the reviewer column
RelationalStruct reviewer = EmbeddedRelationalStruct.newBuilder()
.addLong("ID", 1L)
.addString("NAME", "Anthony Bourdain")
.addString("EMAIL", "abourdain@example.com")
.build();
pstmt.setLong(1, 1L);
pstmt.setObject(2, reviewer);
int inserted = pstmt.executeUpdate();
System.out.println("Rows inserted: " + inserted);
conn.commit();
}
}
Creating Nested STRUCTs¶
You can create STRUCTs that contain nested STRUCTs and insert them as a single value. Here’s an example that creates a customer STRUCT with a nested address STRUCT:
private static void insertData() throws SQLException {
try (Connection conn = DriverManager.getConnection(APP_URL)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO orders (id, customer, items, tags) " +
"VALUES (?, ?, ?, ?)";
try (RelationalPreparedStatement pstmt =
conn.prepareStatement(sql).unwrap(RelationalPreparedStatement.class)) {
pstmt.setLong(1, 1L);
// Create a STRUCT with nested STRUCT for customer
RelationalStruct customer = EmbeddedRelationalStruct.newBuilder()
.addString("name", "Alice Johnson")
.addString("email", "alice@example.com")
.addStruct("address", EmbeddedRelationalStruct.newBuilder()
.addString("street", "123 Main St")
.addString("city", "Springfield")
.build())
.build();
Handling NULL STRUCT Fields¶
You can set NULL values for individual fields within a STRUCT:
try (Connection conn = DriverManager.getConnection(url)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO reviewers (id, reviewer) VALUES (?, ?)";
try (RelationalPreparedStatement pstmt =
conn.prepareStatement(sql).unwrap(RelationalPreparedStatement.class)) {
// Create STRUCT with NULL email field
RelationalStruct reviewer = EmbeddedRelationalStruct.newBuilder()
.addLong("ID", 1L)
.addString("NAME", "Anthony Bourdain")
.addObject("EMAIL", null) // Explicitly set NULL
.build();
pstmt.setLong(1, 1L);
pstmt.setObject(2, reviewer);
int inserted = pstmt.executeUpdate();
conn.commit();
}
}
Working with ARRAY Types¶
The Record Layer supports ARRAY types containing elements of any SQL type, including primitives, STRUCTs, and even nested ARRAYs.
Reading ARRAY Values¶
Use the getArray() method to retrieve ARRAY values from a ResultSet:
try (Connection conn = DriverManager.getConnection(url)) {
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT tags FROM products WHERE id = 1")) {
if (rs.next()) {
Array tagsArray = rs.getArray("tags");
if (tagsArray != null) {
// Method 1: Get array as Object[]
Object[] tags = (Object[]) tagsArray.getArray();
for (Object tag : tags) {
System.out.println("Tag: " + tag);
}
}
}
}
}
}
Using ResultSet to Iterate Arrays¶
You can also retrieve array elements as a ResultSet for more structured access:
try (Connection conn = DriverManager.getConnection(url)) {
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT tags FROM products WHERE id = 1")) {
if (rs.next()) {
Array tagsArray = rs.getArray("tags");
if (tagsArray != null) {
// Method 2: Get array as ResultSet
try (ResultSet arrayRs = tagsArray.getResultSet()) {
// ResultSet has two columns:
// Column 1: index (1-based)
// Column 2: value
while (arrayRs.next()) {
int index = arrayRs.getInt(1);
String value = arrayRs.getString(2);
System.out.printf("tags[%d] = %s%n", index, value);
}
}
}
}
}
}
}
Creating ARRAY Values¶
Use the appropriate builder depending on your JDBC driver. Both builders provide identical APIs:
try (Connection conn = DriverManager.getConnection(url)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO products (id, name, tags) VALUES (?, ?, ?)";
try (RelationalPreparedStatement pstmt =
conn.prepareStatement(sql).unwrap(RelationalPreparedStatement.class)) {
pstmt.setLong(1, 1L);
pstmt.setString(2, "Widget");
pstmt.setArray(3, EmbeddedRelationalArray.newBuilder()
.addAll("electronics", "gadget", "popular")
.build());
int rowsAffected = pstmt.executeUpdate();
System.out.println("Rows inserted: " + rowsAffected);
conn.commit();
}
}
Working with Different Array Element Types¶
try (Connection conn = DriverManager.getConnection(url)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO data_table (id, integers, floats, booleans) " +
"VALUES (?, ?, ?, ?)";
try (RelationalPreparedStatement pstmt =
conn.prepareStatement(sql).unwrap(RelationalPreparedStatement.class)) {
pstmt.setLong(1, 1L);
// Array of integers
pstmt.setArray(2, EmbeddedRelationalArray.newBuilder()
.addAll(10, 20, 30, 40)
.build());
// Array of floats
pstmt.setArray(3, EmbeddedRelationalArray.newBuilder()
.addAll(1.5f, 2.5f, 3.5f)
.build());
// Array of booleans
pstmt.setArray(4, EmbeddedRelationalArray.newBuilder()
.addAll(true, false, true)
.build());
pstmt.executeUpdate();
conn.commit();
}
}
Handling NULL Arrays¶
try (Connection conn = DriverManager.getConnection(url)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO products (id, name, tags) VALUES (?, ?, ?)";
try (RelationalPreparedStatement pstmt =
conn.prepareStatement(sql).unwrap(RelationalPreparedStatement.class)) {
pstmt.setLong(1, 1L);
pstmt.setString(2, "Widget");
pstmt.setNull(3, Types.ARRAY); // Set NULL array
pstmt.executeUpdate();
conn.commit();
}
}
Complex Nested Types¶
Arrays of STRUCTs¶
ARRAYs can contain STRUCT elements, allowing for collections of complex records:
try (Connection conn = DriverManager.getConnection(url)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO orders (id, items) VALUES (?, ?)";
try (RelationalPreparedStatement pstmt =
conn.prepareStatement(sql).unwrap(RelationalPreparedStatement.class)) {
pstmt.setLong(1, 1L);
// Create an array of STRUCT values
pstmt.setArray(2, EmbeddedRelationalArray.newBuilder()
.addAll(
EmbeddedRelationalStruct.newBuilder()
.addInt("product_id", 11)
.addString("product_name", "Widget A")
.addInt("quantity", 2)
.build(),
EmbeddedRelationalStruct.newBuilder()
.addInt("product_id", 22)
.addString("product_name", "Widget B")
.addInt("quantity", 5)
.build()
)
.build());
pstmt.executeUpdate();
conn.commit();
}
}
try (Connection conn = DriverManager.getConnection(url)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO orders (id, items) VALUES (?, ?)";
try (RelationalPreparedStatement pstmt =
conn.prepareStatement(sql).unwrap(RelationalPreparedStatement.class)) {
pstmt.setLong(1, 1L);
// Create an array of STRUCT values
pstmt.setArray(2, JDBCRelationalArray.newBuilder()
.addAll(
JDBCRelationalStruct.newBuilder()
.addInt("product_id", 11)
.addString("product_name", "Widget A")
.addInt("quantity", 2)
.build(),
JDBCRelationalStruct.newBuilder()
.addInt("product_id", 22)
.addString("product_name", "Widget B")
.addInt("quantity", 5)
.build()
)
.build());
pstmt.executeUpdate();
conn.commit();
}
}
Reading Arrays of STRUCTs¶
When reading arrays that contain STRUCT elements, unwrap the array’s ResultSet to RelationalResultSet:
Array itemsArray = rs.getArray("items");
if (itemsArray != null) {
System.out.println(" Items:");
try (ResultSet arrayRs = itemsArray.getResultSet()) {
// Unwrap to RelationalResultSet to access getStruct()
RelationalResultSet itemsRelRs = arrayRs.unwrap(RelationalResultSet.class);
while (itemsRelRs.next()) {
int index = itemsRelRs.getInt(1);
RelationalStruct item = itemsRelRs.getStruct(2);
int productId = item.getInt("product_id");
String itemName = item.getString("name");
int quantity = item.getInt("quantity");
System.out.printf(" - %s (ID: %d) x %d%n",
itemName, productId, quantity);
}
}
}
STRUCTs Containing Arrays¶
STRUCTs can contain ARRAY fields. You can insert the entire STRUCT including its arrays as a single value:
try (Connection conn = DriverManager.getConnection(url)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO products (id, product_info) VALUES (?, ?)";
try (RelationalPreparedStatement pstmt =
conn.prepareStatement(sql).unwrap(RelationalPreparedStatement.class)) {
// Create STRUCT with embedded ARRAY fields
RelationalStruct product = EmbeddedRelationalStruct.newBuilder()
.addLong("ID", 1L)
.addString("NAME", "Multi-tool")
.addArray("TAGS", EmbeddedRelationalArray.newBuilder()
.addAll("versatile", "portable", "durable")
.build())
.addArray("RATINGS", EmbeddedRelationalArray.newBuilder()
.addAll(4.5, 4.8, 4.2, 4.9)
.build())
.build();
pstmt.setLong(1, 1L);
pstmt.setObject(2, product);
int inserted = pstmt.executeUpdate();
conn.commit();
}
}
try (Connection conn = DriverManager.getConnection(url)) {
conn.setAutoCommit(false);
String sql = "INSERT INTO products (id, product_info) VALUES (?, ?)";
try (RelationalPreparedStatement pstmt =
conn.prepareStatement(sql).unwrap(RelationalPreparedStatement.class)) {
// Create STRUCT with embedded ARRAY fields
RelationalStruct product = JDBCRelationalStruct.newBuilder()
.addLong("ID", 1L)
.addString("NAME", "Multi-tool")
.addArray("TAGS", JDBCRelationalArray.newBuilder()
.addAll("versatile", "portable", "durable")
.build())
.addArray("RATINGS", JDBCRelationalArray.newBuilder()
.addAll(4.5, 4.8, 4.2, 4.9)
.build())
.build();
pstmt.setLong(1, 1L);
pstmt.setObject(2, product);
int inserted = pstmt.executeUpdate();
conn.commit();
}
}
Inspecting Array Metadata¶
try (Connection conn = DriverManager.getConnection(url)) {
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT tags FROM products WHERE id = 1")) {
if (rs.next()) {
Array tagsArray = rs.getArray("tags");
if (tagsArray != null) {
// Get array metadata
int baseType = tagsArray.getBaseType();
String baseTypeName = tagsArray.getBaseTypeName();
System.out.println("Array base type: " + baseType);
System.out.println("Array base type name: " + baseTypeName);
// Get array elements
Object[] elements = (Object[]) tagsArray.getArray();
System.out.println("Array length: " + elements.length);
}
}
}
}
}
Inspecting STRUCT Metadata¶
try (Connection conn = DriverManager.getConnection(url)) {
try (Statement stmt = conn.createStatement()) {
try (ResultSet rs = stmt.executeQuery("SELECT reviewer FROM reviewers WHERE id = 1")) {
// Unwrap to RelationalResultSet to access getStruct()
RelationalResultSet relationalRs = rs.unwrap(RelationalResultSet.class);
if (relationalRs.next()) {
RelationalStruct reviewer = relationalRs.getStruct("reviewer");
if (reviewer != null) {
// Get STRUCT metadata
var metaData = reviewer.getMetaData();
int columnCount = metaData.getColumnCount();
System.out.println("STRUCT has " + columnCount + " fields:");
// Iterate through all fields
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
String columnTypeName = metaData.getColumnTypeName(i);
int columnType = metaData.getColumnType(i);
System.out.printf(" Field %d: %s (%s, type=%d)%n",
i, columnName, columnTypeName, columnType);
}
}
}
}
}
}
See Also¶
JDBC Guide - Basic JDBC usage patterns
SQL Reference - Complete SQL syntax reference
Databases, Schemas, Schema Templates - Understanding the data model