cds-caching 1.3.0 → 1.3.2
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 +16 -0
- package/app/dashboard/controller/App-dbg.controller.js +14 -14
- package/app/dashboard/controller/App-dbg.controller.js.map +1 -1
- package/app/dashboard/controller/App.controller.js +1 -1
- package/app/dashboard/controller/App.controller.js.map +1 -1
- package/app/dashboard/controller/App.controller.ts +14 -14
- package/app/dashboard/controller/BaseController-dbg.js +7 -0
- package/app/dashboard/controller/BaseController-dbg.js.map +1 -1
- package/app/dashboard/controller/BaseController.js +1 -1
- package/app/dashboard/controller/BaseController.js.map +1 -1
- package/app/dashboard/controller/BaseController.ts +10 -0
- package/app/dashboard/controller/Cache-dbg.controller.js +34 -25
- package/app/dashboard/controller/Cache-dbg.controller.js.map +1 -1
- package/app/dashboard/controller/Cache.controller.js +1 -1
- package/app/dashboard/controller/Cache.controller.js.map +1 -1
- package/app/dashboard/controller/Cache.controller.ts +36 -25
- package/app/dashboard/controller/SingleMetric-dbg.controller.js +56 -50
- package/app/dashboard/controller/SingleMetric-dbg.controller.js.map +1 -1
- package/app/dashboard/controller/SingleMetric.controller.js +1 -1
- package/app/dashboard/controller/SingleMetric.controller.js.map +1 -1
- package/app/dashboard/controller/SingleMetric.controller.ts +29 -22
- package/app/dashboard/i18n/i18n.properties +235 -0
- package/app/dashboard/i18n/i18n_de.properties +271 -3
- package/app/dashboard/i18n/i18n_en.properties +295 -2
- package/app/dashboard/resources/sap-ui-custom.js +11 -11
- package/app/dashboard/resources/sap-ui-custom.js.map +1 -1
- package/app/dashboard/view/Cache.view.xml +149 -149
- package/app/dashboard/view/Main.view.xml +7 -7
- package/app/dashboard/view/SingleMetric.view.xml +30 -30
- package/cds-plugin.js +31 -8
- package/lib/CachingService.js +11 -10
- package/lib/operations/BasicOperations.js +7 -4
- package/lib/support/KeyManager.js +40 -2
- package/lib/util.js +28 -2
- package/package.json +1 -1
|
@@ -13,30 +13,30 @@
|
|
|
13
13
|
formatter: 'cds/plugin/caching/dashboard/model/formatter'
|
|
14
14
|
}">
|
|
15
15
|
|
|
16
|
-
<f:DynamicPage title="
|
|
16
|
+
<f:DynamicPage title="{i18n>homePageTitle}" headerExpanded="true" headerPinned="true">
|
|
17
17
|
<f:title>
|
|
18
18
|
<f:DynamicPageTitle>
|
|
19
19
|
<f:heading>
|
|
20
|
-
<Title text="
|
|
20
|
+
<Title text="{i18n>homePageTitle}" />
|
|
21
21
|
</f:heading>
|
|
22
22
|
<f:actions>
|
|
23
|
-
<Button type="Transparent" icon="sap-icon://refresh" text="
|
|
23
|
+
<Button type="Transparent" icon="sap-icon://refresh" text="{i18n>refreshButton}" press="onRefresh" />
|
|
24
24
|
</f:actions>
|
|
25
25
|
</f:DynamicPageTitle>
|
|
26
26
|
</f:title>
|
|
27
27
|
|
|
28
28
|
<f:content>
|
|
29
29
|
|
|
30
|
-
<Table id="cachesList" headerText="
|
|
30
|
+
<Table id="cachesList" headerText="{i18n>cachingServicesHeader}" mode="SingleSelectMaster" items="{path: '/Caches'}" itemPress="onCacheSelect" noDataText="{i18n>noCachesAvailable}">
|
|
31
31
|
<columns>
|
|
32
32
|
<Column>
|
|
33
|
-
<Text text="
|
|
33
|
+
<Text text="{i18n>colName}" />
|
|
34
34
|
</Column>
|
|
35
35
|
<Column>
|
|
36
|
-
<Text text="
|
|
36
|
+
<Text text="{i18n>colStatisticTrackingEnabled}" />
|
|
37
37
|
</Column>
|
|
38
38
|
<Column>
|
|
39
|
-
<Text text="
|
|
39
|
+
<Text text="{i18n>colKeyTrackingEnabled}" />
|
|
40
40
|
</Column>
|
|
41
41
|
</columns>
|
|
42
42
|
<items>
|
|
@@ -14,19 +14,19 @@
|
|
|
14
14
|
formatter: 'cds/plugin/caching/dashboard/model/formatter'
|
|
15
15
|
}">
|
|
16
16
|
|
|
17
|
-
<f:DynamicPage title="
|
|
17
|
+
<f:DynamicPage title="{i18n>metricDetailsPageTitle}" showFooter="false">
|
|
18
18
|
<f:title>
|
|
19
19
|
<f:DynamicPageTitle>
|
|
20
20
|
<f:heading>
|
|
21
|
-
<Title text="
|
|
21
|
+
<Title text="{= ${i18n>metricPagePrefix} + ' ' + ${ui>/metricId} }" />
|
|
22
22
|
</f:heading>
|
|
23
23
|
<f:actions>
|
|
24
|
-
<Button icon="sap-icon://refresh" text="
|
|
24
|
+
<Button icon="sap-icon://refresh" text="{i18n>refreshButton}" type="Transparent" press="onRefresh" />
|
|
25
25
|
</f:actions>
|
|
26
26
|
<f:navigationActions>
|
|
27
|
-
<OverflowToolbarButton type="Transparent" icon="sap-icon://full-screen" press="handleFullScreen" tooltip="
|
|
28
|
-
<OverflowToolbarButton type="Transparent" icon="sap-icon://exit-full-screen" press="handleExitFullScreen" tooltip="
|
|
29
|
-
<OverflowToolbarButton type="Transparent" icon="sap-icon://decline" press="handleClose" tooltip="
|
|
27
|
+
<OverflowToolbarButton type="Transparent" icon="sap-icon://full-screen" press="handleFullScreen" tooltip="{i18n>ttFullScreenEnter}" visible="{= ${app>/layout} !== 'EndColumnFullScreen' }"/>
|
|
28
|
+
<OverflowToolbarButton type="Transparent" icon="sap-icon://exit-full-screen" press="handleExitFullScreen" tooltip="{i18n>ttFullScreenExit}" visible="{= ${app>/layout} === 'EndColumnFullScreen' }"/>
|
|
29
|
+
<OverflowToolbarButton type="Transparent" icon="sap-icon://decline" press="handleClose" tooltip="{i18n>ttCloseEndColumn}"/>
|
|
30
30
|
</f:navigationActions>
|
|
31
31
|
</f:DynamicPageTitle>
|
|
32
32
|
</f:title>
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
<cssgrid:ResponsiveColumnItemLayoutData columns="24"/>
|
|
44
44
|
</layoutData>
|
|
45
45
|
|
|
46
|
-
<Title text="
|
|
46
|
+
<Title text="{i18n>sectionReadThroughPerformance}" titleStyle="H5" class="sapUiMediumMarginTop sapUiTinyMarginBottom" />
|
|
47
47
|
|
|
48
48
|
<f:GridContainer id="metricCacheThroughGrid" columnsChange=".onColumnsChange">
|
|
49
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
49
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>hitRatio}" subheader="{i18n>tileSubCacheHitRate}">
|
|
50
50
|
<TileContent unit="%">
|
|
51
51
|
<NumericContent valueColor="{formatter: 'formatter.formatHitRatioWithColor', path: 'ui>/hitRatio'}" value="{ path: 'ui>/hitRatio', type: 'sap.ui.model.type.Float', formatOptions: {style: 'short', decimals: 2}}" />
|
|
52
52
|
</TileContent>
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
</layoutData>
|
|
56
56
|
</GenericTile>
|
|
57
57
|
|
|
58
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
58
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerCacheEfficiency}" subheader="{i18n>tileSubPerformanceFactor}">
|
|
59
59
|
<TileContent unit="x">
|
|
60
60
|
<NumericContent valueColor="{formatter: 'formatter.formatCacheEfficiencyWithColor', path: 'ui>/cacheEfficiency'}" value="{path: 'ui>/cacheEfficiency', type: 'sap.ui.model.type.Float', formatOptions: {style: 'short', decimals: 2}}" />
|
|
61
61
|
</TileContent>
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
</layoutData>
|
|
65
65
|
</GenericTile>
|
|
66
66
|
|
|
67
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
67
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>throughput}" subheader="{i18n>tileSubReqPerSec}">
|
|
68
68
|
<TileContent unit="req/s">
|
|
69
69
|
<NumericContent value="{ path: 'ui>/throughput', type: 'sap.ui.model.type.Float', formatOptions: {style: 'short', decimals: 2}}" withMargin="false" />
|
|
70
70
|
</TileContent>
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
</layoutData>
|
|
74
74
|
</GenericTile>
|
|
75
75
|
|
|
76
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
76
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerErrorRate}" subheader="{i18n>tileSubCacheErrors}">
|
|
77
77
|
<TileContent unit="%">
|
|
78
78
|
<NumericContent valueColor="{formatter: 'formatter.formatErrorRateWithColor', path: 'ui>/errorRate'}" value="{ path: 'ui>/errorRate', formatter: 'formatter.formatPercentageValue' }" />
|
|
79
79
|
</TileContent>
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
</layoutData>
|
|
83
83
|
</GenericTile>
|
|
84
84
|
|
|
85
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
85
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>totalRequests}" subheader="{i18n>tileSubCacheOperations}">
|
|
86
86
|
<TileContent unit="req">
|
|
87
87
|
<NumericContent value="{= ${ui>/hits} + ${ui>/misses}}" withMargin="false" />
|
|
88
88
|
</TileContent>
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
</layoutData>
|
|
92
92
|
</GenericTile>
|
|
93
93
|
|
|
94
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
94
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerCacheHits}" subheader="{i18n>tileSubSuccessfulRetrievals}">
|
|
95
95
|
<TileContent unit="hits">
|
|
96
96
|
<NumericContent value="{ui>/hits}" withMargin="false" />
|
|
97
97
|
</TileContent>
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
</layoutData>
|
|
101
101
|
</GenericTile>
|
|
102
102
|
|
|
103
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
103
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerCacheMisses}" subheader="{i18n>tileSubFailedRetrievals}">
|
|
104
104
|
<TileContent unit="misses">
|
|
105
105
|
<NumericContent value="{ui>/misses}" withMargin="false" />
|
|
106
106
|
</TileContent>
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
</GenericTile>
|
|
111
111
|
|
|
112
112
|
|
|
113
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
113
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerCacheErrors}" subheader="{i18n>tileSubErrorCount}">
|
|
114
114
|
<TileContent unit="errors">
|
|
115
115
|
<NumericContent value="{ui>/errors}" withMargin="false" />
|
|
116
116
|
</TileContent>
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
</GenericTile>
|
|
121
121
|
|
|
122
122
|
|
|
123
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
123
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerHitLatency}" subheader="{i18n>tileSubCacheHitResponse}">
|
|
124
124
|
<TileContent unit="ms">
|
|
125
125
|
<NumericContent valueColor="{formatter: 'formatter.formatLatencyWithColor', path: 'ui>/avgHitLatency'}" value="{ path: 'ui>/avgHitLatency', type: 'sap.ui.model.type.Float', formatOptions: {style: 'short', decimals: 2}}" />
|
|
126
126
|
</TileContent>
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
</layoutData>
|
|
130
130
|
</GenericTile>
|
|
131
131
|
|
|
132
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
132
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerMissLatency}" subheader="{i18n>tileSubCacheMissResponse}">
|
|
133
133
|
<TileContent unit="ms">
|
|
134
134
|
<NumericContent valueColor="{formatter: 'formatter.formatLatencyWithColor', path: 'ui>/avgMissLatency'}" value="{ path: 'ui>/avgMissLatency', type: 'sap.ui.model.type.Float', formatOptions: {style: 'short', decimals: 2}}" />
|
|
135
135
|
</TileContent>
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
<!-- Read-Through Distribution Chart -->
|
|
142
142
|
<f:Card class="sapUiTinyMargin">
|
|
143
143
|
<f:header>
|
|
144
|
-
<card:Header title="
|
|
144
|
+
<card:Header title="{i18n>cardReadThroughOps}" subtitle="{i18n>cardReadThroughOpsSub}" />
|
|
145
145
|
</f:header>
|
|
146
146
|
<f:content>
|
|
147
147
|
<l:VerticalLayout width="100%">
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
<!-- Latency Chart -->
|
|
176
176
|
<f:Card class="sapUiTinyMargin">
|
|
177
177
|
<f:header>
|
|
178
|
-
<card:Header title="
|
|
178
|
+
<card:Header title="{i18n>cardLatencyCompare}" subtitle="{i18n>cardLatencyCompareSub}" />
|
|
179
179
|
</f:header>
|
|
180
180
|
<f:content>
|
|
181
181
|
<l:VerticalLayout width="100%">
|
|
@@ -214,10 +214,10 @@
|
|
|
214
214
|
<cssgrid:ResponsiveColumnItemLayoutData columns="24"/>
|
|
215
215
|
</layoutData>
|
|
216
216
|
|
|
217
|
-
<Title text="
|
|
217
|
+
<Title text="{i18n>sectionNativeFunctionPerformance}" titleStyle="H5" class="sapUiMediumMarginTop sapUiTinyMarginBottom" />
|
|
218
218
|
|
|
219
219
|
<f:GridContainer id="metricNativeGrid" columnsChange=".onColumnsChange">
|
|
220
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
220
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerNativeThroughput}" subheader="{i18n>tileSubNativeOpsPerSec}">
|
|
221
221
|
<TileContent unit="ops/s">
|
|
222
222
|
<NumericContent value="{ path: 'ui>/nativeThroughput', formatter: 'formatter.formatNumberValue' }" withMargin="false" />
|
|
223
223
|
</TileContent>
|
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
</layoutData>
|
|
227
227
|
</GenericTile>
|
|
228
228
|
|
|
229
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
229
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerNativeErrorRate}" subheader="{i18n>tileSubCacheErrors}">
|
|
230
230
|
<TileContent unit="%">
|
|
231
231
|
<NumericContent valueColor="{formatter: 'formatter.formatErrorRateWithColor', path: 'ui>/nativeErrorRate'}" value="{ path: 'ui>/nativeErrorRate', formatter: 'formatter.formatPercentageValue' }" />
|
|
232
232
|
</TileContent>
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
</layoutData>
|
|
236
236
|
</GenericTile>
|
|
237
237
|
|
|
238
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
238
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerNativeSets}" subheader="{i18n>tileSubWriteOps}">
|
|
239
239
|
<TileContent unit="sets">
|
|
240
240
|
<NumericContent value="{ui>/nativeSets}" withMargin="false" />
|
|
241
241
|
</TileContent>
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
</layoutData>
|
|
245
245
|
</GenericTile>
|
|
246
246
|
|
|
247
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
247
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerNativeGets}" subheader="{i18n>tileSubReadOps}">
|
|
248
248
|
<TileContent unit="gets">
|
|
249
249
|
<NumericContent value="{ui>/nativeGets}" withMargin="false" />
|
|
250
250
|
</TileContent>
|
|
@@ -253,7 +253,7 @@
|
|
|
253
253
|
</layoutData>
|
|
254
254
|
</GenericTile>
|
|
255
255
|
|
|
256
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
256
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerNativeDeletes}" subheader="{i18n>tileSubDeleteOps}">
|
|
257
257
|
<TileContent unit="deletes">
|
|
258
258
|
<NumericContent value="{ui>/nativeDeletes}" withMargin="false" />
|
|
259
259
|
</TileContent>
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
</layoutData>
|
|
263
263
|
</GenericTile>
|
|
264
264
|
|
|
265
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
265
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerNativeClears}" subheader="{i18n>tileSubClearOps}">
|
|
266
266
|
<TileContent unit="clears">
|
|
267
267
|
<NumericContent value="{ui>/nativeClears}" withMargin="false" />
|
|
268
268
|
</TileContent>
|
|
@@ -271,7 +271,7 @@
|
|
|
271
271
|
</layoutData>
|
|
272
272
|
</GenericTile>
|
|
273
273
|
|
|
274
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
274
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerDeleteByTags}" subheader="{i18n>tileSubTagDeletes}">
|
|
275
275
|
<TileContent unit="deletes">
|
|
276
276
|
<NumericContent value="{ui>/nativeDeleteByTags}" withMargin="false" />
|
|
277
277
|
</TileContent>
|
|
@@ -280,7 +280,7 @@
|
|
|
280
280
|
</layoutData>
|
|
281
281
|
</GenericTile>
|
|
282
282
|
|
|
283
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
283
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerNativeErrors}" subheader="{i18n>tileSubErrorCount}">
|
|
284
284
|
<TileContent unit="errors">
|
|
285
285
|
<NumericContent value="{ui>/nativeErrors}" withMargin="false" />
|
|
286
286
|
</TileContent>
|
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
</layoutData>
|
|
290
290
|
</GenericTile>
|
|
291
291
|
|
|
292
|
-
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="
|
|
292
|
+
<GenericTile class="sapUiTinyMarginBegin sapUiTinyMarginTop tileLayout" header="{i18n>headerTotalNativeOps}" subheader="{i18n>tileSubAllOperations}">
|
|
293
293
|
<TileContent unit="ops">
|
|
294
294
|
<NumericContent value="{ui>/totalNativeOperations}" withMargin="false" />
|
|
295
295
|
</TileContent>
|
|
@@ -300,7 +300,7 @@
|
|
|
300
300
|
<!-- Native Function Distribution Chart -->
|
|
301
301
|
<f:Card class="sapUiTinyMargin">
|
|
302
302
|
<f:header>
|
|
303
|
-
<card:Header title="
|
|
303
|
+
<card:Header title="{i18n>cardNativeFunctionOps}" subtitle="{i18n>cardReadThroughOpsSub}" />
|
|
304
304
|
</f:header>
|
|
305
305
|
<f:content>
|
|
306
306
|
<l:VerticalLayout width="100%">
|
package/cds-plugin.js
CHANGED
|
@@ -14,8 +14,6 @@ if (cachingConfigs.some(c => c.statistics)) {
|
|
|
14
14
|
cds.env.roots.push(path.join(__dirname, 'db', 'statistics'));
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
cds.add?.register?.('caching-dashboard', require('./lib/add'))
|
|
18
|
-
|
|
19
17
|
cds.on('served', scanCachingAnnotations)
|
|
20
18
|
const LOG = cds.log("cds-caching");
|
|
21
19
|
|
|
@@ -27,14 +25,20 @@ cds.build?.register?.('cds-caching', class CachingBuildPlugin extends cds.build.
|
|
|
27
25
|
clean() { }
|
|
28
26
|
|
|
29
27
|
static hasTask() {
|
|
28
|
+
cds.log('cds-caching').info('hasTask', cds.env.requires);
|
|
30
29
|
const requires = cds.env.requires || {};
|
|
30
|
+
const dbKind = requires.db?.kind || '';
|
|
31
|
+
const isHanaDB = dbKind === 'hana' || dbKind === 'sql';
|
|
31
32
|
return Object.values(requires).some(
|
|
32
|
-
r => r.impl === 'cds-caching' && r.store === 'hana'
|
|
33
|
+
r => r.impl === 'cds-caching' && (r.store === 'hana' || (r.store === 'cds' && isHanaDB))
|
|
33
34
|
);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
async build() {
|
|
37
38
|
const requires = cds.env.requires || {};
|
|
39
|
+
const compileOpts = { ...this.options(), sql_mapping: cds.env.sql.names };
|
|
40
|
+
let wroteCacheStore = false;
|
|
41
|
+
|
|
38
42
|
for (const [, config] of Object.entries(requires)) {
|
|
39
43
|
if (config.impl === 'cds-caching' && config.store === 'hana') {
|
|
40
44
|
const table = config.credentials?.table || 'KEYV';
|
|
@@ -48,12 +52,31 @@ cds.build?.register?.('cds-caching', class CachingBuildPlugin extends cds.build.
|
|
|
48
52
|
await this.write(content).to(`src/gen/${table}.hdbtable`);
|
|
49
53
|
LOG.info('Building cds-caching hana table', { table, keySize });
|
|
50
54
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
|
|
56
|
+
if (config.store === 'cds' && !wroteCacheStore) {
|
|
57
|
+
await this._buildCacheStoreHdbtables(compileOpts);
|
|
58
|
+
wroteCacheStore = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The HANA build task compiles only the app `db/` folder, so `plugin.cds_caching.CacheStore`
|
|
65
|
+
* from env.roots is not part of that CSN. Emit matching .hdbtable files here.
|
|
66
|
+
* Non-HANA DBs still get the table via normal CDS deploy / DDL.
|
|
67
|
+
*/
|
|
68
|
+
async _buildCacheStoreHdbtables(compileOpts) {
|
|
69
|
+
const modelPath = path.join(__dirname, 'db', 'cache-store');
|
|
70
|
+
const model = await cds.load(modelPath, { ...compileOpts, cwd: cds.root });
|
|
71
|
+
const artifacts = cds.compile.to.hdbtable(model, compileOpts);
|
|
72
|
+
for (const [content, key] of artifacts) {
|
|
73
|
+
const file = key.file || `${key.name}${key.suffix || ''}`;
|
|
74
|
+
await this.write(content).to(path.join('src/gen', file));
|
|
55
75
|
}
|
|
76
|
+
LOG.info('Built cds-caching CacheStore HANA artifacts from cache-store model');
|
|
56
77
|
}
|
|
57
78
|
});
|
|
58
79
|
|
|
59
|
-
|
|
80
|
+
// CAP resolves `impl: 'cds-caching'` to this file; the main export must be the service class
|
|
81
|
+
// so `cds.connect.to('<cache>')` returns CachingService (e.g. addCachableFunction).
|
|
82
|
+
module.exports = CachingService
|
package/lib/CachingService.js
CHANGED
|
@@ -87,15 +87,16 @@ class CachingService extends cds.Service {
|
|
|
87
87
|
this.log.debug(`CLEAR`);
|
|
88
88
|
await this.cache.clear();
|
|
89
89
|
|
|
90
|
-
// Also clear statistics
|
|
91
90
|
if (this.statistics) {
|
|
92
91
|
await this.statistics.resetCurrentStats();
|
|
93
92
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
if (event.data.clearStatistics) {
|
|
94
|
+
try {
|
|
95
|
+
await this.statistics.deleteMetrics();
|
|
96
|
+
await this.statistics.deleteKeyMetrics();
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.log.warn(`Failed to clear statistics for cache ${this.name}:`, error);
|
|
99
|
+
}
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
}
|
|
@@ -173,10 +174,10 @@ class CachingService extends cds.Service {
|
|
|
173
174
|
return this.basicOperations.delete(key);
|
|
174
175
|
}
|
|
175
176
|
|
|
176
|
-
async clear(tx = null) {
|
|
177
|
-
if (tx) return this.basicOperations.clear(tx);
|
|
178
|
-
if (this.options.transactionalOperations) return this.basicOperations.clearInTx();
|
|
179
|
-
return this.basicOperations.clear();
|
|
177
|
+
async clear(options = {}, tx = null) {
|
|
178
|
+
if (tx) return this.basicOperations.clear(options, tx);
|
|
179
|
+
if (this.options.transactionalOperations) return this.basicOperations.clearInTx(options);
|
|
180
|
+
return this.basicOperations.clear(options);
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
async deleteByTag(tag, tx = null) {
|
|
@@ -156,12 +156,15 @@ class BasicOperations {
|
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
158
|
* Clear all cache entries
|
|
159
|
+
* @param {object} options - clear options
|
|
160
|
+
* @param {boolean} [options.clearStatistics=false] - also delete persisted metrics and key metrics
|
|
159
161
|
* @returns {Promise<void>}
|
|
160
162
|
*/
|
|
161
|
-
async clear(tx = null) {
|
|
163
|
+
async clear(options = {}, tx = null) {
|
|
162
164
|
const srv = tx || this.cache;
|
|
163
165
|
await srv.send('CLEAR', {
|
|
164
|
-
deleteAll: true
|
|
166
|
+
deleteAll: true,
|
|
167
|
+
clearStatistics: options.clearStatistics || false
|
|
165
168
|
});
|
|
166
169
|
|
|
167
170
|
// Record the native clear operation
|
|
@@ -318,10 +321,10 @@ class BasicOperations {
|
|
|
318
321
|
}
|
|
319
322
|
}
|
|
320
323
|
|
|
321
|
-
async clearInTx() {
|
|
324
|
+
async clearInTx(options = {}) {
|
|
322
325
|
const tx = await this.cache.tx();
|
|
323
326
|
try {
|
|
324
|
-
await this.clear(tx);
|
|
327
|
+
await this.clear(options, tx);
|
|
325
328
|
await tx.commit();
|
|
326
329
|
return;
|
|
327
330
|
} catch (error) {
|
|
@@ -78,13 +78,34 @@ class KeyManager {
|
|
|
78
78
|
case "Request":
|
|
79
79
|
case "NoaRequest":
|
|
80
80
|
case "ODataRequest":
|
|
81
|
-
//
|
|
81
|
+
// Hash request information including the HTTP URL which
|
|
82
|
+
// contains $filter/$select/$orderby query parameters.
|
|
83
|
+
//
|
|
84
|
+
// Why req.query (CQN) is unreliable for hashing:
|
|
85
|
+
// NoaRequest ("New OData Adapter Request", defined in
|
|
86
|
+
// @sap/cds/libx/odata/ODataAdapter.js) carries cds.ql
|
|
87
|
+
// query objects where SELECT.where lives on the prototype
|
|
88
|
+
// chain, not as an own property. JSON.stringify only
|
|
89
|
+
// serializes own enumerable properties, so the where clause
|
|
90
|
+
// from $filter is silently dropped — two requests that
|
|
91
|
+
// differ only by $filter produce the same hash.
|
|
92
|
+
// This does NOT affect programmatic CQL (e.g.
|
|
93
|
+
// SELECT.from(Entity).where(...)) where `where` is an own
|
|
94
|
+
// property. It only affects CQN built from OData URL
|
|
95
|
+
// parsing in the adapter pipeline.
|
|
96
|
+
//
|
|
97
|
+
// The HTTP URL (req._.req.url / req.http.req.url) always
|
|
98
|
+
// contains the full query string as a plain string and is
|
|
99
|
+
// the reliable source for all OData query options. When a
|
|
100
|
+
// URL is available it supersedes req.query.
|
|
101
|
+
const url = this.normalizeUrl(keyOrObject._?.req?.url || keyOrObject.http?.req?.url);
|
|
82
102
|
return this.createHash({
|
|
83
103
|
method: keyOrObject.method,
|
|
84
104
|
path: keyOrObject.path || keyOrObject.http?.req?.path,
|
|
105
|
+
url,
|
|
85
106
|
data: keyOrObject.data,
|
|
86
107
|
params: keyOrObject.params,
|
|
87
|
-
query: keyOrObject.query,
|
|
108
|
+
query: url ? undefined : keyOrObject.query,
|
|
88
109
|
event: keyOrObject.event,
|
|
89
110
|
target: keyOrObject.target?.name
|
|
90
111
|
});
|
|
@@ -105,6 +126,23 @@ class KeyManager {
|
|
|
105
126
|
return this.createHash({ content: keyOrObject });
|
|
106
127
|
}
|
|
107
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Normalize a URL by sorting its query parameters alphabetically by key.
|
|
131
|
+
* Ensures the same parameters in different order produce the same cache key.
|
|
132
|
+
* @param {string} url - URL string to normalize
|
|
133
|
+
* @returns {string|undefined} - Normalized URL or undefined if input is falsy
|
|
134
|
+
*/
|
|
135
|
+
normalizeUrl(url) {
|
|
136
|
+
if (!url) return undefined;
|
|
137
|
+
try {
|
|
138
|
+
const urlObj = new URL(url, 'http://localhost');
|
|
139
|
+
urlObj.searchParams.sort();
|
|
140
|
+
return urlObj.pathname + (urlObj.searchParams.size ? '?' + urlObj.searchParams.toString() : '');
|
|
141
|
+
} catch {
|
|
142
|
+
return url;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
108
146
|
/**
|
|
109
147
|
* Serialize function arguments for cache key generation
|
|
110
148
|
* @param {any} arg - argument to serialize
|
package/lib/util.js
CHANGED
|
@@ -85,17 +85,42 @@ const bindFunction = async (service, action, isBound = false) => {
|
|
|
85
85
|
})
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
const ENTITY_TAG_PREFIX = 'entity:';
|
|
89
|
+
|
|
90
|
+
const getEntityTag = (entity) => `${ENTITY_TAG_PREFIX}${entity.name}`;
|
|
91
|
+
|
|
88
92
|
const bindEntity = async (service, entity) => {
|
|
93
|
+
const invalidateOnWrite = entity['@cache.invalidateOnWrite'];
|
|
94
|
+
const entityTag = invalidateOnWrite ? getEntityTag(entity) : null;
|
|
95
|
+
|
|
89
96
|
service.prepend(function () {
|
|
90
97
|
service.on('READ', entity.name, async (req, next) => {
|
|
91
98
|
const cache = await cds.connect.to(entity['@cache.service'] || "caching");
|
|
99
|
+
|
|
100
|
+
const tags = [...(entity['@cache.tags'] || [])];
|
|
101
|
+
if (entityTag) tags.push({ value: entityTag });
|
|
102
|
+
|
|
92
103
|
const { result, cacheKey } = await cache.rt.run(req, next, {
|
|
93
104
|
ttl: entity['@cache.ttl'],
|
|
94
|
-
tags
|
|
105
|
+
tags,
|
|
95
106
|
key: extractCacheProperties(entity, 'key')
|
|
96
107
|
});
|
|
97
108
|
return result;
|
|
98
109
|
})
|
|
110
|
+
|
|
111
|
+
if (invalidateOnWrite) {
|
|
112
|
+
const cudHandler = async (data, req) => {
|
|
113
|
+
try {
|
|
114
|
+
const cache = await cds.connect.to(entity['@cache.service'] || "caching");
|
|
115
|
+
await cache.deleteByTag(entityTag);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
cds.log('cds-caching').warn(`Cache invalidation failed for ${entity.name}:`, error);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
service.after('CREATE', entity.name, cudHandler);
|
|
121
|
+
service.after('UPDATE', entity.name, cudHandler);
|
|
122
|
+
service.after('DELETE', entity.name, cudHandler);
|
|
123
|
+
}
|
|
99
124
|
})
|
|
100
125
|
}
|
|
101
126
|
|
|
@@ -183,7 +208,8 @@ const scanCachingAnnotations = async (srvs) => {
|
|
|
183
208
|
cacheConfig.entities[entity.name] = {
|
|
184
209
|
ttl: entity['@cache.ttl'],
|
|
185
210
|
tags: entity['@cache.tags'],
|
|
186
|
-
key: extractCacheProperties(entity, 'key')
|
|
211
|
+
key: extractCacheProperties(entity, 'key'),
|
|
212
|
+
invalidateOnWrite: entity['@cache.invalidateOnWrite'] || false
|
|
187
213
|
};
|
|
188
214
|
}
|
|
189
215
|
|