@saptools/cf-hana 0.3.1 → 0.3.4
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/CHANGELOG.md +17 -0
- package/README.md +41 -31
- package/dist/cli.js +92 -54
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +83 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
<!-- cspell:words VARCHAR -->
|
|
4
|
+
|
|
5
|
+
## 0.3.4 - 2026-07-02
|
|
6
|
+
|
|
7
|
+
- Keep explicit selector region lookup map-backed so unknown technical keys fail before isolated authentication unless they are added to the maintained SAP CF region catalog.
|
|
8
|
+
|
|
9
|
+
## 0.3.3 - 2026-07-02
|
|
10
|
+
|
|
11
|
+
- Fix Cloud Foundry region resolution for indexed SAP regions such as `eu10-005`, representative `eu20`/`us10` indexed endpoints, and China `platform.sapcloud.cn` endpoints.
|
|
12
|
+
- Keep bare CLI selectors on the core current-session path so healthy `cf env <app>` calls do not require isolated SAP re-authentication.
|
|
13
|
+
- Preserve and validate the current CF API endpoint for auth fallback, reject unsafe endpoint shapes, and keep binding discovery live-only without restoring `cf-sync` cache reads.
|
|
14
|
+
- Update `--refresh`, README, and cf-hana skill text to describe the retained compatibility flag truthfully.
|
|
15
|
+
|
|
16
|
+
## 0.3.2 - 2026-07-01
|
|
17
|
+
|
|
18
|
+
- Remove the `cf-hana: saved result expires at...` stderr notice from `query --save` output while keeping result refs available for inspection.
|
|
19
|
+
|
|
3
20
|
## 0.3.1 - 2026-07-01
|
|
4
21
|
|
|
5
22
|
- Add actionable HANA LOB `ORDER BY`/`GROUP BY` hints that recommend removing LOB columns or wrapping them with `TO_VARCHAR(<column>)`.
|
package/README.md
CHANGED
|
@@ -2,19 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
> Run SQL directly against SAP HANA Cloud databases bound to a Cloud Foundry app — addressed by a `region/org/space/app` selector.
|
|
4
4
|
|
|
5
|
-
`@saptools/cf-hana`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
It is fast because credentials come from `cf-sync`'s on-disk cache (no 5–15s CF
|
|
11
|
-
login on the hot path) and connections are pooled and reused within a process.
|
|
5
|
+
`@saptools/cf-hana` opens live SAP HANA Cloud connections from Cloud Foundry app
|
|
6
|
+
bindings. Pass a selector, get a connected, pooled client, and run `SELECT` /
|
|
7
|
+
`INSERT` / `UPDATE` / `DELETE` / DDL. Bare app names use your active `cf target`
|
|
8
|
+
and current CF session first, so a healthy local CF login does not require SAP
|
|
9
|
+
password re-authentication.
|
|
12
10
|
|
|
13
11
|
## Features
|
|
14
12
|
|
|
15
13
|
- **Selector-based connect** — `region/org/space/app` or a bare app name.
|
|
16
|
-
- **Credentials, handled for you** —
|
|
17
|
-
|
|
14
|
+
- **Credentials, handled for you** — HANA bindings are read live from Cloud Foundry.
|
|
15
|
+
Bare app names use the current CF session first; explicit selectors use isolated live authentication.
|
|
18
16
|
- **Parameterized queries** — values always travel as bound `?` parameters, never
|
|
19
17
|
string-concatenated.
|
|
20
18
|
- **Connection pooling** — pooled, reused connections; opt out with `pool: false`.
|
|
@@ -74,8 +72,9 @@ Every entry point takes a selector as its first argument:
|
|
|
74
72
|
|
|
75
73
|
- **Explicit** — `region/org/space/app` (e.g.
|
|
76
74
|
`eu10/example-org/space-demo/app-demo`). Works without any cached topology.
|
|
77
|
-
- **Bare app name** — `app-demo`. Resolved against the
|
|
78
|
-
|
|
75
|
+
- **Bare app name** — `app-demo`. Resolved only against the active `cf target`
|
|
76
|
+
(current org/space/API endpoint) and fetched with `cf env app-demo` using your
|
|
77
|
+
existing CF session before any isolated re-auth fallback.
|
|
79
78
|
|
|
80
79
|
## CLI
|
|
81
80
|
|
|
@@ -89,7 +88,7 @@ cf-hana info <selector> Print the resolved connection metada
|
|
|
89
88
|
cf-hana result <command> Inspect saved query refs
|
|
90
89
|
```
|
|
91
90
|
|
|
92
|
-
Common options: `--refresh
|
|
91
|
+
Common options: `--refresh` (deprecated compatibility flag; binding discovery is already live), `--role <runtime|hdi>`, `--binding <name>` /
|
|
93
92
|
`--binding-index <n>`, `--timeout <ms>`, `--read-only`, `--allow-destructive`,
|
|
94
93
|
`--limit <n>`, `--no-auto-limit`. The `query` command also accepts
|
|
95
94
|
`--param <value>` (repeatable), `--cell-limit <n>`, `--save`,
|
|
@@ -170,10 +169,11 @@ atomically, and expire after exactly 30 minutes. The cache key is derived from
|
|
|
170
169
|
non-secret connection identity: selector, app name, host, active schema, role,
|
|
171
170
|
and driver. It does not include passwords, certificates, tokens, SQL parameter
|
|
172
171
|
values, result rows, or table data, and malformed cache files are treated as
|
|
173
|
-
misses. Pass `--refresh-metadata` to bypass this cache for a query
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
misses. Pass `--refresh-metadata` to bypass this cache for a query. The legacy
|
|
173
|
+
`--refresh` flag is accepted for compatibility but does not bypass the metadata
|
|
174
|
+
cache and is not a credential-cache control because binding discovery is already
|
|
175
|
+
live. If metadata lookup or cache I/O fails, cf-hana preserves the original
|
|
176
|
+
query error and simply omits suggestions.
|
|
177
177
|
|
|
178
178
|
## Programmatic API
|
|
179
179
|
|
|
@@ -187,21 +187,31 @@ and simply omits suggestions.
|
|
|
187
187
|
|
|
188
188
|
`ConnectOptions` highlights: `role` (`runtime` | `hdi`), `bindingName` /
|
|
189
189
|
`bindingIndex`, `readOnly`, `allowDestructive`, `autoLimit`, `queryTimeoutMs`,
|
|
190
|
-
`connectTimeoutMs`, `refresh`, `pool`.
|
|
190
|
+
`connectTimeoutMs`, deprecated `refresh`, `pool`.
|
|
191
191
|
|
|
192
192
|
## Credentials
|
|
193
193
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
194
|
+
Credential discovery is **live-only** and does not read `@saptools/cf-sync`
|
|
195
|
+
snapshots or `~/.saptools/cf-db-bindings.json`.
|
|
196
|
+
|
|
197
|
+
- Bare app selectors (`app-demo`) read the active `cf target`, preserve its exact
|
|
198
|
+
validated API endpoint, and first run `cf env app-demo` with your current CF
|
|
199
|
+
session. This path does not require `SAP_EMAIL` or `SAP_PASSWORD` while the
|
|
200
|
+
current session is healthy.
|
|
201
|
+
- If that direct bare-app call fails with an auth/session error, cf-hana falls
|
|
202
|
+
back to an isolated temporary `CF_HOME`, runs `cf api <current-endpoint>`,
|
|
203
|
+
authenticates with `SAP_EMAIL` / `SAP_PASSWORD` (or the programmatic `email` /
|
|
204
|
+
`password` options), targets the current org/space, and reads `cf env`.
|
|
205
|
+
- Explicit `region/org/space/app` selectors always use isolated live
|
|
206
|
+
authentication. Region keys support current SAP CF technical keys, including
|
|
207
|
+
indexed regions such as `eu10-005`, `eu20-001`, `us10-002`, and China
|
|
208
|
+
endpoints such as `cn40` on `platform.sapcloud.cn`.
|
|
209
|
+
|
|
210
|
+
The legacy `refresh` / `--refresh` option is retained only for compatibility and
|
|
211
|
+
has no credential-cache meaning; binding discovery is already live. Use
|
|
212
|
+
`--refresh-metadata` when you specifically want to bypass the table/view
|
|
213
|
+
suggestion metadata cache. Credential resolution writes no binding credentials
|
|
214
|
+
under `~/.saptools/`.
|
|
205
215
|
|
|
206
216
|
## SQL history
|
|
207
217
|
|
|
@@ -264,9 +274,9 @@ parameters.
|
|
|
264
274
|
## Requirements
|
|
265
275
|
|
|
266
276
|
- Node.js >= 20.
|
|
267
|
-
- A HANA binding reachable from your network. Resolving a bare app name
|
|
268
|
-
|
|
269
|
-
`
|
|
277
|
+
- A HANA binding reachable from your network. Resolving a bare app name needs the Cloud Foundry CLI and an active `cf target`.
|
|
278
|
+
Isolated fallback or explicit selectors additionally need `SAP_EMAIL` /
|
|
279
|
+
`SAP_PASSWORD`.
|
|
270
280
|
|
|
271
281
|
## Development
|
|
272
282
|
|
package/dist/cli.js
CHANGED
|
@@ -698,7 +698,7 @@ async function listColumns(connection, schema, table) {
|
|
|
698
698
|
|
|
699
699
|
// src/config.ts
|
|
700
700
|
var CLI_NAME = "cf-hana";
|
|
701
|
-
var CLI_VERSION = "0.3.
|
|
701
|
+
var CLI_VERSION = "0.3.4";
|
|
702
702
|
var ENV_PREFIX = "CF_HANA";
|
|
703
703
|
var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
|
|
704
704
|
var DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
|
|
@@ -750,40 +750,94 @@ var REGION_API_MAP = {
|
|
|
750
750
|
ap20: "https://api.cf.ap20.hana.ondemand.com",
|
|
751
751
|
ap21: "https://api.cf.ap21.hana.ondemand.com",
|
|
752
752
|
ap30: "https://api.cf.ap30.hana.ondemand.com",
|
|
753
|
+
ap31: "https://api.cf.ap31.hana.ondemand.com",
|
|
753
754
|
br10: "https://api.cf.br10.hana.ondemand.com",
|
|
754
755
|
br20: "https://api.cf.br20.hana.ondemand.com",
|
|
755
756
|
br30: "https://api.cf.br30.hana.ondemand.com",
|
|
756
757
|
ca10: "https://api.cf.ca10.hana.ondemand.com",
|
|
757
758
|
ca20: "https://api.cf.ca20.hana.ondemand.com",
|
|
758
759
|
ch20: "https://api.cf.ch20.hana.ondemand.com",
|
|
760
|
+
cn20: "https://api.cf.cn20.platform.sapcloud.cn",
|
|
761
|
+
cn40: "https://api.cf.cn40.platform.sapcloud.cn",
|
|
762
|
+
eu01: "https://api.cf.eu01.hana.ondemand.com",
|
|
763
|
+
eu02: "https://api.cf.eu02.hana.ondemand.com",
|
|
759
764
|
eu10: "https://api.cf.eu10.hana.ondemand.com",
|
|
765
|
+
"eu10-002": "https://api.cf.eu10-002.hana.ondemand.com",
|
|
766
|
+
"eu10-003": "https://api.cf.eu10-003.hana.ondemand.com",
|
|
767
|
+
"eu10-004": "https://api.cf.eu10-004.hana.ondemand.com",
|
|
768
|
+
"eu10-005": "https://api.cf.eu10-005.hana.ondemand.com",
|
|
769
|
+
"eu10-006": "https://api.cf.eu10-006.hana.ondemand.com",
|
|
760
770
|
eu11: "https://api.cf.eu11.hana.ondemand.com",
|
|
761
771
|
eu12: "https://api.cf.eu12.hana.ondemand.com",
|
|
772
|
+
eu13: "https://api.cf.eu13.hana.ondemand.com",
|
|
762
773
|
eu20: "https://api.cf.eu20.hana.ondemand.com",
|
|
774
|
+
"eu20-001": "https://api.cf.eu20-001.hana.ondemand.com",
|
|
775
|
+
"eu20-002": "https://api.cf.eu20-002.hana.ondemand.com",
|
|
763
776
|
eu21: "https://api.cf.eu21.hana.ondemand.com",
|
|
777
|
+
eu22: "https://api.cf.eu22.hana.ondemand.com",
|
|
764
778
|
eu30: "https://api.cf.eu30.hana.ondemand.com",
|
|
765
|
-
|
|
779
|
+
eu31: "https://api.cf.eu31.hana.ondemand.com",
|
|
780
|
+
il30: "https://api.cf.il30.hana.ondemand.com",
|
|
781
|
+
in30: "https://api.cf.in30.hana.ondemand.com",
|
|
782
|
+
jp01: "https://api.cf.jp01.hana.ondemand.com",
|
|
766
783
|
jp10: "https://api.cf.jp10.hana.ondemand.com",
|
|
767
784
|
jp20: "https://api.cf.jp20.hana.ondemand.com",
|
|
768
785
|
jp30: "https://api.cf.jp30.hana.ondemand.com",
|
|
786
|
+
jp31: "https://api.cf.jp31.hana.ondemand.com",
|
|
787
|
+
sa30: "https://api.cf.sa30.hana.ondemand.com",
|
|
788
|
+
sa31: "https://api.cf.sa31.hana.ondemand.com",
|
|
789
|
+
uk20: "https://api.cf.uk20.hana.ondemand.com",
|
|
790
|
+
us01: "https://api.cf.us01.hana.ondemand.com",
|
|
791
|
+
us02: "https://api.cf.us02.hana.ondemand.com",
|
|
769
792
|
us10: "https://api.cf.us10.hana.ondemand.com",
|
|
793
|
+
"us10-001": "https://api.cf.us10-001.hana.ondemand.com",
|
|
794
|
+
"us10-002": "https://api.cf.us10-002.hana.ondemand.com",
|
|
795
|
+
"us10-003": "https://api.cf.us10-003.hana.ondemand.com",
|
|
770
796
|
us11: "https://api.cf.us11.hana.ondemand.com",
|
|
771
797
|
us20: "https://api.cf.us20.hana.ondemand.com",
|
|
772
798
|
us21: "https://api.cf.us21.hana.ondemand.com",
|
|
799
|
+
"us21-001": "https://api.cf.us21-001.hana.ondemand.com",
|
|
800
|
+
us22: "https://api.cf.us22.hana.ondemand.com",
|
|
773
801
|
us30: "https://api.cf.us30.hana.ondemand.com",
|
|
774
|
-
|
|
802
|
+
us32: "https://api.cf.us32.hana.ondemand.com"
|
|
775
803
|
};
|
|
804
|
+
function normalizeSapCfApiEndpoint(apiEndpoint) {
|
|
805
|
+
const trimmed = apiEndpoint.trim();
|
|
806
|
+
let parsed;
|
|
807
|
+
try {
|
|
808
|
+
parsed = new URL(trimmed);
|
|
809
|
+
} catch {
|
|
810
|
+
throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
|
|
811
|
+
}
|
|
812
|
+
if (/^https:\/\/[^/]*:\d+(?:[/?#]|$)/i.test(trimmed)) {
|
|
813
|
+
throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
|
|
814
|
+
}
|
|
815
|
+
if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || parsed.port !== "" || parsed.pathname.replace(/\/+$/, "") !== "" || parsed.search !== "" || parsed.hash !== "") {
|
|
816
|
+
throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
|
|
817
|
+
}
|
|
818
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
819
|
+
const match = /^api\.cf\.([a-z]{2}\d{2}(?:-\d{3})?)\.(hana\.ondemand\.com|platform\.sapcloud\.cn)$/.exec(hostname);
|
|
820
|
+
if (!match) {
|
|
821
|
+
throw new Error(`Invalid or untrusted CF API endpoint "${apiEndpoint}".`);
|
|
822
|
+
}
|
|
823
|
+
return `https://${hostname}`;
|
|
824
|
+
}
|
|
776
825
|
function getApiEndpointForRegion(regionKey) {
|
|
777
|
-
return REGION_API_MAP[regionKey];
|
|
826
|
+
return REGION_API_MAP[regionKey.trim().toLowerCase()];
|
|
778
827
|
}
|
|
779
828
|
function getRegionKeyForApi(apiEndpoint) {
|
|
780
|
-
|
|
829
|
+
let normalized;
|
|
830
|
+
try {
|
|
831
|
+
normalized = normalizeSapCfApiEndpoint(apiEndpoint);
|
|
832
|
+
} catch {
|
|
833
|
+
return void 0;
|
|
834
|
+
}
|
|
781
835
|
for (const [key, endpoint] of Object.entries(REGION_API_MAP)) {
|
|
782
|
-
if (endpoint.toLowerCase() ===
|
|
836
|
+
if (endpoint.toLowerCase() === normalized) {
|
|
783
837
|
return key;
|
|
784
838
|
}
|
|
785
839
|
}
|
|
786
|
-
return
|
|
840
|
+
return /^https:\/\/api\.cf\.([a-z]{2}\d{2}(?:-\d{3})?)\.hana\.ondemand\.com$/.exec(normalized)?.[1];
|
|
787
841
|
}
|
|
788
842
|
async function withCfSession(work) {
|
|
789
843
|
const cfHome = await mkdtemp(join2(tmpdir(), "saptools-cf-hana-"));
|
|
@@ -883,9 +937,15 @@ function parseCfTargetOutput(stdout) {
|
|
|
883
937
|
if (!api || !org || !space) {
|
|
884
938
|
return void 0;
|
|
885
939
|
}
|
|
886
|
-
|
|
940
|
+
let normalizedApi;
|
|
941
|
+
try {
|
|
942
|
+
normalizedApi = normalizeSapCfApiEndpoint(api);
|
|
943
|
+
} catch {
|
|
944
|
+
return void 0;
|
|
945
|
+
}
|
|
946
|
+
const regionKey = getRegionKeyForApi(normalizedApi);
|
|
887
947
|
return {
|
|
888
|
-
apiEndpoint:
|
|
948
|
+
apiEndpoint: normalizedApi,
|
|
889
949
|
orgName: org,
|
|
890
950
|
spaceName: space,
|
|
891
951
|
...regionKey ? { regionKey } : {}
|
|
@@ -911,12 +971,8 @@ function formatCurrentCfAppSelector(target, appName) {
|
|
|
911
971
|
if (!name) {
|
|
912
972
|
throw new Error("App name is required.");
|
|
913
973
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
`Current CF API endpoint "${target.apiEndpoint}" does not match a known SAP region. Pass a full region/org/space/app selector.`
|
|
917
|
-
);
|
|
918
|
-
}
|
|
919
|
-
return `${target.regionKey}/${target.orgName}/${target.spaceName}/${name}`;
|
|
974
|
+
const region = target.regionKey ?? "current";
|
|
975
|
+
return `${region}/${target.orgName}/${target.spaceName}/${name}`;
|
|
920
976
|
}
|
|
921
977
|
function extractVcapSection(stdout) {
|
|
922
978
|
const start = stdout.indexOf("VCAP_SERVICES:");
|
|
@@ -996,6 +1052,9 @@ function classifyCfError(stderr = "", stdout = "") {
|
|
|
996
1052
|
}
|
|
997
1053
|
|
|
998
1054
|
// src/credentials.ts
|
|
1055
|
+
function errorMessageFromUnknown(error) {
|
|
1056
|
+
return error instanceof Error ? error.message : "Invalid or untrusted CF API endpoint.";
|
|
1057
|
+
}
|
|
999
1058
|
async function resolveAppBindings(rawSelector, options) {
|
|
1000
1059
|
const selector = rawSelector.trim();
|
|
1001
1060
|
if (!selector) {
|
|
@@ -1014,7 +1073,7 @@ async function resolveAppBindings(rawSelector, options) {
|
|
|
1014
1073
|
const displaySelector = current.regionKey ? formatCurrentCfAppSelector(current, selector) : `current/${current.orgName}/${current.spaceName}/${selector}`;
|
|
1015
1074
|
target = {
|
|
1016
1075
|
selector: displaySelector,
|
|
1017
|
-
apiEndpoint: current.
|
|
1076
|
+
apiEndpoint: current.apiEndpoint,
|
|
1018
1077
|
orgName: current.orgName,
|
|
1019
1078
|
spaceName: current.spaceName,
|
|
1020
1079
|
appName: selector
|
|
@@ -1032,10 +1091,16 @@ async function resolveAppBindings(rawSelector, options) {
|
|
|
1032
1091
|
if (!apiEndpoint) {
|
|
1033
1092
|
throw new CfHanaError(
|
|
1034
1093
|
"CONFIG",
|
|
1035
|
-
`Unknown region
|
|
1094
|
+
`Unknown SAP CF region "${regionKey}". Verify the current SAP region list or use the current CF target.`
|
|
1036
1095
|
);
|
|
1037
1096
|
}
|
|
1038
|
-
|
|
1097
|
+
let normalizedApiEndpoint;
|
|
1098
|
+
try {
|
|
1099
|
+
normalizedApiEndpoint = normalizeSapCfApiEndpoint(apiEndpoint);
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
throw new CfHanaError("CONFIG", errorMessageFromUnknown(error), { cause: error });
|
|
1102
|
+
}
|
|
1103
|
+
target = { selector, apiEndpoint: normalizedApiEndpoint, orgName, spaceName, appName };
|
|
1039
1104
|
}
|
|
1040
1105
|
let bindings;
|
|
1041
1106
|
const source = "live";
|
|
@@ -1059,9 +1124,6 @@ Run "cf login" + "cf target", or provide SAP_EMAIL + SAP_PASSWORD.`,
|
|
|
1059
1124
|
{ cause: directError }
|
|
1060
1125
|
);
|
|
1061
1126
|
}
|
|
1062
|
-
if (!target.apiEndpoint) {
|
|
1063
|
-
throw new CfHanaError("CONFIG", "Cannot determine API endpoint for fallback auth.");
|
|
1064
|
-
}
|
|
1065
1127
|
const api = target.apiEndpoint;
|
|
1066
1128
|
bindings = await withCfSession(async (ctx) => {
|
|
1067
1129
|
await cfApi(api, ctx);
|
|
@@ -1085,9 +1147,6 @@ Run "cf login" + "cf target", or provide SAP_EMAIL + SAP_PASSWORD.`,
|
|
|
1085
1147
|
`SAP_EMAIL and SAP_PASSWORD are required to fetch HANA bindings for explicit selector "${selector}".`
|
|
1086
1148
|
);
|
|
1087
1149
|
}
|
|
1088
|
-
if (!target.apiEndpoint) {
|
|
1089
|
-
throw new CfHanaError("CONFIG", "Invalid explicit selector.");
|
|
1090
|
-
}
|
|
1091
1150
|
const api = target.apiEndpoint;
|
|
1092
1151
|
bindings = await withCfSession(async (ctx) => {
|
|
1093
1152
|
await cfApi(api, ctx);
|
|
@@ -3529,25 +3588,6 @@ function parseQualifiedName(value) {
|
|
|
3529
3588
|
}
|
|
3530
3589
|
return { schema: value.slice(0, dot), table: value.slice(dot + 1) };
|
|
3531
3590
|
}
|
|
3532
|
-
async function resolveSelectorArgument(selector) {
|
|
3533
|
-
if (selector.includes("/")) {
|
|
3534
|
-
return selector;
|
|
3535
|
-
}
|
|
3536
|
-
const current = await readCurrentCfTarget().catch((error) => {
|
|
3537
|
-
throw new CfHanaError(
|
|
3538
|
-
"CONFIG",
|
|
3539
|
-
"No current CF target found. Run `cf target -o <org> -s <space>` or pass a full region/org/space/app selector.",
|
|
3540
|
-
{ cause: error }
|
|
3541
|
-
);
|
|
3542
|
-
});
|
|
3543
|
-
if (current === void 0) {
|
|
3544
|
-
throw new CfHanaError(
|
|
3545
|
-
"CONFIG",
|
|
3546
|
-
"No current CF target found. Run `cf target -o <org> -s <space>` or pass a full region/org/space/app selector."
|
|
3547
|
-
);
|
|
3548
|
-
}
|
|
3549
|
-
return formatCurrentCfAppSelector(current, selector);
|
|
3550
|
-
}
|
|
3551
3591
|
function toConnectOptions(opts) {
|
|
3552
3592
|
assertPositiveOption("--limit", opts.limit);
|
|
3553
3593
|
assertPositiveOption("--timeout", opts.timeout);
|
|
@@ -3602,7 +3642,7 @@ function formatInfo(info) {
|
|
|
3602
3642
|
].join("\n");
|
|
3603
3643
|
}
|
|
3604
3644
|
function withConnectionOptions(command) {
|
|
3605
|
-
return command.option("--refresh", "
|
|
3645
|
+
return command.option("--refresh", "deprecated compatibility flag; binding discovery is already live", false).option("--role <role>", "HANA user role: runtime or hdi", "runtime").option("--binding <name>", "select a HANA binding by service name").option("--binding-index <n>", "select a HANA binding by index", parseIntOption2).option("--read-only", "block every DML and DDL statement", false).option("--allow-destructive", "permit destructive statements", false).option("--timeout <ms>", "connection and query timeout in milliseconds", parseIntOption2).option("--limit <n>", "row cap auto-applied to bare SELECT statements", parseIntOption2).option("--no-auto-limit", "disable the automatic SELECT row cap").option("--refresh-metadata", "bypass the 30-minute table/view metadata suggestion cache", false);
|
|
3606
3646
|
}
|
|
3607
3647
|
function withFormattedConnectionOptions(command) {
|
|
3608
3648
|
return withConnectionOptions(command).option(
|
|
@@ -3690,14 +3730,14 @@ async function runQuery(selector, sql, command) {
|
|
|
3690
3730
|
const opts = command.opts();
|
|
3691
3731
|
assertQueryOptions(sql, opts);
|
|
3692
3732
|
const cellLimit = resolveCellLimit(opts.cellLimit);
|
|
3693
|
-
const client = await connect(
|
|
3733
|
+
const client = await connect(selector, toConnectOptions(opts));
|
|
3694
3734
|
try {
|
|
3695
3735
|
const params = opts.param ?? [];
|
|
3696
3736
|
let backup;
|
|
3697
3737
|
try {
|
|
3698
3738
|
backup = await client.backupWriteStatement(sql, params);
|
|
3699
3739
|
} catch (error) {
|
|
3700
|
-
await enrichAndRethrowQueryError(error, client, sql, opts.refreshMetadata
|
|
3740
|
+
await enrichAndRethrowQueryError(error, client, sql, opts.refreshMetadata);
|
|
3701
3741
|
}
|
|
3702
3742
|
if (backup !== void 0) {
|
|
3703
3743
|
process.stderr.write(`${CLI_NAME}: backup saved to ${backup.directory}
|
|
@@ -3708,7 +3748,7 @@ async function runQuery(selector, sql, command) {
|
|
|
3708
3748
|
error,
|
|
3709
3749
|
client,
|
|
3710
3750
|
sql,
|
|
3711
|
-
opts.refreshMetadata
|
|
3751
|
+
opts.refreshMetadata
|
|
3712
3752
|
);
|
|
3713
3753
|
});
|
|
3714
3754
|
if (result.statement === "select") {
|
|
@@ -3720,8 +3760,6 @@ async function runQuery(selector, sql, command) {
|
|
|
3720
3760
|
...opts.resultTtlMinutes === void 0 ? {} : { ttlMinutes: opts.resultTtlMinutes }
|
|
3721
3761
|
});
|
|
3722
3762
|
print2(`ref=${session.ref}`);
|
|
3723
|
-
process.stderr.write(`${CLI_NAME}: saved result expires at ${session.expiresAt}
|
|
3724
|
-
`);
|
|
3725
3763
|
}
|
|
3726
3764
|
print2(compact.text);
|
|
3727
3765
|
if (result.truncated) {
|
|
@@ -3743,7 +3781,7 @@ async function runQuery(selector, sql, command) {
|
|
|
3743
3781
|
}
|
|
3744
3782
|
async function runTables(selector, schema, command) {
|
|
3745
3783
|
const opts = command.opts();
|
|
3746
|
-
const client = await connect(
|
|
3784
|
+
const client = await connect(selector, toConnectOptions(opts));
|
|
3747
3785
|
try {
|
|
3748
3786
|
const tables = await client.listTables(schema ?? client.info.schema);
|
|
3749
3787
|
const rows = tables.map((table) => ({
|
|
@@ -3759,7 +3797,7 @@ async function runTables(selector, schema, command) {
|
|
|
3759
3797
|
async function runColumns(selector, target, command) {
|
|
3760
3798
|
const opts = command.opts();
|
|
3761
3799
|
const { schema, table } = parseQualifiedName(target);
|
|
3762
|
-
const client = await connect(
|
|
3800
|
+
const client = await connect(selector, toConnectOptions(opts));
|
|
3763
3801
|
try {
|
|
3764
3802
|
const columns = await client.listColumns(schema, table);
|
|
3765
3803
|
const rows = columns.map((column) => ({
|
|
@@ -3777,7 +3815,7 @@ async function runColumns(selector, target, command) {
|
|
|
3777
3815
|
async function runCount(selector, target, command) {
|
|
3778
3816
|
const opts = command.opts();
|
|
3779
3817
|
const { schema, table } = parseQualifiedName(target);
|
|
3780
|
-
const client = await connect(
|
|
3818
|
+
const client = await connect(selector, toConnectOptions(opts));
|
|
3781
3819
|
try {
|
|
3782
3820
|
const total = await client.count({ schema, table });
|
|
3783
3821
|
print2(String(total));
|
|
@@ -3787,7 +3825,7 @@ async function runCount(selector, target, command) {
|
|
|
3787
3825
|
}
|
|
3788
3826
|
async function runPing(selector, command) {
|
|
3789
3827
|
const opts = command.opts();
|
|
3790
|
-
const client = await connect(
|
|
3828
|
+
const client = await connect(selector, toConnectOptions(opts));
|
|
3791
3829
|
try {
|
|
3792
3830
|
const started = Date.now();
|
|
3793
3831
|
await client.query("SELECT 1 FROM DUMMY");
|
|
@@ -3800,7 +3838,7 @@ async function runPing(selector, command) {
|
|
|
3800
3838
|
}
|
|
3801
3839
|
async function runInfo(selector, command) {
|
|
3802
3840
|
const opts = command.opts();
|
|
3803
|
-
const client = await connect(
|
|
3841
|
+
const client = await connect(selector, toConnectOptions(opts));
|
|
3804
3842
|
try {
|
|
3805
3843
|
print2(formatInfo(client.info));
|
|
3806
3844
|
} finally {
|