Skip to content
4 changes: 2 additions & 2 deletions .github/workflows/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,6 @@ echo "HOST=localhost" >> src/test/resources/config.properties
echo "PORT=1521" >> src/test/resources/config.properties
echo "USER=test" >> src/test/resources/config.properties
echo "PASSWORD=test" >> src/test/resources/config.properties
echo "CONNECT_TIMEOUT=120" >> src/test/resources/config.properties
echo "SQL_TIMEOUT=120" >> src/test/resources/config.properties
echo "CONNECT_TIMEOUT=180" >> src/test/resources/config.properties
echo "SQL_TIMEOUT=180" >> src/test/resources/config.properties
mvn clean compile test
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,55 @@ prefetched entirely, a smaller prefetch size can be configured using the
option, and the LOB can be consumed as a stream. By mapping LOB columns to
`Blob` or `Clob` objects, the content can be consumed as a reactive stream.

### REF Cursors
### ARRAY
Oracle Database supports `ARRAY` as a user defined type only. A `CREATE TYPE`
command is used to define an `ARRAY` type:
```sql
CREATE TYPE MY_ARRAY AS ARRAY(8) OF NUMBER
```
Oracle R2DBC defines `oracle.r2dbc.OracleR2dbcType.ArrayType` as a `Type` for
representing user defined `ARRAY` types. A `Parameter` with a type of
`ArrayType` must be used when binding array values to a `Statement`.
```java
Publisher<Result> arrayBindExample(Connection connection) {
Statement statement =
connection.createStatement("INSERT INTO example VALUES (:array_bind)");

// Use the name defined for an ARRAY type:
// CREATE TYPE MY_ARRAY AS ARRAY(8) OF NUMBER
ArrayType arrayType = OracleR2dbcTypes.arrayType("MY_ARRAY");
Integer[] arrayValues = {1, 2, 3};
statement.bind("arrayBind", Parameters.in(arrayType, arrayValues));

return statement.execute();
}
```
A `Parameter` with a type of `ArrayType` must also be used when binding OUT
parameters of a PL/SQL call.
```java
Publisher<Result> arrayOutBindExample(Connection connection) {
Statement statement =
connection.createStatement("BEGIN; exampleCall(:array_bind); END;");

// Use the name defined for an ARRAY type:
// CREATE TYPE MY_ARRAY AS ARRAY(8) OF NUMBER
ArrayType arrayType = OracleR2dbcTypes.arrayType("MY_ARRAY");
statement.bind("arrayBind", Parameters.out(arrayType));

return statement.execute();
}
```
`ARRAY` values may be consumed from a `Row` or `OutParameter` as a Java array.
The element type of the Java array may be any Java type that is supported as
a mapping for the SQL type of the `ARRAY`. For instance, if the `ARRAY` type is
`NUMBER`, then a `Integer[]` mapping is supported:
```java
Publisher<Integer[]> arrayMapExample(Result result) {
return result.map(readable -> readable.get("arrayValue", Integer[].class));
}
```

### REF Cursor
Use the `oracle.r2dbc.OracleR2dbcTypes.REF_CURSOR` type to bind `SYS_REFCURSOR` out
parameters:
```java
Expand Down
98 changes: 97 additions & 1 deletion src/main/java/oracle/r2dbc/OracleR2dbcTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
*/
package oracle.r2dbc;

import io.r2dbc.spi.Parameter;
import io.r2dbc.spi.R2dbcType;
import io.r2dbc.spi.Result;
import io.r2dbc.spi.Statement;
import io.r2dbc.spi.Type;
import oracle.sql.json.OracleJsonObject;

Expand All @@ -29,6 +32,7 @@
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Period;
import java.util.Objects;

/**
* SQL types supported by Oracle Database that are not defined as standard types
Expand Down Expand Up @@ -99,10 +103,102 @@ private OracleR2dbcTypes() {}
public static final Type REF_CURSOR =
new TypeImpl(Result.class, "SYS_REFCURSOR");

/**
* <p>
* Creates an {@link ArrayType} representing a user defined {@code ARRAY}
* type. The {@code name} passed to this method must identify the name of a
* user defined {@code ARRAY} type.
* </p><p>
* Typically, the name passed to this method should be UPPER CASE, unless the
* {@code CREATE TYPE} command that created the type used an "enquoted" type
* name.
* </p><p>
* The {@code ArrayType} object returned by this method may be used to create
* a {@link Parameter} that binds an array value to a {@link Statement}.
* </p><pre>{@code
* Publisher<Result> arrayBindExample(Connection connection) {
* Statement statement =
* connection.createStatement("INSERT INTO example VALUES (:array_bind)");
*
* // Use the name defined for an ARRAY type:
* // CREATE TYPE MY_ARRAY AS ARRAY(8) OF NUMBER
* ArrayType arrayType = OracleR2dbcTypes.arrayType("MY_ARRAY");
* Integer[] arrayValues = {1, 2, 3};
* statement.bind("arrayBind", Parameters.in(arrayType, arrayValues));
*
* return statement.execute();
* }
* }</pre>
* @param name Name of a user defined ARRAY type. Not null.
* @return A {@code Type} object representing the user defined ARRAY type. Not
* null.
*/
public static ArrayType arrayType(String name) {
return new ArrayTypeImpl(Objects.requireNonNull(name, "name is null"));
}

/**
* Extension of the standard {@link Type} interface used to represent user
* defined ARRAY types. An instance of {@code ArrayType} must be used when
* binding an array value to a {@link Statement} created by the Oracle R2DBC
* Driver.
* </p><p>
* Oracle Database does not support an anonymous {@code ARRAY} type, which is
* what the standard {@link R2dbcType#COLLECTION} type represents. Oracle
* Database only supports {@code ARRAY} types which are declared as a user
* defined type, as in:
* <pre>{@code
* CREATE TYPE MY_ARRAY AS ARRAY(8) OF NUMBER
* }</pre>
* In order to bind an array, the name of a user defined ARRAY type must
* be known to Oracle R2DBC. Instances of {@code ArrayType} retain the name
* that is provided to the {@link #arrayType(String)} factory method.
*/
public interface ArrayType extends Type {

/**
* {@inheritDoc}
* Returns {@code Object[].class}, which is the standard mapping for
* {@link R2dbcType#COLLECTION}. The true default type mapping is the array
* variant of the default mapping for the element type of the {@code ARRAY}.
* For instance, an {@code ARRAY} of {@code VARCHAR} maps to a
* {@code String[]} by default.
*/
@Override
Class<?> getJavaType();

/**
* {@inheritDoc}
* Returns the name of this user defined {@code ARRAY} type. For instance,
* this method returns "MY_ARRAY" if the type is declared as:
* <pre>{@code
* CREATE TYPE MY_ARRAY AS ARRAY(8) OF NUMBER
* }</pre>
*/
@Override
String getName();
}

/** Concrete implementation of the {@code ArrayType} interface */
private static final class ArrayTypeImpl
extends TypeImpl implements ArrayType {

/**
* Constructs an ARRAY type with the given {@code name}. The constructed
* {@code ArrayType} as a default Java type mapping of
* {@code Object[].class}. This is consistent with the standard
* {@link R2dbcType#COLLECTION} type.
* @param name User defined name of the type. Not null.
*/
ArrayTypeImpl(String name) {
super(Object[].class, name);
}
}

/**
* Implementation of the {@link Type} SPI.
*/
private static final class TypeImpl implements Type {
private static class TypeImpl implements Type {

/**
* The Java Language mapping of this SQL type.
Expand Down
Loading