Skip to content

OBJECT Types #104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Nov 15, 2022
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
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=240" >> src/test/resources/config.properties
echo "SQL_TIMEOUT=240" >> src/test/resources/config.properties
mvn clean compile test
164 changes: 163 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,169 @@ 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));
}
```

### OBJECT
Oracle Database supports `OBJECT` as a user defined type. A `CREATE TYPE`
command is used to define an `OBJECT` type:
```sql
CREATE TYPE PET AS OBJECT(
name VARCHAR(128),
species VARCHAR(128),
weight NUMBER,
birthday DATE)
```
Oracle R2DBC defines `oracle.r2dbc.OracleR2dbcType.ObjectType` as a `Type` for
representing user defined `OBJECT` types. A `Parameter` with a type of
`ObjectType` may be used to bind `OBJECT` values to a `Statement`.

Use an `Object[]` to bind the attribute values of an `OBJECT` by index:
```java
Publisher<Result> objectArrayBindExample(Connection connection) {
Statement statement =
connection.createStatement("INSERT INTO petTable VALUES (:petObject)");

// Bind the attributes of the PET OBJECT defined above
ObjectType objectType = OracleR2dbcTypes.objectType("PET");
Object[] attributeValues = {
"Derby",
"Dog",
22.8,
LocalDate.of(2015, 11, 07)
};
statement.bind("petObject", Parameters.in(objectType, attributeValues));

return statement.execute();
}
```

Use a `Map<String,Object>` to bind the attribute values of an `OBJECT` by name:
```java
Publisher<Result> objectMapBindExample(Connection connection) {
Statement statement =
connection.createStatement("INSERT INTO petTable VALUES (:petObject)");

// Bind the attributes of the PET OBJECT defined above
ObjectType objectType = OracleR2dbcTypes.objectType("PET");
Map<String,Object> attributeValues = Map.of(
"name", "Derby",
"species", "Dog",
"weight", 22.8,
"birthday", LocalDate.of(2015, 11, 07));
statement.bind("petObject", Parameters.in(objectType, attributeValues));

return statement.execute();
}
```
A `Parameter` with a type of `ObjectType` must be used when binding OUT
parameters of `OBJECT` types for a PL/SQL call:
```java
Publisher<Result> objectOutBindExample(Connection connection) {
Statement statement =
connection.createStatement("BEGIN; getPet(:petObject); END;");

ObjectType objectType = OracleR2dbcTypes.objectType("PET");
statement.bind("petObject", Parameters.out(objectType));

return statement.execute();
}
```
`OBJECT` values may be consumed from a `Row` or `OutParameter` as an
`oracle.r2dbc.OracleR2dbcObject`. The `OracleR2dbcObject` interface is a subtype
of `io.r2dbc.spi.Readable`. Attribute values may be accessed using the standard
`get` methods of `Readable`. The `get` methods of `OracleR2dbcObject` support
alll SQL to Java type mappings defined by the
[R2DBC Specification](https://r2dbc.io/spec/1.0.0.RELEASE/spec/html/#datatypes.mapping):
```java
Publisher<Pet> objectMapExample(Result result) {
return result.map(row -> {
OracleR2dbcObject oracleObject = row.get(0, OracleR2dbcObject.class);
return new Pet(
oracleObject.get("name", String.class),
oracleObject.get("species", String.class),
oracleObject.get("weight", Float.class),
oracleObject.get("birthday", LocalDate.class));
});
}
```

Instances of `OracleR2dbcObject` may be passed directly to `Statement` bind
methods:
```java
Publisher<Result> objectBindExample(
OracleR2dbcObject oracleObject, Connection connection) {
Statement statement =
connection.createStatement("INSERT INTO petTable VALUES (:petObject)");

statement.bind("petObject", oracleObject);

return statement.execute();
}
```
Attribute metadata is exposed by the `getMetadata` method of
`OracleR2dbcObject`:
```java
void printObjectMetadata(OracleR2dbcObject oracleObject) {
OracleR2dbcObjectMetadata metadata = oracleObject.getMetadata();
OracleR2dbcTypes.ObjectType objectType = metadata.getObjectType();

System.out.println("Object Type: " + objectType);
metadata.getAttributeMetadatas()
.stream()
.forEach(attributeMetadata -> {
System.out.println("\tAttribute Name: " + attributeMetadata.getName()));
System.out.println("\tAttribute Type: " + attributeMetadata.getType()));
});
}
```

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

public interface OracleR2dbcObject extends io.r2dbc.spi.Readable {

OracleR2dbcObjectMetadata getMetadata();

}
49 changes: 49 additions & 0 deletions src/main/java/oracle/r2dbc/OracleR2dbcObjectMetadata.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package oracle.r2dbc;

import io.r2dbc.spi.ReadableMetadata;

import java.util.List;
import java.util.NoSuchElementException;

/**
* Represents the metadata for attributes of an OBJECT. Metadata for attributes
* can either be retrieved by index or by name. Attribute indexes are
* {@code 0}-based. Retrieval by attribute name is case-insensitive.
*/
public interface OracleR2dbcObjectMetadata {

/**
* Returns the type of the OBJECT which metadata is provided for.
* @return The type of the OBJECT. Not null.
*/
OracleR2dbcTypes.ObjectType getObjectType();

/**
* Returns the {@link ReadableMetadata} for one attribute.
*
* @param index the attribute index starting at 0
* @return the {@link ReadableMetadata} for one attribute. Not null.
* @throws IndexOutOfBoundsException if {@code index} is out of range
* (negative or equals/exceeds {@code getParameterMetadatas().size()})
*/
ReadableMetadata getAttributeMetadata(int index);

/**
* Returns the {@link ReadableMetadata} for one attribute.
*
* @param name the name of the attribute. Not null. Parameter names are
* case-insensitive.
* @return the {@link ReadableMetadata} for one attribute. Not null.
* @throws IllegalArgumentException if {@code name} is {@code null}
* @throws NoSuchElementException if there is no attribute with the
* {@code name}
*/
ReadableMetadata getAttributeMetadata(String name);

/**
* Returns the {@link ReadableMetadata} for all attributes.
*
* @return the {@link ReadableMetadata} for all attributes. Not null.
*/
List<? extends ReadableMetadata> getAttributeMetadatas();
}
Loading