@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.
Files changed (114) hide show
  1. package/README.md +384 -0
  2. package/dist/adt-client.js +364 -0
  3. package/dist/cli/activate.js +113 -0
  4. package/dist/cli/init.js +333 -0
  5. package/dist/cli/remove.js +80 -0
  6. package/dist/cli/status.js +229 -0
  7. package/dist/cli/systems.js +68 -0
  8. package/dist/cli.js +81 -0
  9. package/dist/index.js +1318 -0
  10. package/dist/knowledge/abap/abap-dictionary.md +199 -0
  11. package/dist/knowledge/abap/abap-sql.md +296 -0
  12. package/dist/knowledge/abap/amdp.md +273 -0
  13. package/dist/knowledge/abap/clean-code.md +293 -0
  14. package/dist/knowledge/abap/cloud-background-processing.md +250 -0
  15. package/dist/knowledge/abap/cloud-communication.md +265 -0
  16. package/dist/knowledge/abap/cloud-development.md +176 -0
  17. package/dist/knowledge/abap/cloud-extensibility.md +252 -0
  18. package/dist/knowledge/abap/cloud-released-apis.md +261 -0
  19. package/dist/knowledge/abap/constructor-expressions.md +289 -0
  20. package/dist/knowledge/abap/enhancements.md +232 -0
  21. package/dist/knowledge/abap/exceptions.md +271 -0
  22. package/dist/knowledge/abap/internal-tables.md +205 -0
  23. package/dist/knowledge/abap/object-orientation.md +298 -0
  24. package/dist/knowledge/abap/performance.md +216 -0
  25. package/dist/knowledge/abap/rap-abstract-entities.md +206 -0
  26. package/dist/knowledge/abap/rap-business-events.md +216 -0
  27. package/dist/knowledge/abap/rap-draft.md +191 -0
  28. package/dist/knowledge/abap/rap-eml.md +453 -0
  29. package/dist/knowledge/abap/rap-end-to-end.md +486 -0
  30. package/dist/knowledge/abap/rap-feature-control.md +185 -0
  31. package/dist/knowledge/abap/rap-numbering.md +280 -0
  32. package/dist/knowledge/abap/rap-service-exposure.md +163 -0
  33. package/dist/knowledge/abap/rap-unmanaged.md +468 -0
  34. package/dist/knowledge/abap/string-processing.md +180 -0
  35. package/dist/knowledge/abap/unit-testing.md +303 -0
  36. package/dist/knowledge/abap-cds/access-control.md +241 -0
  37. package/dist/knowledge/abap-cds/annotations.md +331 -0
  38. package/dist/knowledge/abap-cds/associations.md +254 -0
  39. package/dist/knowledge/abap-cds/expressions.md +230 -0
  40. package/dist/knowledge/abap-cds/functions.md +245 -0
  41. package/dist/knowledge/abap-cds/metadata-extensions.md +294 -0
  42. package/dist/knowledge/cap/authentication.md +278 -0
  43. package/dist/knowledge/cap/cdl-syntax.md +247 -0
  44. package/dist/knowledge/cap/cql-queries.md +266 -0
  45. package/dist/knowledge/cap/deployment.md +343 -0
  46. package/dist/knowledge/cap/event-handlers.md +287 -0
  47. package/dist/knowledge/cap/fiori-integration.md +303 -0
  48. package/dist/knowledge/cap/service-definitions.md +287 -0
  49. package/dist/knowledge/fiori/annotations.md +347 -0
  50. package/dist/knowledge/fiori/deployment.md +340 -0
  51. package/dist/knowledge/fiori/fiori-elements.md +332 -0
  52. package/dist/knowledge/fiori/fiori-side-effects.md +107 -0
  53. package/dist/knowledge/fiori/fiori-valuelist.md +144 -0
  54. package/dist/knowledge/fiori/ui5-controllers.md +358 -0
  55. package/dist/knowledge/fiori/ui5-data-binding.md +311 -0
  56. package/dist/knowledge/fiori/ui5-fragments-dialogs.md +330 -0
  57. package/dist/knowledge/fiori/ui5-manifest.md +411 -0
  58. package/dist/knowledge/fiori/ui5-routing.md +303 -0
  59. package/dist/knowledge/fiori/ui5-xml-views.md +294 -0
  60. package/dist/logger.js +114 -0
  61. package/dist/system-profile.js +207 -0
  62. package/dist/tools/abap-doc.js +72 -0
  63. package/dist/tools/abapgit.js +161 -0
  64. package/dist/tools/activate.js +68 -0
  65. package/dist/tools/atc-check.js +117 -0
  66. package/dist/tools/auth-object.js +56 -0
  67. package/dist/tools/breakpoints.js +76 -0
  68. package/dist/tools/call-hierarchy.js +84 -0
  69. package/dist/tools/cds-annotations.js +98 -0
  70. package/dist/tools/cds-dependencies.js +65 -0
  71. package/dist/tools/check.js +47 -0
  72. package/dist/tools/code-completion.js +70 -0
  73. package/dist/tools/code-coverage.js +111 -0
  74. package/dist/tools/create-amdp.js +111 -0
  75. package/dist/tools/create-dcl.js +81 -0
  76. package/dist/tools/create-transport.js +38 -0
  77. package/dist/tools/create.js +285 -0
  78. package/dist/tools/data-preview.js +37 -0
  79. package/dist/tools/delete.js +45 -0
  80. package/dist/tools/deploy-bsp.js +298 -0
  81. package/dist/tools/discovery.js +59 -0
  82. package/dist/tools/element-info.js +93 -0
  83. package/dist/tools/enhancements.js +186 -0
  84. package/dist/tools/extract-method.js +44 -0
  85. package/dist/tools/function-group.js +59 -0
  86. package/dist/tools/knowledge.js +275 -0
  87. package/dist/tools/lock-object.js +75 -0
  88. package/dist/tools/message-class.js +67 -0
  89. package/dist/tools/navigate.js +80 -0
  90. package/dist/tools/number-range.js +57 -0
  91. package/dist/tools/object-documentation.js +43 -0
  92. package/dist/tools/object-structure.js +78 -0
  93. package/dist/tools/object-versions.js +57 -0
  94. package/dist/tools/package-contents.js +60 -0
  95. package/dist/tools/pretty-printer.js +35 -0
  96. package/dist/tools/publish-binding.js +49 -0
  97. package/dist/tools/quick-fix.js +69 -0
  98. package/dist/tools/read.js +167 -0
  99. package/dist/tools/refactor-rename.js +60 -0
  100. package/dist/tools/release-transport.js +24 -0
  101. package/dist/tools/released-apis.js +51 -0
  102. package/dist/tools/repository-tree.js +90 -0
  103. package/dist/tools/scaffold-rap.js +642 -0
  104. package/dist/tools/search.js +73 -0
  105. package/dist/tools/shared/data-format.js +101 -0
  106. package/dist/tools/sql-console.js +17 -0
  107. package/dist/tools/system-info.js +270 -0
  108. package/dist/tools/traces.js +66 -0
  109. package/dist/tools/transport-contents.js +83 -0
  110. package/dist/tools/transports.js +67 -0
  111. package/dist/tools/unit-test.js +135 -0
  112. package/dist/tools/where-used.js +59 -0
  113. package/dist/tools/write.js +101 -0
  114. 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 |