8.4 Flex Columns, Beyond the Basics

All about duality-view flex columns: rules of the road; when, where, and why to use them; field-name conflicts; gotchas.

Any tables underlying a duality view can have any number of JSON-type columns. At most one JSON column per table can be designated as a flex column at each position where that table is used in the view definition. If a given table is used only at one place in a view definition (a typical case) then only one flex column for the table can be used. If the same table is used in N different places in a view definition, then up to N different flex columns for the table can be designated at those places.

You can designate the same flex column to provide the fields for different places of the same document. Those different places share all of the fields stored in that flex column. Updates to any of the places must concord, by not providing different new fields or different values for the same field.

Note:

The same general behavior holds for a nonflex column: if used to support fields in multiple places of a document then all of those places share the same data. In the nonflex case only the field values must be the same; the field names can be different in different places.

In a given duality-view definition, you can't use the same JSON column as a flex column in one document place and as a nonflex column in another place. An error is raised if you try to do this.

In any table, a JSON column generally provides for flexible data: by default, its typing and structure are not constrained/specified in any way (for example, by a JSON schema).

The particularity of a JSON column that's designated as a flex column for a duality view is this:

  • The column value must be a JSON object or SQL NULL.

    This means that it must be declared as type JSON (OBJECT), not just JSON. Otherwise, an error is raised when you try to use that column in a duality-view definition.

    (This restriction doesn't apply to a nonflex JSON-type column; its value can be any JSON value: scalar, array, or object.)

  • On read, the object stored in a flex column is unnested: its fields are unpacked into the resulting document object.

    That is, the stored object is not included as such, as the value of some field in the object produced by the flex column's table. Instead, each of the stored object's fields is included in that document object.

    (Any value — object, array, or scalar — in a nonflex JSON-type column is just included as is; an object is not unnested, unless unnesting is explicitly specified in the duality-view definition. See Creating Duality Views.)

    For example, if the object in a given row of the flex column for table tab1 has fields foo and bar then, in the duality-view document that corresponds to that row, the object produced from tab1 also contains those fields, foo and bar.

  • On write, the fields from the document object are packed back into the stored object, and any fields not supported by other columns are automatically added to the flex column. That is, an unrecognized field "overflows" into the object in the JSON flex column.

    For example, if a new field toto is added to a document object corresponding to a table that has a flex column, then on insertion of the document if field toto isn't already supported by the table then field toto is added to the flex-column's object.

Note:

To require a nonflex JSON-type column to hold only object values (or SQL NULL) you can define it using the modified data type JSON (OBJECT), or you can use a JSON-Schema VALIDATE check constraint of {"type":"object"}. See Validating JSON Data with a JSON Schema in Oracle Database JSON Developer’s Guide.

More generally, you can require a nonflex JSON-type column to hold only scalar, object, or array JSON values, or any combination of those. And you can restrict scalar values to be of a specific type, such as a string or a date. For example, if the column type is JSON (OBJECT, SCALAR DATE) then it allows only values that are objects or dates.

A column designated as flex for a duality view is such (is flex) only for the view. For the table that it belongs to, it's just an ordinary JSON-type column, except that the value in each row must be a single JSON object or SQL NULL.

Different duality views can thus define different flex columns (that is, with different names) for the same table, each view's flex column suiting that view's own purposes, providing fields for the documents that only it supports.

Note:

If for some reason you actually want two or more duality views to share a flex column, then just give the flex column the same name when defining each view. This might sometimes be what you want, but be aware of the consequence.

Unlike nonflex columns, which are dedicated to individual fields that are specified explicitly in a view's definition, a flex column holds the data for multiple fields that are unknown to the view definition. A flex column is essentially a free pass for unrecognized incoming fields at certain locations in a document (that's its purpose: provide flexibility).

