diff --git a/guides/databases.md b/guides/databases.md index 5e2979bf3..ab2325c95 100644 --- a/guides/databases.md +++ b/guides/databases.md @@ -22,13 +22,13 @@ impl-variants: true
-### Migrating to New Database Services? {.node} +### Migrating to the `@cap-js/` Database Services? {.node} -With CDS 8, the new database services for SQLite, PostgreSQL, and SAP HANA are now generally available. It's highly recommended to migrate. You can find instructions in the [migration guide](databases-sqlite#migration). Although the guide is written in the context of the new SQLite Service, the same hints apply to PostgreSQL and SAP HANA. +With CDS 8, the [`@cap-js`](https://github.com/cap-js/cds-dbs) database services for SQLite, PostgreSQL, and SAP HANA are generally available. It's highly recommended to migrate. You can find instructions in the [migration guide](databases-sqlite#migration). Although the guide is written in the context of the SQLite Service, the same hints apply to PostgreSQL and SAP HANA. ### Adding Database Packages {.node} -Following are cds-plugin packages for CAP Node.js runtime that support respective databases: +Following are cds-plugin packages for CAP Node.js runtime that support the respective databases: | Database | Package | Remarks | | ------------------------------ | ------------------------------------------------------------ | ---------------------------------- | @@ -37,7 +37,7 @@ Following are cds-plugin packages for CAP Node.js runtime that support respectiv | **[PostgreSQL](databases-postgres)** | [`@cap-js/postgres`](https://www.npmjs.com/package/@cap-js/postgres) | maintained by community + CAP team | -> Follow the links above to find specific information for each. +> Follow the preceding links to find specific information for each. In general, all you need to do is to install one of the database packages, as follows: @@ -345,7 +345,7 @@ Select.from(AUTHOR) ### Standard Operators {.node} -The database services guarantee identical behavior of these operators: +The database services guarantee the identical behavior of these operators: * `==`, `=` — with `=` null being translated to `is null` * `!=`, `<>` — with `!=` translated to `IS NOT` in SQLite, or to `IS DISTINCT FROM` in standard SQL, or to an equivalent polyfill in SAP HANA @@ -358,67 +358,10 @@ The operator mappings are available for runtime queries only, but not in CDS fil ::: -### Functions Mappings for Runtime Queries {.node} - -A specified set of standard functions is supported in a **database-agnostic**, hence portable way, and translated to database-specific variants or polyfills. -Note that these functions are only supported within runtime queries, but not in CDS files. -This set of functions are by large the same as specified in OData: - -* `concat(x,y,...)` — concatenates the given strings or numbers -* `trim(x)` — removes leading and trailing whitespaces -* `contains(x,y)` — checks whether `y` is contained in `x`, may be fuzzy -* `startswith(x,y)` — checks whether `y` starts with `x` -* `endswith(x,y)` — checks whether `y` ends with `x` -* `matchespattern(x,y)` — checks whether `x` matches regex `y` -* `substring(x,i,n?)` 1 — - Extracts a substring from `x` starting at index `i` (0-based) with optional length `n`. - * **`i`**: Positive starts at `i`, negative starts `i` before the end. - * **`n`**: Positive extracts `n` items; omitted extracts to the end; negative is invalid. -* `indexof(x,y)` 1 — returns the index of the first occurrence of `y` in `x` -* `length(x)` — returns the length of string `x` -* `tolower(x)` — returns all-lowercased `x` -* `toupper(x)` — returns all-uppercased `x` -* `ceiling(x)` — rounds the input numeric parameter up to the nearest numeric value -* `floor(x)` — rounds the input numeric parameter down to the nearest numeric value -* `round(x)` — rounds the input numeric parameter to the nearest numeric value. - The mid-point between two integers is rounded away from zero, i.e. 0.5 is rounded to 1 and ‑0.5 is rounded to -1. -* `year(x)` `month(x)`, `day(x)`, `hour(x)`, `minute(x)`, `second(x)` — - returns parts of a datetime for a given `cds.DateTime` / `cds.Date` / `cds.Time` -* `time(x)`, `date(x)` - returns a string representing the `time` / `date` for a given `cds.DateTime` / `cds.Date` / `cds.Time` -* `fractionalseconds(x)` - returns a a `Decimal` representing the fractions of a second for a given `cds.Timestamp` -* `maxdatetime()` - returns the latest possible point in time: `'9999-12-31T23:59:59.999Z'` -* `mindatetime()` — returns the earliest possible point in time: `'0001-01-01T00:00:00.000Z'` -* `totalseconds(x)` — returns the duration of the value in total seconds, including fractional seconds. The [OData spec](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_totalseconds) defines the input as EDM.Duration: `P12DT23H59M59.999999999999S` -* `now()` — returns the current datetime -* `min(x)` `max(x)` `sum(x)` `average(x)` `count(x)`, `countdistinct(x)` — aggregate functions -* `search(xs,y)` — checks whether `y` is contained in any of `xs`, may be fuzzy → [see Searching Data](../guides/providing-services#searching-data) -* `session_context(v)` — with standard variable names → [see Session Variables](#session-variables) -> 1 These functions work zero-based. E.g., `substring('abcdef', 1, 3)` returns 'bcd' - -> You have to write these functions exactly as given; all-uppercase usages aren't supported. - -In addition to the standard functions, which all `@cap-js` database services support, `@cap-js/sqlite` and `@cap-js/postgres` also support these common SAP HANA functions, to further increase the scope for portable testing: - -* `years_between` — Computes the number of years between two specified dates. -* `months_between` — Computes the number of months between two specified dates. -* `days_between` — Computes the number of days between two specified dates. -* `seconds_between` — Computes the number of seconds between two specified dates. -* `nano100_between` — Computes the time difference between two dates to the precision of 0.1 microseconds. - -The database service implementation translates these to the best-possible native SQL functions, thus enhancing the extent of **portable** queries. -With open source and the new database service architecture, we also have methods in place to enhance this list by custom implementation. - -> For the SAP HANA functions, both usages are allowed: all-lowercase as given above, as well as all-uppercase. - -::: warning Runtime Only -The function mappings are available for runtime queries only, but not in CDS files. -::: - - ### Session Variables {.node} -The API shown below, which includes the function `session_context()` and specific pseudo variable names, is supported by **all** new database services, that is, *SQLite*, *PostgreSQL* and *SAP HANA*. -This allows you to write respective code once and run it on all these databases: +The API shown after this, which includes the function `session_context()` and specific pseudo variable names, is supported by **all** new database services, that is, *SQLite*, *PostgreSQL* and *SAP HANA*. +This allows you to write the respective code once and run it on all these databases: ```sql SELECT session_context('$user.id') @@ -454,7 +397,7 @@ db.queryForList("SELECT from sqlite_schema where name like ?", name); ### Reading `LargeBinary` / BLOB {.node} -Formerly, `LargeBinary` elements (or BLOBs) were always returned as any other data type. Now, they are skipped from `SELECT *` queries. Yet, you can still enforce reading BLOBs by explicitly selecting them. Then the BLOB properties are returned as readable streams. +Formerly, `LargeBinary` elements (or BLOBs) were always returned as any other data type. Now, they're skipped from `SELECT *` queries. Yet, you can still enforce reading BLOBs by explicitly selecting them. Then the BLOB properties are returned as readable streams. ```js SELECT.from(Books) //> [{ ID, title, ..., image1, image2 }] // [!code --] @@ -481,7 +424,7 @@ You can also do this manually with the CLI command `cds compile --to `. When you've created a CAP Java application with `cds init --java` or with CAP Java's [Maven archetype](../java/developing-applications/building#the-maven-archetype), the Maven build invokes the CDS compiler to generate a `schema.sql` file for your target database. In the `default` profile (development mode), an in-memory database is [initialized by Spring](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.data-initialization) and the schema is bootstrapped from the `schema.sql` file. -[Learn more about adding an inital database schema.](../java/cqn-services/persistence-services#initial-database-schema){.learn-more} +[Learn more about adding an initial database schema.](../java/cqn-services/persistence-services#initial-database-schema){.learn-more}
@@ -791,7 +734,7 @@ The following rules apply: - If you refer to a column name in the annotation, you need to take care of a potential name mapping yourself, for example, for structured elements. -- Annotation `@sql.prepend` is only supported for entities translating to tables. It can't be used with views nor with elements. +- Annotation `@sql.prepend` is only supported for entities translating to tables. It can't be used with views or with elements. - For SAP HANA tables, there's an implicit `@sql.prepend: 'COLUMN'` that is overwritten by an explicitly provided `@sql.prepend`. * Both `@sql.prepend` and `@sql.append` are disallowed in SaaS extension projects. @@ -823,7 +766,7 @@ ROW TABLE E ( [Learn more about Columnar and Row-Based Data Storage](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-administration-guide/columnar-and-row-based-data-storage){.learn-more} ### Reserved Words -The CDS compiler and CAP runtimes provide smart quoting for reserved words in SQLite and in SAP HANA so that they can still be used in most situations. But in general reserved words cannot be used as identifiers. The list of reserved words varies per database. +The CDS compiler and CAP runtimes provide smart quoting for reserved words in SQLite and in SAP HANA so that they can still be used in most situations. But in general reserved words can't be used as identifiers. The list of reserved words varies per database. Find here a collection of resources on selected databases and their reference documentation: @@ -940,7 +883,178 @@ Instead, they protect the integrity of your data in the database layer against p → Use [`@assert.target`](providing-services#assert-target) for corresponding input validations. ::: +## Standard Database Functions +{ #functions-mappings-for-runtime-queries } + +A specified set of standard functions - inspired by [OData](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_StringandCollectionFunctions) and [SAP HANA](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/alphabetical-list-of-functions?locale=en-US) - is supported in a **database-agnostic**, hence portable way, and translated to the best-possible native SQL functions or polyfills. + +Those functions are automatically mapped during runtime (Node.js) already today. +To switch on the same mappings for your CDL files, you have to set: + +cds.cdsc.standardDatabaseFunctions = true + +::: tip +this will be the default starting with `@sap/cds-compiler >= 9` and hence is only needed for earlier versions. +::: + + + +### OData standard functions + +The `@sap/cds-compiler` and all CAP Node.js database services come with out of the box support for common OData functions. + +::: warning Case Sensitivity +The OData function mappings are case-sensitive and must be written as in the list below. +::: + +Assuming you have the following entity definition: + +```cds +entity V as select from Books { + startswith(title, 'Raven') as lowerCase, // mapped to native SQL equivalent + startsWith(title, 'Raven') as camelCase, // passed as-is +} +``` + + +Then you compile the SAP HANA artifacts: + +`$ cds compile -2 sql --dialect hana` + + +This is the result: + +```sql +CREATE VIEW V AS SELECT + (CASE WHEN locate(title, 'Raven') = 1 THEN TRUE ELSE FALSE END) AS lowerCase, + -- the below will most likely fail on SAP HANA + startsWith(title, 'Raven') AS camelCase +FROM Books; +``` + +💡 If you want to use a DB native function or a UDF (User-Defined Function) instead of the OData function mappings, you can +do that by using a different casing than the OData function names as defined in the list below. +For example, `startsWith` instead of `startswith` will be passed as-is to the database. + +#### String Functions + +- `concat(x, y, ...)` + Concatenates the given strings or numbers. + +- `trim(x)` + Removes leading and trailing whitespaces. + +- `contains(x, y)` + Checks whether `y` is contained in `x` (case-sensitive). + +- `startswith(x, y)` + Checks whether `y` starts with `x` (case-sensitive). + +- `endswith(x, y)` + Checks whether `y` ends with `x` (case-sensitive). + +- `matchespattern(x, y)` + Checks whether `x` matches the regular expression `y`. + +- `indexof(x, y)` 1 + Returns the index of the first occurrence of `y` in `x` (case-sensitive). + +- `substring(x, i, n?)` 1 + Extracts a substring from `x` starting at index `i` (0-based) with an optional length `n`. + + | Parameter | Positive | Negative | Omitted + | --- | --- | --- | -- | + | `i` | starts at index `i` | starts `i` positions before the end | + | `n` | extracts `n` characters | invalid | extracts until the end of the string + +- `length(x)` + Returns the length of the string `x`. + +- `tolower(x)` + Converts all characters in `x` to lowercase. + +- `toupper(x)` + Converts all characters in `x` to uppercase. + +> 1 These functions work zero-based. For example, `substring('abcdef', 1, 3)` returns 'bcd' + +#### Numeric Functions + +- `ceiling(x)` + Rounds the numeric parameter up to the nearest integer. + +- `floor(x)` + Rounds the numeric parameter down to the nearest integer. + +- `round(x)` + Rounds the numeric parameter to the nearest integer. + The midpoint between two integers is rounded away from zero (e.g., `0.5` → `1` and `-0.5` → `-1`). + + ::: warning `round` function with more than one argument + Note that most databases support `round` functions with multiple arguments, + the second parameter being the precision. SAP HANA even has a third argument which is the rounding mode. + If you provide more than one argument, the `round` function may behave differently depending on the database. + ::: + +#### Date and Time Functions + +- `year(x)`, `month(x)`, `day(x)`, `hour(x)`, `minute(x)`, `second(x)` + Extracts and returns specific date / time parts as integer value from a given `cds.DateTime`, `cds.Date`, or `cds.Time`. + +- `time(x)`, `date(x)` + Extracts and returns a time or date from a given `cds.DateTime`, `cds.Date`, or `cds.Time`. + +- `fractionalseconds(x)` + Returns a `Decimal` representing the fractional seconds for a given `cds.Timestamp`. + +- `maxdatetime()` + Returns the latest possible point in time: `'9999-12-31T23:59:59.999Z'`. + +- `mindatetime()` + Returns the earliest possible point in time: `'0001-01-01T00:00:00.000Z'`. + +#### Aggregate Functions + +- `min(x)`, `max(x)`, `sum(x)`, `average(x)`, `count(x)`, `countdistinct(x)` + Standard aggregate functions used to calculate minimum, maximum, sum, average, count, and distinct count of values. + + +### SAP HANA Functions + +In addition to the OData standard functions, the `@sap/cds-compiler` and all CAP Node.js database services come with +out of the box support for some common SAP HANA functions, to further increase the scope for portable testing: + +::: warning Upper- and Lowercase are supported +For the SAP HANA functions, both usages are allowed: all-lowercase as given above, as well as all-uppercase. +::: + +- `years_between` + Computes the number of years between two specified dates. ([link](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/years-between-function-datetime?locale=en-US)) +- `months_between` + Computes the number of months between two specified dates. ([link](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/months-between-function-datetime?locale=en-US)) +- `days_between` + Computes the number of days between two specified dates. ([link](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/days-between-function-datetime?locale=en-US)) +- `seconds_between` + Computes the number of seconds between two specified dates. ([link](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/seconds-between-function-datetime?locale=en-US)) +- `nano100_between` + Computes the time difference between two dates to the precision of 0.1 microseconds. ([link](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/nano100-between-function-datetime?locale=en-US)) + +### Special Runtime Functions + +In addition to the OData and SAP HANA standard functions, the **CAP runtimes** provides special functions that are only available for runtime queries: + +- `search(x, y)` + Checks whether `y` is contained in any element of `x` (fuzzy matching may apply). + See [Searching Data](../guides/providing-services#searching-data) for more details. + +- `session_context()` + Utilizes standard variable names to maintain session context. + Refer to [Session Variables](#session-variables) for additional information. +- `now()` + Returns the current timestamp. ## Using Native Features { #native-db-functions} @@ -1023,7 +1137,7 @@ In case of conflicts, follow these steps to provide different models for differe ``` 4. For the Spring Boot side it's similar. If you have a local development database and a hybrid profile with a remote SAP HANA database, you only need to run in default (or any other) profile. For the SAP HANA part, the build and deploy part is done separately and the application just needs to be started using `cds bind`. -Once you have 2 non-HANA local databases you need to have 2 distinct database configurations in your Spring Boot configuration (in most cases application.yaml). +Once you have 2 non-HANA local databases, you need to have 2 distinct database configurations in your Spring Boot configuration (in most cases application.yaml). ```yaml spring: