🔔This is the 3rd part about BTP-related topics. See others here.

Database schema migrations can sometimes be complicated - the CAP framework tries to help us here. If you have worked or are working with ABAP, you are probably used to the platform doing the work for you (automatically or semi-automatically) related to adding, removing, or changing field types, even if there is already data in the table. In CAP, we also have support - but in certain situations, we need to help ourselves and, above all, be cautious.

Sample Application

The example application is a simple service with a model copied entirely from the file in SAP sample app. After cds init sample-hana-migrations and copying the above content to the db/schema.cds folder, I added the service:

srv/service.cds

using {sap.capire.reviews as db} from '../db/schema.cds';

service ReviewsService {
  entity Reviews as projection on db.Reviews;
}

Then I added the HANA and MTA features.

cda add hana, mta
cds build --production
npx mbt build
cf deploy mta_archives\sample-hana-migrations_1.0.0.mtar

HANA Cloud - what we have at the start

Let's see how the tables in the database created from our model look:

HANA Cloud reviews table HANA Cloud likes table

The tables are currently empty - what will happen if we completely "demolish" them - change the column names and their data types? In the Reviews entity, I changed every field name and data type.

entity Reviews {
  key ID          : UUID;
      what        : Int16;
      who         : Int16;
      how_many    : String(20);
      about       : Int16;
      description : Decimal(10, 2);
      happened     : Date;
      loved       : Composition of many Likes
                      on loved.review = $self;
      counted     : String(90);
}

After deployment:

HANA Cloud reviews table

It went smoothly - the table was empty, so there was no problem with the schema migration.

Let's move on to more interesting cases.

Migrations for a table with data

I restored the Reviews entity to its initial state and added a few rows.

HANA Cloud reviews table content

Let's now look at various basic scenarios that may occur on the developer's path.

Adding a new column

We add a new column accepted with the Boolean type:

entity Reviews {
  key ID       : UUID;
      subject  : ReviewedSubject;
      reviewer : User;
      rating   : Rating;
      accepted : Boolean; // new
      title    : String(111);
      text     : String(1111);
      date     : DateTime;
      likes    : Composition of many Likes
                   on likes.review = $self;
      liked    : Integer default 0;
}

It went smoothly, no problems - the new column was added with NULLs inside.

New column in the Reviews

When adding new columns, the table is modified (ALTER TABLE) and no further actions are required on our part.

Removing a column

Now let's look at two cases of removing a column. First, let's take the newly created accepted field - there are no values in this column in any row.

entity Reviews {
  key ID       : UUID;
      subject  : ReviewedSubject;
      reviewer : User;
      rating   : Rating;
      title    : String(111);
      text     : String(1111);
      date     : DateTime;
      likes    : Composition of many Likes
                   on likes.review = $self;
      liked    : Integer default 0;
}

After deployment:

Reviews after remove of accepted column

The change went smoothly.

For the changes so far, everything is going well, according to the documentation - if compatible changes can be applied, it happens without any data loss or the need for additional migration logic.

Now let's see what happens when we want to remove the rating field - in all rows in this column, we already have values.

entity Reviews {
  key ID       : UUID;
      subject  : ReviewedSubject;
      reviewer : User;
      title    : String(111);
      text     : String(1111);
      date     : DateTime;
      likes    : Composition of many Likes
                   on likes.review = $self;
      liked    : Integer default 0;
}

After a smooth deployment:

Reviews after remove of rating column

So the column with data was removed. Therefore, be cautious with changes in the cds files with the model, as accidental field deletions can lead to potential data loss in the production table - there is no warning.

Renaming a column

Let's see what happens when renaming a column - changing the field name title to heading:

entity Reviews {
  key ID       : UUID;
      subject  : ReviewedSubject;
      reviewer : User;
      heading  : String(111);
      text     : String(1111);
      date     : DateTime;
      likes    : Composition of many Likes
                   on likes.review = $self;
      liked    : Integer default 0;
}

This time we lost all the titles - the column was "changed", which means according to the documentation, the old column was dropped and a new one was added.

CAP documentation

Changing the column type

Let's now look at type changes. The date field in our model has the DateTime type, let's see what happens when converting it to just a date (Date).

entity Reviews {
  key ID       : UUID;
      subject  : ReviewedSubject;
      reviewer : User;
      heading  : String(111);
      text     : String(1111);
      date     : Date; // change from DateTime
      likes    : Composition of many Likes
                   on likes.review = $self;
      liked    : Integer default 0;
}

Data in the table before conversion:

Date column

After the change and deployment:

Date column as date

Not bad - the data is preserved correctly. After restoring the DateTime format, the dates are still correctly preserved with the time set to zero:

Date column as timestamp

Now let's convert this field to String:

entity Reviews {
  key ID       : UUID;
      subject  : ReviewedSubject;
      reviewer : User;
      heading  : String(111);
      text     : String(1111);
      date     : String(500); // change from DateTime
      likes    : Composition of many Likes
                   on likes.review = $self;
      liked    : Integer default 0;
}

After deployment:

Date column as String

The data was preserved without any issues. Now let's see the conversion of this field to Integer:

entity Reviews {
  key ID       : UUID;
      subject  : ReviewedSubject;
      reviewer : User;
      heading  : String(111);
      text     : String(1111);
      date     : Integer; // change from String
      likes    : Composition of many Likes
                   on likes.review = $self;
      liked    : Integer default 0;
}

When attempting to deploy, we encounter an error. In the database deployer module logs, we have:

Date column as String

So we cannot perform such an incompatible change. Let's change the type back to DateTime for the date field and look at another thing - let's change the liked field first to String:

entity Reviews {
  key ID       : UUID;
      subject  : ReviewedSubject;
      reviewer : User;
      heading  : String(111);
      text     : String(1111);
      date     : DateTime; // change from String
      likes    : Composition of many Likes
                   on likes.review = $self;
      liked    : String;
}

After deployment, the date column has the correct data. The liked column also - the number of likes was correctly converted to a string:

Liked column converted

Now let's change liked to another numeric type - let's use Double:

entity Reviews {
  key ID       : UUID;
      subject  : ReviewedSubject;
      reviewer : User;
      heading  : String(111);
      text     : String(1111);
      date     : DateTime; // change from String
      likes    : Composition of many Likes
                   on likes.review = $self;
      liked    : Double;
}

After deployment, we see that there were no issues converting strings with numbers to numeric values:

Liked column converted to Double

Another conversion is shortening the data type - let's change the length of the string for heading to 10 characters:

entity Reviews {
  key ID       : UUID;
      subject  : ReviewedSubject;
      reviewer : User;
      heading  : String(10); // length changed from 111 to 10
      text     : String(1111);
      date     : DateTime;
      likes    : Composition of many Likes
                   on likes.review = $self;
      liked    : Double;
}

Shortening the type caused data loss, meaning the column was dropped and recreated with the new type.

Title after data type change

Help in determining which conversions will work can be found in the documentation, with tables where we can check what to expect when attempting to convert one type to another.

How does it work?

The CAP framework uses the HANA Deployment Infrastructure to deploy tables for HANA. After running the cds build command, you will find various files with the hdb... extension in the gen directory.

Example hdbtable file for the Reviews table contains the SQL that will be executed on the database:

hdbtable file content

After various changes made in this article, we always ended up with a file that was then interpreted by HDI. As you can see, it does not contain any hints regarding the changes introduced.

Summary

Through various examples, we have seen that some changes are seamless, but some can cause us trouble (data loss) if we are not aware of the mechanisms.
In the next episodes, we will see how to address this.