On write, an unrecognized field is stored in a flex column (of the table relevant to the field's location in the document). If two views with the same underlying table share a flex column there, then incoming fields unrecognized by either view get stored in that column, and on read those fields are exposed in the documents for both views.

Because a flex column's object is unnested on read, adding its fields to those produced by the other columns in the table, and because a JSON column is by default schemaless, changes to flex-column data can change the structure of the resulting document object, as well as the types of some of its fields.

In effect, the typing and structure of a duality view's supported documents can change/evolve at any level, by providing a flex column for the table supporting the JSON object at that level. (Otherwise, to allow evolution of typing and structure for the values of particular JSON fields, you can map nonflex JSON-type columns to those fields.)

You can change the typing and structure of a duality view's documents by modifying flex-column data directly, through the column's table. More importantly, you can do so simply by inserting or updating documents with fields that don't correspond to underlying relational columns. Any such fields are automatically added to the corresponding flex columns. Applications are thus free to create documents with any fields they like, in any objects whose underlying tables have a flex column.

However, be aware that unnesting the object from a flex column can lead to name conflicts between its fields and those derived from the other columns of the same table. Such conflicts cannot arise for JSON columns that don't serve as flex columns.

For this reason, if you don't need to unnest a stored JSON object — if it's sufficient to just include the whole object as the value of a field — then don't designate its column as flex. Use a flex column where you need to be able to add fields to a document object that's otherwise supported by relational columns.

The value of any row of a flex column must be a JSON object or the SQL value NULL.

SQL NULL and an empty object ({}) behave the same, except that they typically represent different contributions to the document ETAG value. (You can annotate a flex column with NOCHECK to remove its data from ETAG calculation. You can also use column annotation [NO]UPDATE, [NO]CHECK on a flex column.)

In a duality-view definition you designate a JSON-type column as being a flex column for the view by following the column name in the view definition with keywords AS FLEX in SQL or with annotation @flex in GraphQL.

For example, in this GraphQL definition of duality view dv1, column t1_json_col of table table1 is designated as a flex column. The fields of its object value are included in the resulting document as siblings of field1 and field2. (JSON objects have undefined field order, so the order in which a table's columns are specified in a duality-view definition doesn't matter.)

CREATE JSON RELATIONAL DUALITY VIEW dv1 AS
  table1 @insert @update @delete
    {_id       : id_col,
     t1_field1 : col_1,
     t1_json_col @flex,
     t1_field2 : col_2};

When a table underlies multiple duality views, those views can of course use some or all of the same columns from the table. A given column from such a shared table can be designated as flex, or not, for any number of those views.

The fact that a column is used in a duality view as a flex column means that if any change is made directly to the column value by updating its table then the column value must still be a JSON object (or SQL NULL).

It also means that if the same column is used in a table that underlies another duality view, and it's not designated as a flex column for that view, then for that view the JSON fields produced by the column are not unpacked in the resulting documents; in that view the JSON object with those fields is included as such. In other words, designation as a flex column is view-specific.

You can tell whether a given table underlying a duality view has a flex column by checking BOOLEAN column HAS_FLEX_COL in static dictionary views *_JSON_DUALITY_VIEW_TABS. You can tell whether a given column in an underlying table is a flex column by checking BOOLEAN column IS_FLEX_COL in static dictionary views *_JSON_DUALITY_VIEW_TAB_COLS. See ALL_JSON_DUALITY_VIEW_TABS and ALL_JSON_DUALITY_VIEW_TAB_COLS in Oracle Database Reference.

The data in both flex and nonflex JSON columns in a table underlying a duality view can be schemaless, and it is so by default.

But you can apply JSON schemas to any JSON-type columns used anywhere in a duality-view definition, to remove their flexibility ("lock" them). You can also impose a JSON schema on the documents generated/supported by a duality view.

Because the fields of an object in a flex column are unpacked into the resulting document, if you apply a JSON schema to a flex column the effect is similar to having added a separate column for each of that object's fields to the flex column's table using DML.

Whether a JSON-type column underlying a duality view is a flex column or not, by applying a JSON schema to it you change the logical structure of the data, and thus the structure of the documents supported by the view. You remove some schema flexibility, but you don't change the storage structure (tables).

See Also:

Using JSON to Implement Flexfields (video, 24 minutes)

Field Naming Conflicts Produced By Flex Columns

Because fields in a flex column are unpacked into an object that also has fields provided otherwise, field name conflicts can arise. There are multiple ways this can happen, including these:

  • A table underlying a duality view gets redefined, adding a new column. The duality view gets redefined, giving the JSON field that corresponds to the new column the same name as a field already present in the flex column for the same table.

    Problem: The field name associated with a nonflex column would be the same as a field in the flex-column data.

  • A flex column is updated directly (that is, not by updating documents supported by the view), adding a field that has the same name as a field that corresponds in the view definition to another column of the same underlying table.

    Problem: The field name associated with a nonflex column is also used in the flex-column data.

  • Two duality views, dv1 and dv2, share an underlying table, using the same column, jcol, as flex. Only dv1 uses nonflex column, foocol from the table, naming its associated field foo.

    Data is inserted into dv1, populating column foocol. This can happen by inserting a row into the table or by inserting a document with field foo into dv1.

    A JSON row with field foo is added to the flex column, by inserting a document into dv2.

    Problem: View dv2 has no problem. But for view dv1 field-name foo is associated with a nonflex column and is also used in the flex-column data.

It's not feasible for the database to prevent such conflicts from arising, but you can specify the behavior you prefer for handling them when they detected during a read (select, get, JSON generation) operation. (All such conflicts are detected during a read.)

You do this using the following keywords at the end of a flex-column declaration. Note that in all cases that don't raise an error, any field names in conflict are read from nonflex columns — that is, priority is always given to nonflex columns.

GraphQL SQL Behavior

(conflict: KEEP_NESTED)

KEEP [NESTED] ON [NAME] CONFLICT

(Keywords NESTED and NAME are optional.)

Any field names in conflict are read from nonflex columns. Field _nameConflicts (a reserved name) is added, with value an object whose members are the conflicting names and their values, taken from the flex column.

This is the default behavior.

For example, if for a given document nonflex field quantity has value 100, and the flex-column data has field quantity with value "314", then nonflex field quantity would keep its value 100, and field _nameConflicts would be created or modified to include the member "quantity":314.

(conflict: ARRAY)

ARRAY ON [NAME] CONFLICT

(Keyword NAME is optional.)

Any field names in conflict are read from nonflex columns. The value of each name that has a conflict is changed in its nonflex column to be an array whose elements are the values: one from the nonflex column and one from the flex-column data, in that order.

For example, if for a given document nonflex field quantity has value 100, and the flex-column data has field quantity with value "314", then nonflex field quantity would have its value changed to the array [100,314].

(conflict: IGNORE)

IGNORE ON [NAME] CONFLICT

(Keyword NAME is optional.)

Any field names in conflict are read from nonflex columns. The same names are ignored from the flex column.

(conflict: ERROR)

ERROR ON [NAME] CONFLICT

(Keyword NAME is optional.)

An error is raised.

For example, this GraphQL flex declaration defines column extras as a flex column, and it specifies that any conflicts that might arise from its field names are handled by simply ignoring the problematic fields from the flex column data:

extras: JSON @flex (conflict: IGNORE)

Note:

IGNORE ON CONFLICT and ARRAY ON CONFLICT are incompatible with ETAG-checking. An error is raised if you try to create a duality view with a flex column that is ETAG-checked and has either of these on-conflict declarations.

Note:

If the name of a hidden field conflicts with the name of a field stored in a flex column for the same table, then, in documents supported by the duality view the field is absent from the JSON object that corresponds to that table.