Aster Web Data / Connect your data
OracleCommercial
How Aster Web Data uses Oracle Database, and how Oracle's types, empty strings and results shape what clients receive.
If your data is in Oracle Database, Aster Web Data serves it from your existing tables through Oracle's own Rust driver, with no Instant Client to install. Filters, sorts, pages and counts run in Oracle, and each result is Oracle's own, decoded exactly or refused. Where Oracle's answer differs from OData's, Oracle's stands. The difference users notice first is that VARCHAR2 stores an empty string as null; Departures from OData lists the others.
Two choices need care: the TLS setup, because Oracle's ca_file adds to the public roots rather than replacing them, and the version column that gives a set its ETag, because Oracle has no row version Aster can use.
Connecting
Give the connection's parts, with the password from an environment variable or a file:
databases:
enterprise:
kind: oracle
host: database.example.com
port: 1522 # the listener's TCPS port
user: ASTER_APP
password_env: ASTER_ORACLE_PASSWORD
database: FREEPDB1 # the service name
schema: ASTER_APP # the user that owns the tables
tls: verify-full
ca_file: /etc/aster/oracle-ca.pem
In place of the parts, url_env or url_file can hold a JSON document with user, password and an Easy Connect connect_string. Secrets explains the rules, and the configuration reference lists every key with its default. These behave in a way particular to Oracle:
schemanames an existing user that owns the tables.setupnever creates or drops a user.tls: verify-fullconnects over TCPS, verifies the certificate chain and the host name, and never falls back to TCP. A verified JSON connection must use Easy Connect.ca_fileadds its certificates to the driver's public root certificates; it does not replace them. A certificate from any trusted public authority still passes when its host name matches.checkprints this and start-up logs it.requireandverify-caare refused, because the driver always checks both the certificate and the host name over TCPS.preferuses TCP for connection parts. Strict TLS needsverify-full. Plain TCP suits a trusted network only.read_only: truemakes the driver connection refuse every write.
The tests run on Oracle AI Database 26ai (23.26).
Start-up check
When the service opens the database, it reads the ALL_ catalog views for each data source's table, keeping quoted identifiers and the positions of composite keys, and compares the native types and decimal precision and scale with the configuration. A failed check makes only that database's sets unavailable; A database goes down describes what clients see. To run the check on its own, use the check command.
Types
| OData type | Column created by recreate |
Notes |
|---|---|---|
Edm.Int64, enumerations |
NUMBER(19,0) |
A value out of range is refused when read, naming the column. |
Edm.Decimal(p, s) |
NUMBER(p,s) |
Precision and scale are required, with a precision of at most 28. Never converted through a float. |
Edm.Double |
BINARY_DOUBLE |
NaN, infinities, signed zero and subnormals. |
Edm.Boolean |
NUMBER(1,0) |
Exactly 0 or 1. |
Edm.String |
VARCHAR2(n CHAR) |
At most 1,000 characters. The empty string is stored as null. |
Edm.Date |
DATE |
Midnight, years 1 to 9999. |
Edm.TimeOfDay |
NUMBER(14,0) |
Nanoseconds since midnight. |
Edm.DateTimeOffset |
TIMESTAMP(9) WITH TIME ZONE |
Instants in UTC, with nanoseconds. |
Edm.Duration |
NUMBER(25,0) |
Integral nanoseconds. |
Edm.Guid |
RAW(16) |
Exactly 16 bytes. |
Edm.Binary |
BLOB |
An empty value is distinct from null. |
| 128-bit unsigned integer | VARCHAR2(39 CHAR) |
Zero-padded decimal digits. |
| JSON | CLOB |
UTF-8 text. |
Collections are refused, and a float column cannot be read as a decimal. A timestamp stored with a named region is read through SYS_EXTRACT_UTC, keeping its exact instant. TIMESTAMP WITH LOCAL TIME ZONE is not mapped.
Collation and case
Every session sets NLS_COMP and NLS_SORT to BINARY, NLS_NUMERIC_CHARACTERS to ., and TIME_ZONE to +00:00, so comparison and sorting are binary in AL32UTF8 order, and trailing spaces count. Ordinary columns report USING_NLS_COMP, which $metadata keeps; tables the service creates write no COLLATE clause. A column with another named collation is refused.
tolower and toupper are LOWER and UPPER, with Oracle's character mapping. contains, startswith and endswith are LIKE with the pattern bound and a backslash escape.
Where each operation runs
Each OData operator becomes the Oracle operator with the same meaning:
| OData | Oracle | Result |
|---|---|---|
eq, ne, lt, le, gt, ge, in |
Comparisons and IN |
OData's rules for nulls are kept. |
add, sub, mul, unary - on integers |
+, -, *, - |
NUMBER widens past 64 bits; a returned value that does not fit is refused. |
div on integers |
TRUNC(a / b) |
Truncates toward zero. |
mod |
MOD(a, b) |
Takes the dividend's sign. |
| Decimal and double arithmetic | +, -, *, / |
Oracle's precision and special values. |
divby |
BINARY_DOUBLE division |
INF, -INF or NaN on zero. |
round, ceiling, floor |
ROUND, CEIL, FLOOR |
|
contains, startswith, endswith, like |
LIKE |
|
tolower, toupper |
LOWER, UPPER |
|
length |
LENGTH |
Characters in AL32UTF8. |
substring |
SUBSTR(s, start + 1, length) |
Zero-based, as OData is. |
indexof |
INSTR(s, needle) - 1 |
-1 when absent. |
trim, concat |
TRIM, ` |
|
count, sum, avg, min, max |
Oracle's aggregates | Decoded exactly or refused. |
| Paging and null placement | OFFSET ... FETCH, NULLS FIRST and NULLS LAST |
These run in the service, within the limits on rows it may hold: regular expressions, general casts, and date and time arithmetic Oracle has no exact form for. mod of doubles and comparisons of BLOB values are not supported. Each $expand level is read by key batches, one statement per level.
Errors
| Case | Response |
|---|---|
| ORA-01426 (overflow), ORA-01438 (value too large), ORA-01476 (division by zero), ORA-01722 (invalid number) | 400 EvaluationFailed |
| Constraint violation | 409 Conflict |
| ORA-00060 (deadlock), ORA-08177 (serialisation failure) | 409 TransactionConflict, after the atomic group's retries |
| ORA-01013 (the call was cancelled at the statement timeout) | 503 Timeout |
| ORA-00054, ORA-30006 (the row is locked and the wait ran out) | 503 PoolExhausted |
| Lost connection, such as ORA-03113, ORA-03135 or a listener error | 503 ConnectionLost. The connection is discarded. |
| Any other database error | 500 InternalError. The client gets a generic message, and the database's detail goes to the log. |
Nulls and ordering
ORDER BY names NULLS FIRST or NULLS LAST as OData asks, with the entity key ending every sort. NaN equals itself and sorts after every other number. $count=true runs the page and a COUNT in one read-only snapshot, so the total agrees with the page.
Departures from OData
Where OData 4.01 defines a result and Oracle gives another, Oracle's result stands. These are the places:
- The empty string is null in
VARCHAR2, and asubstringpast the end returns null. concattreats null as the empty string:null || 'x'is'x'.trimremoves spaces only, not every Unicode whitespace character.NUMBERwidens past 64 bits. A filter may compare the widened value, and returning it as an integer fails. A decimal division can produce 38 significant digits and fail to read as a 28-digit decimal.modby zero returns the dividend. An integer or decimaldivby zero raises ORA-01476.BINARY_DOUBLEoverflow gives infinity, a division by zero gives infinity orNaN,NaNequals itself and sorts greatest, androundties go to even.- Case mapping and a negative
substringstart follow Oracle's rules. ca_fileadds to the public roots instead of replacing them, as described under Connecting.
Writes and transactions
- Isolation. Read committed and serializable are available. A read that takes several statements, such as a page with its count, runs in a
SET TRANSACTION READ ONLYsnapshot. - Writes by key. An update or delete locks its row with
SELECT ... FOR UPDATE, held to the commit, and compares the ETag's token there. - Inserts use
RETURNING ROWIDand read the row back, after a query that checks the result types. A multi-row insert runs as single-row inserts in the same transaction. setup: recreateis not atomic, because Oracle's DDL commits as it runs.
| Write | Statements |
|---|---|
POST |
3: the type check, the insert, the read by ROWID |
POST with $expand |
5 |
| Nested insert of a parent and two children | 11, each row its own insert |
PATCH or PUT with an ETag |
2: the lock, then one block that updates and reads the row. A view, a synonym or a large text or binary value takes 3. |
PATCH or DELETE without an ETag, DELETE with one |
2 |
ETags.
etag: nativeis refused, andORA_ROWSCNis not a token, because it changes only when the transaction commits, too late for the write to answer with the new tag. The usual token is a version column Aster advances (versionField: version, aNUMBER(19)column), or one a trigger advances, named inetag: {columns: [version]}:ALTER TABLE orders ADD (version NUMBER(19) DEFAULT 1 NOT NULL); CREATE OR REPLACE TRIGGER orders_version BEFORE UPDATE ON orders FOR EACH ROW BEGIN :NEW.version := :OLD.version + 1; END;ETags and concurrent edits explains the tokens.
Indexes and sort order
Index the columns your screens filter and sort on, as Indexes describes. In the benchmark, equality, prefix and child IN queries used their ordinary indexes, while a text sort with single-column indexes chose a full scan and a sort.
Scaffold
The scaffold command reads the ALL_ catalog views for the tables, columns, keys, defaults, generated columns and comments, and writes a starting data-source configuration for the columns it can map exactly. Connect your own database walks through it.