Remove a Field from an API

Remove stand pricing from the newer API while keeping older clients compatible.

Deletion of a field from the API.

Business Step

Dave’s lemonade kiosk is growing into a fast-food chain model.

In the earlier sales API, stand_info included price_per_cup. That made sense when every stand behaved like a simple lemonade cart. As the business grows, pricing becomes a broader business concern. The newer sales API should describe the stand without exposing cup pricing as part of the stand object.

The challenge is compatibility. Existing clients still expect price_per_cup in stand_info, while newer clients should move forward without it.

Evolution Goal

This scenario removes price_per_cup from the newer duality view API.

There are two ways to solve the problem:

Case Approach
Case 1 Keep the backing column in stands, but hide price_per_cup from sales_v2. If DML through sales_v2 still needs a value, generate one behind the scenes.
Case 2 Remove price_per_cup from stands, then keep sales_v1 compatible by generating the old field in the view.

sales_v1 remains the older compatibility API. sales_v2 is the newer API introduced by this scenario.

Documentation view Role
sales_v1 Older compatibility API. Existing clients still see price_per_cup.
sales_v2 Newer API. stand_info no longer exposes price_per_cup.

Case 1: Column retained, field removed from the new API

In this version, price_per_cup is removed only from the newer JSON API.

The backing column still exists in the stands table. That means the database can continue storing price_per_cup, but sales_v2 no longer exposes it to newer clients.

This is the safer first step in field deletion. The public document shape changes, but the relational schema still has the old column available.

Base Table Default

Because sales_v2 no longer sends price_per_cup, the base table must be able to provide a value when a new stand row is inserted through the newer view.

ALTER TABLE stands MODIFY (
    price_per_cup DEFAULT 2.75 NOT NULL
);

The default belongs on the base table because the newer view does not expose the field.

sales_v2 Without price_per_cup

sales_v2 removes price_per_cup from stand_info.

CREATE OR REPLACE JSON RELATIONAL DUALITY VIEW sales_v2 AS
sales @insert @update @delete
{
    _id: sale_id,
    date: sale_date,
    quantity,
    amount,
    product_type,
    tags,
    customer_name,
    email_address,
    stand_info: stands @insert @update @nodelete
    {
        _id: stand_id,
        location
    }
};

The newer document shape is now smaller.

{
    "_id": 7,
    "date": "2026-01-17",
    "quantity": 10,
    "amount": 25.00,
    "product_type": "Berry Lemonade",
    "tags": "seasonal",
    "customer_name": "talia",
    "email_address": "talia@example.com",
    "stand_info": {
        "_id": 4,
        "location": "CAMPUS CART, WEST QUAD"
    }
}

What This Solves

sales_v2 gives newer clients the cleaner stand_info shape. The newer application does not see price_per_cup and does not send it during DML.

The stands table still owns the physical column. If a new stand row is created through sales_v2, the table default supplies price_per_cup.

Case 1 Behavior

API or schema object Behavior
sales_v1 Still exposes price_per_cup because older clients expect it.
sales_v2 Does not expose price_per_cup.
stands.price_per_cup Still exists in the relational schema.
Base table default Supplies price_per_cup when sales_v2 inserts a stand without that field.

Case 1 Limitation

This approach depends on the base column being nullable or having a default value. If price_per_cup is NOT NULL and has no default, inserts through sales_v2 can fail because the newer document no longer provides that field.

This is only API-level deletion. The field is removed from sales_v2, but it still exists in the relational schema and remains available to sales_v1.

Case 2: Column removed, old API remains compatible

In this version, Dave goes further.

The business no longer wants price_per_cup stored in the stands table. The column is physically removed from the relational schema.

ALTER TABLE stands DROP COLUMN price_per_cup;

After this point, sales_v2 can continue to omit price_per_cup naturally because the newer API does not need it.

The harder part is sales_v1. Existing clients still expect the old field. Since the backing column is gone, sales_v1 must synthesize the value.

sales_v1 With a Generated Compatibility Field

CREATE OR REPLACE JSON RELATIONAL DUALITY VIEW sales_v1 AS
sales @insert @update @delete
{
    _id: sale_id,
    date: sale_date,
    quantity,
    amount,
    product_type,
    tags,
    customer_name,
    email_address,
    stand_info: stands @insert @update @nodelete
    {
        _id: stand_id,
        location,
        price_per_cup @generated(sql: "2.75")
    }
};

The older document shape still includes price_per_cup.

{
    "_id": 8,
    "date": "2026-01-18",
    "quantity": 4,
    "amount": 11.00,
    "product_type": "Ginger Lemonade",
    "tags": "seasonal",
    "customer_name": "sahil",
    "email_address": "sahil@example.com",
    "stand_info": {
        "_id": 1,
        "location": "MARKET CART, NORTH PLAZA",
        "price_per_cup": 2.75
    }
}

The field still exists in the older JSON API. It no longer exists as stored stand data.

What This Solves

Case 2 completes the schema cleanup.

The newer API has already moved on. The relational schema now moves on too. sales_v1 remains usable because price_per_cup is generated for compatibility.

This is different from Case 1. In Case 1, the field is hidden from the newer view but still stored. In Case 2, the field is removed from storage and generated only for the older view.

Case 2 Behavior

API or schema object Behavior
sales_v1 Still exposes price_per_cup for older clients.
sales_v2 Does not expose price_per_cup.
stands.price_per_cup Removed from the relational schema.
price_per_cup @generated Recreates the old field as compatibility output.

Case 2 Limitation

After the backing column is dropped, price_per_cup becomes compatibility-only. Older clients may still send a value for this field, but that value should not be treated as persisted stand data.

A generated compatibility field is safe only when the old field can become constant, derived, or ignored. It is not safe when old clients still depend on row-specific stored values.

New Behavior Used

This scenario introduces two important compatibility techniques.

Feature Why it matters
@hidden Keeps a field out of the JSON document while still allowing the view to use it internally.
@generated Supplies a value when the client does not provide one, or when the original backing column no longer exists.

Evolution Check

By the end of this scenario, the evolution proves the following:

Topic Takeaway

Field deletion is not one single move.

The safer first move is to remove the field from the newer JSON API while the relational column still exists. That gives newer clients the right shape without forcing immediate storage cleanup.

The deeper move is to remove the backing column. At that point, the older API needs an explicit compatibility plan. In this scenario, sales_v1 keeps price_per_cup alive with @generated, while sales_v2 moves forward without exposing the field.