Skip to content

Commit 17adf49

Browse files
Merge pull request #146 from oracle/144-vector
VECTOR Support
2 parents fc037e4 + 8aa5121 commit 17adf49

File tree

7 files changed

+417
-18
lines changed

7 files changed

+417
-18
lines changed

README.md

+99-2
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,7 @@ types of Oracle Database.
779779
| [INTERVAL DAY TO SECOND](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-B03DD036-66F8-4BD3-AF26-6D4433EBEC1C) | `java.time.Duration` |
780780
| [INTERVAL YEAR TO MONTH](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-ED59E1B3-BA8D-4711-B5C8-B0199C676A95) | `java.time.Period` |
781781
| [SYS_REFCURSOR](https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/static-sql.html#GUID-470A7A99-888A-46C2-BDAF-D4710E650F27) | `io.r2dbc.spi.Result` |
782+
| [VECTOR](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-801FFE49-217D-4012-9C55-66DAE1BA806F) | `double[]`, `float[]`, `byte[]`, or `oracle.sql.VECTOR` |
782783
> Unlike the standard SQL type named "DATE", the Oracle Database type named
783784
> "DATE" stores values for year, month, day, hour, minute, and second. The
784785
> standard SQL type only stores year, month, and day. LocalDateTime objects are able
@@ -981,8 +982,8 @@ void printObjectMetadata(OracleR2dbcObject oracleObject) {
981982
```
982983

983984
### REF Cursor
984-
Use the `oracle.r2dbc.OracleR2dbcTypes.REF_CURSOR` type to bind `SYS_REFCURSOR` out
985-
parameters:
985+
Use the `oracle.r2dbc.OracleR2dbcTypes.REF_CURSOR` type to bind `SYS_REFCURSOR`
986+
out parameters:
986987
```java
987988
Publisher<Result> executeProcedure(Connection connection) {
988989
connection.createStatement(
@@ -1009,6 +1010,102 @@ Publisher<ExampleObject> mapRefCursorRows(Result refCursorResult) {
10091010
}
10101011
```
10111012

1013+
### VECTOR
1014+
The default mapping for `VECTOR` is the
1015+
[oracle.sql.VECTOR](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/sql/VECTOR.html)
1016+
class. Instances of this class may be passed to
1017+
`Statement.bind(int/String, Object)`:
1018+
```java
1019+
void bindVector(Statement statement, float[] floatArray) throws SQLException {
1020+
final VECTOR vector;
1021+
try {
1022+
vector = VECTOR.ofFloat32Values(floatArray);
1023+
}
1024+
catch (SQLException sqlException) {
1025+
throw new IllegalArgumentException(sqlException);
1026+
}
1027+
statement.bind("vector", vector);
1028+
}
1029+
```
1030+
The `oracle.sql.VECTOR` class defines three factory methods: `ofFloat64Values`,
1031+
`ofFloat32Values`, and `ofInt8Values`. These methods support Java to VECTOR
1032+
conversions of `boolean[]`, `byte[]`, `short[]`, `int[]`, `long[]`,
1033+
`float[]`, and `double[]`:
1034+
```java
1035+
void bindVector(Statement statement, int[] intArray) {
1036+
final VECTOR vector;
1037+
try {
1038+
vector = VECTOR.ofFloat64Values(intArray);
1039+
}
1040+
catch (SQLException sqlException) {
1041+
throw new IllegalArgumentException(sqlException);
1042+
}
1043+
statement.bind("vector", vector);
1044+
}
1045+
```
1046+
The factory methods of `oracle.sql.VECTOR` may perform lossy conversions, such
1047+
as when converting a `double[]` into a VECTOR of 32-bit floating point numbers.
1048+
[The JavaDocs of these methods specify which conversions are lossy](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/sql/VECTOR.html).
1049+
1050+
The `OracleR2dbcTypes.VECTOR` type descriptor can be used to register an OUT or
1051+
IN/OUT parameter:
1052+
```java
1053+
void registerOutVector(Statement statement) {
1054+
Parameter outVector = Parameters.out(OracleR2dbcTypes.VECTOR);
1055+
statement.bind("vector", outVector);
1056+
}
1057+
```
1058+
The `OracleR2dbcTypes.VECTOR` type descriptor can also be used as an alternative to
1059+
`oracle.sql.VECTOR` when binding an IN parameter to a `double[]`, `float[]`, or
1060+
`byte[]`:
1061+
```java
1062+
void bindVector(Statement statement, float[] floatArray) {
1063+
Parameter inVector = Parameters.in(OracleR2dbcTypes.VECTOR, floatArray);
1064+
statement.bind("vector", inVector);
1065+
}
1066+
```
1067+
Note that `double[]`, `float[]`, and `byte[]` can NOT be passed directly to
1068+
`Statement.bind(int/String, Object)` when binding `VECTOR` data. The R2DBC
1069+
Specification defines `ARRAY` as the default mapping for Java arrays.
1070+
1071+
A `VECTOR` column or OUT parameter is converted to `oracle.sql.VECTOR` by
1072+
default. The column or OUT parameter can also be converted to `double[]`,
1073+
`float[]`, or `byte[]` by passing the corresponding array class to the `get`
1074+
methods:
1075+
```java
1076+
float[] getVector(io.r2dbc.Readable readable) {
1077+
return readable.get("vector", float[].class);
1078+
}
1079+
```
1080+
1081+
#### Returning VECTOR from DML
1082+
Returning a VECTOR column with `Statement.returningGeneratedValues(String...)`
1083+
is not supported due to a defect in the 23.4 release of Oracle JDBC. Attempting
1084+
to return a `VECTOR` column will result in a `Subscriber` that never receives
1085+
`onComplete` or `onError`. The defect will be fixed in the next release of
1086+
Oracle JDBC.
1087+
1088+
A `RETURNING ... INTO` clause can be used as a temporary workaround. This clause
1089+
must appear within a PL/SQL block, denoted by the `BEGIN` and `END;` keywords.
1090+
In the following example, a `VECTOR` column named "embedding" is returned:
1091+
```java
1092+
Publisher<double[]> returningVectorExample(Connection connection, String vectorString) {
1093+
1094+
Statement statement = connection.createStatement(
1095+
"BEGIN INSERT INTO example(embedding)"
1096+
+ " VALUES (TO_VECTOR(:vectorString, 999, FLOAT64))"
1097+
+ " RETURNING embedding INTO :embedding;"
1098+
+ " END;")
1099+
.bind("vectorString", vectorString)
1100+
.bind("embedding", Parameters.out(OracleR2dbcTypes.VECTOR));
1101+
1102+
return Flux.from(statement.execute())
1103+
.flatMap(result ->
1104+
result.map(outParameters ->
1105+
outParameters.get("embedding", double[].class)));
1106+
}
1107+
```
1108+
10121109
## Secure Programming Guidelines
10131110
The following security related guidelines should be adhered to when programming
10141111
with the Oracle R2DBC Driver.

src/main/java/oracle/r2dbc/OracleR2dbcTypes.java

+9
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@ private OracleR2dbcTypes() {}
103103
public static final Type REF_CURSOR =
104104
new TypeImpl(Result.class, "SYS_REFCURSOR");
105105

106+
/**
107+
* A vector of 64-bit floating point numbers, 32-bit floating point numbers,
108+
* or 8-bit signed integers. Maps to <code>double[]</code> by default, as a
109+
* <code>double</code> can store all the possible number formats without
110+
* losing information.
111+
*/
112+
public static final Type VECTOR =
113+
new TypeImpl(oracle.sql.VECTOR.class, "VECTOR");
114+
106115
/**
107116
* <p>
108117
* Creates an {@link ArrayType} representing a user defined {@code ARRAY}

src/main/java/oracle/r2dbc/impl/SqlTypeMap.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import oracle.jdbc.OracleType;
2626
import oracle.r2dbc.OracleR2dbcObject;
2727
import oracle.r2dbc.OracleR2dbcTypes;
28+
import oracle.sql.VECTOR;
2829
import oracle.sql.json.OracleJsonObject;
2930

3031
import java.math.BigDecimal;
@@ -85,6 +86,7 @@ final class SqlTypeMap {
8586
entry(JDBCType.NUMERIC, R2dbcType.NUMERIC),
8687
entry(JDBCType.NVARCHAR, R2dbcType.NVARCHAR),
8788
entry(JDBCType.REAL, R2dbcType.REAL),
89+
entry(JDBCType.REF_CURSOR, OracleR2dbcTypes.REF_CURSOR),
8890
entry(JDBCType.ROWID, OracleR2dbcTypes.ROWID),
8991
entry(JDBCType.SMALLINT, R2dbcType.SMALLINT),
9092
entry(JDBCType.TIME, R2dbcType.TIME),
@@ -101,7 +103,7 @@ final class SqlTypeMap {
101103
entry(JDBCType.TINYINT, R2dbcType.TINYINT),
102104
entry(JDBCType.VARBINARY, R2dbcType.VARBINARY),
103105
entry(JDBCType.VARCHAR, R2dbcType.VARCHAR),
104-
entry(JDBCType.REF_CURSOR, OracleR2dbcTypes.REF_CURSOR)
106+
entry(OracleType.VECTOR, OracleR2dbcTypes.VECTOR)
105107
);
106108

107109
/**
@@ -177,10 +179,13 @@ final class SqlTypeMap {
177179
entry(float[].class, JDBCType.ARRAY),
178180
entry(double[].class, JDBCType.ARRAY),
179181

180-
// Support binding OracleR2dbcReadable, Object[], and Map<String, Object>
181-
// to OBJECT (ie: STRUCT)
182+
// Support binding Map<String, Object> and OracleR2dbcObject to OBJECT
183+
// (ie: STRUCT)
182184
entry(Map.class, JDBCType.STRUCT),
183-
entry(OracleR2dbcObject.class, JDBCType.STRUCT)
185+
entry(OracleR2dbcObject.class, JDBCType.STRUCT),
186+
187+
// Support binding oracle.sql.VECTOR to VECTOR
188+
entry(VECTOR.class, OracleType.VECTOR)
184189
);
185190

186191
/**
@@ -269,6 +274,7 @@ else if (r2dbcType instanceof OracleR2dbcTypes.ObjectType)
269274
* <li>{@link Period} : INTERVAL YEAR TO MONTH</li>
270275
* <li>{@link RowId} : ROWID</li>
271276
* <li>{@link OracleJsonObject} : JSON</li>
277+
* <li>{@link oracle.sql.VECTOR} : VECTOR</li>
272278
* </ul>
273279
* @param javaType Java type to map
274280
* @return SQL type mapping for the {@code javaType}

src/test/java/oracle/r2dbc/impl/OracleReadableMetadataImplTest.java

+37-11
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@
2727
import io.r2dbc.spi.Parameters;
2828
import io.r2dbc.spi.R2dbcType;
2929
import io.r2dbc.spi.ReadableMetadata;
30-
import io.r2dbc.spi.Statement;
3130
import io.r2dbc.spi.Type;
3231
import oracle.jdbc.OracleType;
3332
import oracle.r2dbc.OracleR2dbcObject;
3433
import oracle.r2dbc.OracleR2dbcObjectMetadata;
3534
import oracle.r2dbc.OracleR2dbcTypes;
35+
import oracle.sql.VECTOR;
3636
import oracle.sql.json.OracleJsonFactory;
3737
import oracle.sql.json.OracleJsonObject;
38+
import org.junit.jupiter.api.Assumptions;
3839
import org.junit.jupiter.api.Test;
3940
import reactor.core.publisher.Flux;
4041
import reactor.core.publisher.Mono;
@@ -43,6 +44,7 @@
4344
import java.nio.ByteBuffer;
4445
import java.sql.JDBCType;
4546
import java.sql.RowId;
47+
import java.sql.SQLException;
4648
import java.sql.SQLType;
4749
import java.time.Duration;
4850
import java.time.LocalDate;
@@ -53,6 +55,7 @@
5355
import java.time.ZoneOffset;
5456
import java.util.Arrays;
5557
import java.util.stream.Collectors;
58+
import java.util.stream.DoubleStream;
5659
import java.util.stream.IntStream;
5760

5861
import static java.lang.String.format;
@@ -258,10 +261,6 @@ public void testDateTimeTypes() {
258261
OracleR2dbcTypes.INTERVAL_DAY_TO_SECOND, 9, 9, Duration.class,
259262
Duration.parse("+P123456789DT23H59M59.123456789S"));
260263

261-
// Expect ROWID and String to map.
262-
// Expect UROWID and String to map.
263-
// Expect JSON and OracleJsonObject to map.
264-
265264
}
266265
finally {
267266
tryAwaitNone(connection.close());
@@ -305,17 +304,14 @@ public void testRowIdTypes() {
305304

306305
/**
307306
* Verifies the implementation of {@link OracleReadableMetadataImpl} for
308-
* JSON type columns. When the test database older than version 21c, this test
309-
* is expected to fail with an ORA-00902 error indicating that JSON is not
310-
* a valid data type. The JSON type was added in 21c.
307+
* JSON type columns. When the test database is older than version 21c, this
308+
* test is ignored; The JSON type was added in 21c.
311309
*/
312310
@Test
313311
public void testJsonType() {
314-
315312
// The JSON data type was introduced in Oracle Database version 21c, so this
316313
// test is a no-op if the version is older than 21c.
317-
if (databaseVersion() < 21)
318-
return;
314+
Assumptions.assumeTrue(databaseVersion() >= 21);
319315

320316
Connection connection =
321317
Mono.from(sharedConnection()).block(connectTimeout());
@@ -529,6 +525,36 @@ public void testObjectTypes() {
529525
}
530526
}
531527

528+
/**
529+
* Verifies the implementation of {@link OracleReadableMetadataImpl} for
530+
* VECTOR type columns. When the test database is older than version 23ai,
531+
* this test is ignored; The VECTOR type was added in 23ai.
532+
*/
533+
@Test
534+
public void testVectorType() throws SQLException {
535+
Assumptions.assumeTrue(databaseVersion() >= 23);
536+
537+
Connection connection =
538+
Mono.from(sharedConnection()).block(connectTimeout());
539+
try {
540+
double[] doubleArray =
541+
DoubleStream.iterate(0d, previous -> previous + 0.1d)
542+
.limit(30)
543+
.toArray();
544+
VECTOR vector = VECTOR.ofFloat64Values(doubleArray);
545+
546+
// Expect VECTOR and double[] to map.
547+
verifyColumnMetadata(
548+
connection, "VECTOR", OracleType.VECTOR, OracleR2dbcTypes.VECTOR,
549+
null, null,
550+
VECTOR.class, vector);
551+
}
552+
finally {
553+
tryAwaitNone(connection.close());
554+
}
555+
556+
}
557+
532558
/**
533559
* Calls
534560
* {@link #verifyColumnMetadata(Connection, String, SQLType, Type, Integer, Integer, Nullability, Class, Object)}

0 commit comments

Comments
 (0)