Oracle catalog
The Oracle catalog connects Gravitino to an Oracle database over JDBC, exposing Oracle users as schemas and Oracle tables as tables. It is a read-write catalog for tables and a read-only catalog for schemas, because Oracle schemas are database users and Gravitino does not create or drop them.
Oracle folds an unquoted identifier to uppercase and preserves the case of a quoted one. Gravitino reproduces that rule rather than hiding it, so the naming section below matters more here than it does for most catalogs.
Quick Start
1. Confirm the driver is present. The Oracle JDBC driver ships inside the connector at
catalogs/jdbc-oracle/libs, so there is no separate download step. This differs from the MySQL and
PostgreSQL catalogs, which require you to supply a driver yourself.
2. Create the catalog. Declare the connection to the Oracle instance and the account Gravitino uses to reach it.
- REST
- Java
- Python
GRAVITINO_URL=http://localhost:8090
METALAKE=metalake_demo
curl -X POST -H "Content-Type: application/json" \
-d '{
"name": "oracle_catalog",
"type": "RELATIONAL",
"provider": "jdbc-oracle",
"comment": "Oracle catalog",
"properties": {
"jdbc-url": "jdbc:oracle:thin:@{oracle_host}:1521:{oracle_sid}",
"jdbc-user": "{oracle_user}",
"jdbc-password": "{oracle_password}",
"jdbc-driver": "oracle.jdbc.OracleDriver"
}
}' \
"${GRAVITINO_URL}/api/metalakes/${METALAKE}/catalogs"
GravitinoClient client = GravitinoClient.builder("http://localhost:8090")
.withMetalake("metalake_demo")
.build();
Map<String, String> properties = ImmutableMap.<String, String>builder()
.put("jdbc-url", "jdbc:oracle:thin:@{oracle_host}:1521:{oracle_sid}")
.put("jdbc-user", "{oracle_user}")
.put("jdbc-password", "{oracle_password}")
.put("jdbc-driver", "oracle.jdbc.OracleDriver")
.build();
Catalog catalog = client.createCatalog(
"oracle_catalog",
Catalog.Type.RELATIONAL,
"jdbc-oracle",
"Oracle catalog",
properties);
gravitino_client = GravitinoClient(uri="http://localhost:8090", metalake_name="metalake_demo")
catalog = gravitino_client.create_catalog(
name="oracle_catalog",
catalog_type=Catalog.Type.RELATIONAL,
provider="jdbc-oracle",
comment="Oracle catalog",
properties={
"jdbc-url": "jdbc:oracle:thin:@{oracle_host}:1521:{oracle_sid}",
"jdbc-user": "{oracle_user}",
"jdbc-password": "{oracle_password}",
"jdbc-driver": "oracle.jdbc.OracleDriver",
},
)
3. List the schemas. Gravitino returns the Oracle users visible to the connecting account, filtered as described in Schemas below.
Catalog Properties
The Oracle catalog defines no properties of its own beyond the JDBC properties every JDBC catalog shares. Four of those are required.
| Property | Description | Example |
|---|---|---|
jdbc-url | The Oracle JDBC connection string | jdbc:oracle:thin:@localhost:1521:ORCL |
jdbc-user | The Oracle account Gravitino connects as | gravitino |
jdbc-password | The password for that account | gravitino |
jdbc-driver | The driver class name | oracle.jdbc.OracleDriver |
Gravitino issues ALTER SESSION SET CURRENT_SCHEMA on each connection before working with a table,
so the connecting account needs privileges on every schema you intend to reach through the catalog,
not only on its own.
Schemas
A schema in this catalog is an Oracle user. Gravitino lists and loads them but does not create,
alter, or drop them, since managing Oracle accounts is a database administration task rather than a
metadata one. Attempting to create or drop a schema raises an unsupported operation error. Schema
comments are also unavailable, because Oracle has no COMMENT ON SCHEMA syntax.
Listing filters out Oracle's own accounts. On Oracle 12c and later Gravitino uses the
ORACLE_MAINTAINED column of ALL_USERS, which covers accounts installed by optional components.
On Oracle 11g that column does not exist, so a built-in list of known system users is used instead,
and that list still applies on newer releases as a fallback for accounts that are not marked
consistently.
Tables
Table create, load, list, rename, alter, drop, and purge are all supported. A drop is always issued
as DROP TABLE ... PURGE, so the table does not land in Oracle's recycle bin and cannot be
recovered with FLASHBACK TABLE.
Oracle does not support transactional DDL. An alter that carries several changes is executed as one statement per change in sequence, so a failure partway through leaves the earlier statements committed. The failing statement and the statements that already ran are both written to the server log.
Table Properties
Four properties come back from Oracle's ALL_TABLES view. Only tablespace can be supplied, and
only at create time.
| Property | Source | Settable |
|---|---|---|
tablespace | ALL_TABLES.TABLESPACE_NAME | At table creation only |
partitioned | ALL_TABLES.PARTITIONED | No, derived by Oracle |
row_movement | ALL_TABLES.ROW_MOVEMENT | No, derived by Oracle |
compression | ALL_TABLES.COMPRESSION | No, derived by Oracle |
Table property changes are not supported at all, so a tablespace chosen at creation cannot be
changed afterward through Gravitino.
Table and column comments are supported. Oracle's JDBC driver does not populate REMARKS in
DatabaseMetaData.getColumns(), so Gravitino reads column comments from ALL_COL_COMMENTS
directly.
Indexes
Primary key and unique key are the only index types this catalog supports, both at create time and
through an alter. They are read back from ALL_CONSTRAINTS and ALL_CONS_COLUMNS, which means only
constraint-backed indexes appear, not standalone Oracle indexes.
Index names are case-sensitive and are never folded, so a name must be given exactly as it was created for a delete to match it.
Partitioning
Oracle tables can be created with single-level range, list, or hash partitioning. Range partitioning takes a single column, and composite partitioning is not supported in either direction: a table already partitioned on more than one range column cannot be loaded.
| Gravitino transform | Oracle clause | Restriction |
|---|---|---|
| Range | PARTITION BY RANGE | One column, at least one partition |
| List | PARTITION BY LIST | Single level |
| Bucket | PARTITION BY HASH | Single level |
Range partition bounds are read back by evaluating Oracle's stored HIGH_VALUE expression, so a
bound that Oracle cannot evaluate to a literal of a supported type causes the load to fail rather
than returning a partial result.
Table distribution and sort orders are not supported.
Supported Table Changes
| Change | Supported |
|---|---|
| Rename table | Yes |
| Update table comment | Yes |
| Add column | Yes, at the default position only |
| Delete column | Yes |
| Rename column | Yes |
| Update column type | Yes |
| Update column nullable | Yes |
| Update column default | Yes |
| Update column comment | Yes |
| Add or delete index | Yes, for primary key and unique key |
| Update column position | No, Oracle cannot reorder columns |
| Auto increment | No, at create time or through an alter |
| Set or remove property | No |
Nested column names are not supported by any of the column changes.
Identifier Case and Quoting
Gravitino follows Oracle's own rule rather than treating the catalog as uniformly case-sensitive or case-insensitive. An unquoted name is folded to uppercase. A name written with literal double quotes keeps its exact case, and the quotes are consumed as a signal rather than stored.
| Name as given | Physical Oracle object | Validation |
|---|---|---|
app_user | APP_USER | Must match ^[A-Za-z][A-Za-z0-9_$#]{0,29}$ |
"MyTable" | MyTable | Content is 1 to 30 bytes and excludes . and NUL |
Two consequences are worth stating plainly. The quoting signal is not persisted, so a later reference to a case-sensitive object has to supply the quoted form again to resolve to the same physical name. And the 30-byte limit is measured in bytes of the database character set, not characters, so a multi-byte name reaches the limit sooner than its length suggests.
Oracle has no escape for a double quote inside a quoted identifier, so an embedded " is rejected
rather than doubled.
Names returned by a list operation come back exactly as Oracle stores them, with no quoting added.
Type Mapping
Oracle to Gravitino
| Oracle type | Gravitino type |
|---|---|
NUMBER with no precision or scale | External type NUMBER |
NUMBER(1) | Boolean |
NUMBER(p) where p is 2 to 3 | Byte |
NUMBER(p) where p is 4 to 5 | Short |
NUMBER(p) where p is 6 to 10 | Integer |
NUMBER(p) where p is 11 to 19 | Long |
NUMBER(p) where p is 20 or more | Decimal(p, 0) |
NUMBER(p,s) with positive s | Decimal(p, s) |
NUMBER(p,s) with negative s | External type NUMBER(p,s) |
VARCHAR2(n), VARCHAR(n) | Varchar(n) |
CHAR(n) | Char(n) |
NCHAR, NVARCHAR2 | External type |
CLOB, NCLOB | String |
BLOB, RAW, LONG RAW | Binary |
FLOAT, BINARY_DOUBLE | Double |
BINARY_FLOAT | Float |
DATE | Timestamp without time zone |
TIMESTAMP(p) | Timestamp without time zone, precision p |
TIMESTAMP(p) WITH TIME ZONE | Timestamp with time zone, precision p |
| Any other type | External type carrying the Oracle name |
TIMESTAMP WITH LOCAL TIME ZONE is rejected rather than mapped. Gravitino's timestamp type cannot
distinguish it from TIMESTAMP WITH TIME ZONE, so accepting it would silently change the semantics
of the column on a round trip.
A precision omitted by the driver is treated as Oracle's maximum of 38.
Gravitino to Oracle
| Gravitino type | Oracle type |
|---|---|
| Boolean | NUMBER(1) |
| Byte | NUMBER(3) |
| Short | NUMBER(5) |
| Integer | NUMBER(10) |
| Long | NUMBER(19) |
| Decimal(p, s) | NUMBER(p,s) |
| Float | BINARY_FLOAT |
| Double | BINARY_DOUBLE |
| String | CLOB |
| Varchar(n) | VARCHAR2(n) |
| Char(n) | CHAR(n) |
| Binary | BLOB |
| Timestamp without time zone | TIMESTAMP(p), TIMESTAMP(6) when no precision is set |
| Timestamp with time zone | TIMESTAMP(p) WITH TIME ZONE, TIMESTAMP(6) WITH TIME ZONE when no precision is set |
| External type | The Oracle type name it carries |
Date has no mapping. Oracle's DATE carries a time component and reads back as a timestamp, so
creating a column as Gravitino's date type is rejected. Use an external type of DATE when you need
the Oracle type by name.
Two mappings are deliberately asymmetric and worth knowing before you round-trip a table. String
becomes CLOB on the way out while VARCHAR2 with no precision becomes String on the way back, and
FLOAT becomes Double while Double becomes BINARY_DOUBLE.
Column Default Values
Literal defaults are carried through in both directions. Five Oracle functions are recognized as
function expressions rather than being treated as literal text: SYSDATE, SYSTIMESTAMP,
CURRENT_TIMESTAMP, CURRENT_DATE, and SYS_GUID. Any other expression is preserved as an
unparsed expression.
Oracle requires DEFAULT to precede NOT NULL in a column definition, and Gravitino generates the
clauses in that order.
Error Mapping
Oracle error codes are translated into Gravitino exceptions so that a caller sees the same failure types it would from any other catalog.
| Oracle error | Gravitino exception |
|---|---|
| ORA-01918, ORA-01435 user does not exist | No such schema |
| ORA-01920 user name conflicts with an existing name | Schema already exists |
| ORA-00942 table or view does not exist | No such table |
| ORA-00955 name is already used by an existing object | Table already exists |
| ORA-01031 insufficient privileges | Runtime error naming the cause |
Any other error code produces a generic runtime exception carrying the Oracle message, and the unrecognized code is written to the server log.