@linkup-ai/abap-ai 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +384 -0
- package/dist/adt-client.js +364 -0
- package/dist/cli/activate.js +113 -0
- package/dist/cli/init.js +333 -0
- package/dist/cli/remove.js +80 -0
- package/dist/cli/status.js +229 -0
- package/dist/cli/systems.js +68 -0
- package/dist/cli.js +81 -0
- package/dist/index.js +1318 -0
- package/dist/knowledge/abap/abap-dictionary.md +199 -0
- package/dist/knowledge/abap/abap-sql.md +296 -0
- package/dist/knowledge/abap/amdp.md +273 -0
- package/dist/knowledge/abap/clean-code.md +293 -0
- package/dist/knowledge/abap/cloud-background-processing.md +250 -0
- package/dist/knowledge/abap/cloud-communication.md +265 -0
- package/dist/knowledge/abap/cloud-development.md +176 -0
- package/dist/knowledge/abap/cloud-extensibility.md +252 -0
- package/dist/knowledge/abap/cloud-released-apis.md +261 -0
- package/dist/knowledge/abap/constructor-expressions.md +289 -0
- package/dist/knowledge/abap/enhancements.md +232 -0
- package/dist/knowledge/abap/exceptions.md +271 -0
- package/dist/knowledge/abap/internal-tables.md +205 -0
- package/dist/knowledge/abap/object-orientation.md +298 -0
- package/dist/knowledge/abap/performance.md +216 -0
- package/dist/knowledge/abap/rap-abstract-entities.md +206 -0
- package/dist/knowledge/abap/rap-business-events.md +216 -0
- package/dist/knowledge/abap/rap-draft.md +191 -0
- package/dist/knowledge/abap/rap-eml.md +453 -0
- package/dist/knowledge/abap/rap-end-to-end.md +486 -0
- package/dist/knowledge/abap/rap-feature-control.md +185 -0
- package/dist/knowledge/abap/rap-numbering.md +280 -0
- package/dist/knowledge/abap/rap-service-exposure.md +163 -0
- package/dist/knowledge/abap/rap-unmanaged.md +468 -0
- package/dist/knowledge/abap/string-processing.md +180 -0
- package/dist/knowledge/abap/unit-testing.md +303 -0
- package/dist/knowledge/abap-cds/access-control.md +241 -0
- package/dist/knowledge/abap-cds/annotations.md +331 -0
- package/dist/knowledge/abap-cds/associations.md +254 -0
- package/dist/knowledge/abap-cds/expressions.md +230 -0
- package/dist/knowledge/abap-cds/functions.md +245 -0
- package/dist/knowledge/abap-cds/metadata-extensions.md +294 -0
- package/dist/knowledge/cap/authentication.md +278 -0
- package/dist/knowledge/cap/cdl-syntax.md +247 -0
- package/dist/knowledge/cap/cql-queries.md +266 -0
- package/dist/knowledge/cap/deployment.md +343 -0
- package/dist/knowledge/cap/event-handlers.md +287 -0
- package/dist/knowledge/cap/fiori-integration.md +303 -0
- package/dist/knowledge/cap/service-definitions.md +287 -0
- package/dist/knowledge/fiori/annotations.md +347 -0
- package/dist/knowledge/fiori/deployment.md +340 -0
- package/dist/knowledge/fiori/fiori-elements.md +332 -0
- package/dist/knowledge/fiori/fiori-side-effects.md +107 -0
- package/dist/knowledge/fiori/fiori-valuelist.md +144 -0
- package/dist/knowledge/fiori/ui5-controllers.md +358 -0
- package/dist/knowledge/fiori/ui5-data-binding.md +311 -0
- package/dist/knowledge/fiori/ui5-fragments-dialogs.md +330 -0
- package/dist/knowledge/fiori/ui5-manifest.md +411 -0
- package/dist/knowledge/fiori/ui5-routing.md +303 -0
- package/dist/knowledge/fiori/ui5-xml-views.md +294 -0
- package/dist/logger.js +114 -0
- package/dist/system-profile.js +207 -0
- package/dist/tools/abap-doc.js +72 -0
- package/dist/tools/abapgit.js +161 -0
- package/dist/tools/activate.js +68 -0
- package/dist/tools/atc-check.js +117 -0
- package/dist/tools/auth-object.js +56 -0
- package/dist/tools/breakpoints.js +76 -0
- package/dist/tools/call-hierarchy.js +84 -0
- package/dist/tools/cds-annotations.js +98 -0
- package/dist/tools/cds-dependencies.js +65 -0
- package/dist/tools/check.js +47 -0
- package/dist/tools/code-completion.js +70 -0
- package/dist/tools/code-coverage.js +111 -0
- package/dist/tools/create-amdp.js +111 -0
- package/dist/tools/create-dcl.js +81 -0
- package/dist/tools/create-transport.js +38 -0
- package/dist/tools/create.js +285 -0
- package/dist/tools/data-preview.js +37 -0
- package/dist/tools/delete.js +45 -0
- package/dist/tools/deploy-bsp.js +298 -0
- package/dist/tools/discovery.js +59 -0
- package/dist/tools/element-info.js +93 -0
- package/dist/tools/enhancements.js +186 -0
- package/dist/tools/extract-method.js +44 -0
- package/dist/tools/function-group.js +59 -0
- package/dist/tools/knowledge.js +275 -0
- package/dist/tools/lock-object.js +75 -0
- package/dist/tools/message-class.js +67 -0
- package/dist/tools/navigate.js +80 -0
- package/dist/tools/number-range.js +57 -0
- package/dist/tools/object-documentation.js +43 -0
- package/dist/tools/object-structure.js +78 -0
- package/dist/tools/object-versions.js +57 -0
- package/dist/tools/package-contents.js +60 -0
- package/dist/tools/pretty-printer.js +35 -0
- package/dist/tools/publish-binding.js +49 -0
- package/dist/tools/quick-fix.js +69 -0
- package/dist/tools/read.js +167 -0
- package/dist/tools/refactor-rename.js +60 -0
- package/dist/tools/release-transport.js +24 -0
- package/dist/tools/released-apis.js +51 -0
- package/dist/tools/repository-tree.js +90 -0
- package/dist/tools/scaffold-rap.js +642 -0
- package/dist/tools/search.js +73 -0
- package/dist/tools/shared/data-format.js +101 -0
- package/dist/tools/sql-console.js +17 -0
- package/dist/tools/system-info.js +270 -0
- package/dist/tools/traces.js +66 -0
- package/dist/tools/transport-contents.js +83 -0
- package/dist/tools/transports.js +67 -0
- package/dist/tools/unit-test.js +135 -0
- package/dist/tools/where-used.js +59 -0
- package/dist/tools/write.js +101 -0
- package/package.json +49 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# CDL Syntax — CDS Entity and Type Definitions
|
|
2
|
+
|
|
3
|
+
## Entity Definitions
|
|
4
|
+
|
|
5
|
+
```cds
|
|
6
|
+
namespace my.bookshop;
|
|
7
|
+
|
|
8
|
+
entity Books {
|
|
9
|
+
key ID : UUID;
|
|
10
|
+
title : String(255) not null;
|
|
11
|
+
descr : String(5000);
|
|
12
|
+
stock : Integer default 0;
|
|
13
|
+
price : Decimal(10,2);
|
|
14
|
+
available : Boolean default true;
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Types and Structured Elements
|
|
19
|
+
|
|
20
|
+
```cds
|
|
21
|
+
type Address {
|
|
22
|
+
street : String;
|
|
23
|
+
city : String;
|
|
24
|
+
zipCode : String(10);
|
|
25
|
+
country : String(3);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
entity Customers {
|
|
29
|
+
homeAddress : Address;
|
|
30
|
+
workAddress : Address;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Arrayed types
|
|
34
|
+
entity Contacts {
|
|
35
|
+
emails : many String;
|
|
36
|
+
phones : array of String(20);
|
|
37
|
+
tags : many { name: String };
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Enumerations
|
|
42
|
+
|
|
43
|
+
```cds
|
|
44
|
+
type Status : String enum {
|
|
45
|
+
draft;
|
|
46
|
+
submitted;
|
|
47
|
+
approved;
|
|
48
|
+
rejected = 'REJ';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type Priority : Integer enum {
|
|
52
|
+
low = 1;
|
|
53
|
+
medium = 2;
|
|
54
|
+
high = 3;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
entity Orders {
|
|
58
|
+
status : Status default #draft;
|
|
59
|
+
priority : Priority default #medium;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Calculated and Virtual Elements
|
|
64
|
+
|
|
65
|
+
```cds
|
|
66
|
+
entity Employees {
|
|
67
|
+
firstName : String;
|
|
68
|
+
lastName : String;
|
|
69
|
+
// On-read (computed during queries)
|
|
70
|
+
fullName : String = firstName || ' ' || lastName;
|
|
71
|
+
// Stored (persisted in DB)
|
|
72
|
+
stored : String = (firstName || ' ' || lastName) stored;
|
|
73
|
+
// Virtual (not persisted, set in handler)
|
|
74
|
+
virtual displayName : String @Core.Computed;
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Associations
|
|
79
|
+
|
|
80
|
+
```cds
|
|
81
|
+
// Managed to-one (CAP auto-generates FK: author_ID)
|
|
82
|
+
entity Books {
|
|
83
|
+
author : Association to Authors;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Unmanaged (explicit FK + ON condition required)
|
|
87
|
+
entity Books {
|
|
88
|
+
author : Association to Authors on author.ID = author_ID;
|
|
89
|
+
author_ID : UUID;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// To-many
|
|
93
|
+
entity Authors {
|
|
94
|
+
books : Association to many Books on books.author = $self;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Many-to-many via link entity
|
|
98
|
+
entity Books {
|
|
99
|
+
categories : Association to many Book2Category on categories.book = $self;
|
|
100
|
+
}
|
|
101
|
+
entity Book2Category {
|
|
102
|
+
key book : Association to Books;
|
|
103
|
+
key category : Association to Categories;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Compositions
|
|
108
|
+
|
|
109
|
+
```cds
|
|
110
|
+
// Standard composition
|
|
111
|
+
entity Orders {
|
|
112
|
+
key ID : UUID;
|
|
113
|
+
Items : Composition of many OrderItems on Items.order = $self;
|
|
114
|
+
}
|
|
115
|
+
entity OrderItems {
|
|
116
|
+
key ID : UUID;
|
|
117
|
+
order : Association to Orders;
|
|
118
|
+
product : Association to Products;
|
|
119
|
+
quantity : Integer;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Inline managed composition (auto-creates Orders.Items entity)
|
|
123
|
+
entity Orders {
|
|
124
|
+
key ID : UUID;
|
|
125
|
+
Items : Composition of many {
|
|
126
|
+
key pos : Integer;
|
|
127
|
+
product : Association to Products;
|
|
128
|
+
quantity : Integer;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Aspects and Extend
|
|
134
|
+
|
|
135
|
+
```cds
|
|
136
|
+
aspect tracked {
|
|
137
|
+
createdAt : Timestamp @cds.on.insert: $now;
|
|
138
|
+
createdBy : String(255) @cds.on.insert: $user;
|
|
139
|
+
modifiedAt : Timestamp @cds.on.insert: $now @cds.on.update: $now;
|
|
140
|
+
modifiedBy : String(255) @cds.on.insert: $user @cds.on.update: $user;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Apply via include syntax
|
|
144
|
+
entity Books : tracked {
|
|
145
|
+
key ID : UUID;
|
|
146
|
+
title : String;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Extend existing entity
|
|
150
|
+
extend Books with {
|
|
151
|
+
newField : String;
|
|
152
|
+
}
|
|
153
|
+
extend Books with tracked;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Annotations
|
|
157
|
+
|
|
158
|
+
```cds
|
|
159
|
+
// Grouped annotation syntax
|
|
160
|
+
@(
|
|
161
|
+
title: 'Books',
|
|
162
|
+
description: 'All books',
|
|
163
|
+
UI.HeaderInfo: { TypeName: 'Book' }
|
|
164
|
+
)
|
|
165
|
+
entity Books { ... }
|
|
166
|
+
|
|
167
|
+
// Element-level
|
|
168
|
+
entity Books {
|
|
169
|
+
@title: 'Book Title'
|
|
170
|
+
@mandatory
|
|
171
|
+
title : String;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Separate annotate directive
|
|
175
|
+
annotate Books with @title: 'Books';
|
|
176
|
+
annotate Books with {
|
|
177
|
+
title @title: 'Book Title';
|
|
178
|
+
price @Measures.Unit: currency_code;
|
|
179
|
+
};
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Services with Actions
|
|
183
|
+
|
|
184
|
+
```cds
|
|
185
|
+
service CatalogService @(path: '/catalog') {
|
|
186
|
+
entity Books as projection on my.Books;
|
|
187
|
+
|
|
188
|
+
// Bound actions (entity-level)
|
|
189
|
+
entity Orders { ... } actions {
|
|
190
|
+
action confirm();
|
|
191
|
+
action cancel(reason: String);
|
|
192
|
+
function getTotal() returns Decimal;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Unbound actions (service-level)
|
|
196
|
+
action sendNotification(to: String, message: String);
|
|
197
|
+
function calculateTax(amount: Decimal, region: String) returns Decimal;
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Views and Projections
|
|
202
|
+
|
|
203
|
+
```cds
|
|
204
|
+
entity PublicBooks as projection on Books {
|
|
205
|
+
ID, title, descr, author
|
|
206
|
+
} excluding { createdBy, modifiedBy };
|
|
207
|
+
|
|
208
|
+
entity BooksList as select from Books {
|
|
209
|
+
ID, title, author.name as authorName,
|
|
210
|
+
stock * price as value : Decimal
|
|
211
|
+
} where stock > 0 order by title;
|
|
212
|
+
|
|
213
|
+
// Parameterized view
|
|
214
|
+
entity FilteredBooks(minStock: Integer, maxPrice: Decimal)
|
|
215
|
+
as select from Books
|
|
216
|
+
where stock >= :minStock and price <= :maxPrice;
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Imports
|
|
220
|
+
|
|
221
|
+
```cds
|
|
222
|
+
using my.bookshop.Books from './db/schema';
|
|
223
|
+
using { Authors, Genres } from './db/schema';
|
|
224
|
+
using { Books as MyBooks } from './db/schema';
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Rules
|
|
228
|
+
|
|
229
|
+
- Keywords are case-insensitive; identifiers are case-sensitive
|
|
230
|
+
- Managed associations auto-generate FK columns (e.g., `author` creates `author_ID`)
|
|
231
|
+
- Compositions cascade deep CRUD; associations do not
|
|
232
|
+
- `@cds.on.insert: $now` and `$user` only work in aspects/annotations, not default values
|
|
233
|
+
- Use `extend` to add fields; use `annotate` to add annotations without changing structure
|
|
234
|
+
- Enums use `#value` syntax for defaults: `default #draft`
|
|
235
|
+
- `many` and `array of` are equivalent for arrayed types
|
|
236
|
+
|
|
237
|
+
## Anti-Patterns
|
|
238
|
+
|
|
239
|
+
| Anti-Pattern | Correct Pattern |
|
|
240
|
+
|---|---|
|
|
241
|
+
| Manual FK for managed association: `author_ID : UUID; author : Association to Authors;` | `author : Association to Authors;` (FK auto-generated) |
|
|
242
|
+
| `Composition of Authors` (independent entity) | `Composition of OrderItems` (dependent/child entity only) |
|
|
243
|
+
| `default $now` on plain field | `@cds.on.insert: $now` via aspect |
|
|
244
|
+
| Putting business logic in CDS | CDS is declarative; logic goes in handlers |
|
|
245
|
+
| `entity Items { order : Composition of Orders; }` | Composition is parent-to-child only |
|
|
246
|
+
| Circular compositions | Use associations for non-hierarchical relationships |
|
|
247
|
+
| `String` without length for large text | `String(5000)` or `LargeString` for long content |
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# CQL Queries — CAP Node.js Database Operations
|
|
2
|
+
|
|
3
|
+
## SELECT
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
const { Books, Authors } = cds.entities;
|
|
7
|
+
|
|
8
|
+
// All columns
|
|
9
|
+
const books = await SELECT.from(Books);
|
|
10
|
+
|
|
11
|
+
// Specific columns
|
|
12
|
+
const titles = await SELECT.from(Books).columns('ID', 'title', 'price');
|
|
13
|
+
|
|
14
|
+
// Single record by key
|
|
15
|
+
const book = await SELECT.from(Books, bookId);
|
|
16
|
+
|
|
17
|
+
// Single record with WHERE
|
|
18
|
+
const book = await SELECT.one.from(Books).where({ ID: bookId });
|
|
19
|
+
|
|
20
|
+
// ALWAYS guard single-record results
|
|
21
|
+
const book = await SELECT.one.from(Books).where({ ID: bookId });
|
|
22
|
+
if (!book) return req.reject(404, 'Book not found');
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## WHERE Clauses
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
// Equality
|
|
29
|
+
await SELECT.from(Books).where({ stock: 10 });
|
|
30
|
+
|
|
31
|
+
// Comparison operators
|
|
32
|
+
await SELECT.from(Books).where({ stock: { '>': 0 } });
|
|
33
|
+
await SELECT.from(Books).where({ stock: { '>=': 10 } });
|
|
34
|
+
await SELECT.from(Books).where({ price: { '<=': 50 } });
|
|
35
|
+
await SELECT.from(Books).where({ status: { '!=': 'deleted' } });
|
|
36
|
+
|
|
37
|
+
// IN, LIKE, BETWEEN
|
|
38
|
+
await SELECT.from(Books).where({ genre: { in: ['fiction', 'drama'] } });
|
|
39
|
+
await SELECT.from(Books).where({ title: { like: '%CAP%' } });
|
|
40
|
+
await SELECT.from(Books).where({ price: { between: [10, 50] } });
|
|
41
|
+
|
|
42
|
+
// NULL checks
|
|
43
|
+
await SELECT.from(Books).where({ author_ID: { '=': null } });
|
|
44
|
+
await SELECT.from(Books).where({ author_ID: { '!=': null } });
|
|
45
|
+
|
|
46
|
+
// AND (multiple keys in object)
|
|
47
|
+
await SELECT.from(Books).where({
|
|
48
|
+
stock: { '>': 0 },
|
|
49
|
+
price: { '<': 100 }
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// OR
|
|
53
|
+
await SELECT.from(Books).where({
|
|
54
|
+
or: [
|
|
55
|
+
{ stock: { '>': 100 } },
|
|
56
|
+
{ price: { '<': 10 } }
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Nested AND/OR
|
|
61
|
+
await SELECT.from(Books).where({
|
|
62
|
+
and: [
|
|
63
|
+
{ genre: 'fiction' },
|
|
64
|
+
{ or: [
|
|
65
|
+
{ stock: { '>': 50 } },
|
|
66
|
+
{ featured: true }
|
|
67
|
+
]}
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## ORDER BY, LIMIT, DISTINCT
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
await SELECT.from(Books).orderBy('title asc');
|
|
76
|
+
await SELECT.from(Books).orderBy({ genre: 'asc', price: 'desc' });
|
|
77
|
+
|
|
78
|
+
// Pagination: 10 rows, skip 20
|
|
79
|
+
await SELECT.from(Books).limit(10, 20);
|
|
80
|
+
|
|
81
|
+
await SELECT.distinct.from(Books).columns('genre');
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Aggregations
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
const count = await SELECT.one.from(Books).columns('count(*) as total');
|
|
88
|
+
|
|
89
|
+
const stats = await SELECT.one.from(Books).columns(
|
|
90
|
+
'sum(stock) as totalStock',
|
|
91
|
+
'avg(price) as avgPrice',
|
|
92
|
+
'min(price) as minPrice',
|
|
93
|
+
'max(price) as maxPrice'
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const byGenre = await SELECT.from(Books)
|
|
97
|
+
.columns('genre', 'count(*) as count', 'avg(price) as avgPrice')
|
|
98
|
+
.groupBy('genre')
|
|
99
|
+
.having({ 'count(*)': { '>': 5 } })
|
|
100
|
+
.orderBy('count desc');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Associations and Expand
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
// Path expressions
|
|
107
|
+
await SELECT.from(Books).columns('title', 'author.name');
|
|
108
|
+
await SELECT.from(Books).where({ 'author.name': 'John Doe' });
|
|
109
|
+
|
|
110
|
+
// Expand with ref syntax
|
|
111
|
+
await SELECT.from(Books)
|
|
112
|
+
.columns('ID', 'title', { ref: ['author'], expand: ['ID', 'name'] });
|
|
113
|
+
|
|
114
|
+
// Lambda syntax (fluent)
|
|
115
|
+
await SELECT.from(Books, b => {
|
|
116
|
+
b.ID,
|
|
117
|
+
b.title,
|
|
118
|
+
b.author(a => { a.ID, a.name })
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Nested expand
|
|
122
|
+
await SELECT.from(Orders, o => {
|
|
123
|
+
o.ID,
|
|
124
|
+
o.customer(c => { c.name }),
|
|
125
|
+
o.items(i => {
|
|
126
|
+
i.quantity,
|
|
127
|
+
i.product(p => { p.name, p.price })
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## INSERT
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
// Single entry
|
|
136
|
+
await INSERT.into(Books).entries({
|
|
137
|
+
ID: cds.utils.uuid(),
|
|
138
|
+
title: 'New Book',
|
|
139
|
+
stock: 100,
|
|
140
|
+
price: 29.99
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Multiple entries
|
|
144
|
+
await INSERT.into(Books).entries([
|
|
145
|
+
{ title: 'Book 1', stock: 10 },
|
|
146
|
+
{ title: 'Book 2', stock: 20 }
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
// Deep insert (with compositions)
|
|
150
|
+
await INSERT.into(Orders).entries({
|
|
151
|
+
ID: orderId,
|
|
152
|
+
customer_ID: customerId,
|
|
153
|
+
items: [
|
|
154
|
+
{ product_ID: product1Id, quantity: 2 },
|
|
155
|
+
{ product_ID: product2Id, quantity: 1 }
|
|
156
|
+
]
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Insert from SELECT
|
|
160
|
+
await INSERT.into(ArchivedBooks).as(
|
|
161
|
+
SELECT.from(Books).where({ stock: 0 })
|
|
162
|
+
);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## UPDATE
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
// By key
|
|
169
|
+
await UPDATE(Books, bookId).set({ stock: 50 });
|
|
170
|
+
|
|
171
|
+
// With WHERE
|
|
172
|
+
await UPDATE(Books).set({ featured: true }).where({ stock: { '>': 100 } });
|
|
173
|
+
|
|
174
|
+
// Increment/Decrement
|
|
175
|
+
await UPDATE(Books, bookId).set({ stock: { '+=': 10 } });
|
|
176
|
+
await UPDATE(Books, bookId).set({ stock: { '-=': 5 } });
|
|
177
|
+
await UPDATE(Books, bookId).set({ price: { '*=': 1.1 } });
|
|
178
|
+
|
|
179
|
+
// Conditional update (check affected rows)
|
|
180
|
+
const result = await UPDATE(Books, bookId)
|
|
181
|
+
.set({ stock: { '-=': quantity } })
|
|
182
|
+
.where({ stock: { '>=': quantity } });
|
|
183
|
+
if (result === 0) throw new Error('Insufficient stock');
|
|
184
|
+
|
|
185
|
+
// Deep update
|
|
186
|
+
await UPDATE(Orders, orderId).with({
|
|
187
|
+
status: 'confirmed',
|
|
188
|
+
items: [
|
|
189
|
+
{ ID: item1Id, quantity: 5 },
|
|
190
|
+
{ ID: item2Id, quantity: 3 }
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## DELETE
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
await DELETE.from(Books, bookId);
|
|
199
|
+
await DELETE.from(Books).where({ stock: 0 });
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## UPSERT
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
// Insert if not exists, update if exists — key field REQUIRED
|
|
206
|
+
await UPSERT.into(Books).entries({
|
|
207
|
+
ID: bookId,
|
|
208
|
+
title: 'Updated or New Title',
|
|
209
|
+
stock: 50
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Multiple
|
|
213
|
+
await UPSERT.into(Books).entries([
|
|
214
|
+
{ ID: id1, title: 'Book 1' },
|
|
215
|
+
{ ID: id2, title: 'Book 2' }
|
|
216
|
+
]);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Subqueries
|
|
220
|
+
|
|
221
|
+
```js
|
|
222
|
+
await SELECT.from(Books).where({
|
|
223
|
+
author_ID: {
|
|
224
|
+
in: SELECT.from(Authors).columns('ID')
|
|
225
|
+
.where({ bookCount: { '>': 5 } })
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Locking
|
|
231
|
+
|
|
232
|
+
```js
|
|
233
|
+
const book = await SELECT.from(Books, bookId).forUpdate();
|
|
234
|
+
const book = await SELECT.from(Books, bookId).forShareLock();
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Raw SQL
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
const results = await cds.db.run(
|
|
241
|
+
`SELECT * FROM my_bookshop_Books WHERE stock > ?`, [10]
|
|
242
|
+
);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Rules
|
|
246
|
+
|
|
247
|
+
- `SELECT.one` returns a single object or `undefined`; `SELECT` returns an array
|
|
248
|
+
- `UPDATE` returns number of affected rows (0 means nothing matched)
|
|
249
|
+
- UPSERT requires key fields; does NOT support `.where()`; does NOT merge — all provided fields are set
|
|
250
|
+
- Deep INSERT/UPDATE only works with compositions, not associations
|
|
251
|
+
- Compositions automatically cascade deletes
|
|
252
|
+
- `DELETE.from(Entity)` without WHERE deletes ALL records
|
|
253
|
+
- Use `cds.utils.uuid()` for generating UUIDs in INSERT
|
|
254
|
+
- `.set()` and `.with()` are equivalent on UPDATE
|
|
255
|
+
|
|
256
|
+
## Anti-Patterns
|
|
257
|
+
|
|
258
|
+
| Anti-Pattern | Correct Pattern |
|
|
259
|
+
|---|---|
|
|
260
|
+
| `SELECT.one.from(Books).where(...)` without null check | Always guard: `if (!book) req.reject(404, ...)` |
|
|
261
|
+
| `DELETE.from(Books)` without WHERE (deletes all) | Always add `.where(...)` unless intentional |
|
|
262
|
+
| UPSERT with `.where()` | UPSERT uses key fields only; use UPDATE for conditional |
|
|
263
|
+
| Deep insert via association | Deep insert only works with compositions |
|
|
264
|
+
| String concatenation for SQL | Use parameterized queries: `cds.db.run(sql, [params])` |
|
|
265
|
+
| `await SELECT.from(Books, id)` without handling undefined | Check result before accessing properties |
|
|
266
|
+
| Mixing CQL and raw SQL in same transaction | Stick to CQL; raw SQL only when CQL cannot express the query |
|