@saptools/cf-hana 0.3.2 → 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 CHANGED
@@ -1,5 +1,18 @@
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
+
3
16
  ## 0.3.2 - 2026-07-01
4
17
 
5
18
  - Remove the `cf-hana: saved result expires at...` stderr notice from `query --save` output while keeping result refs available for inspection.
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` closes the last gap in the `saptools` chain: `@saptools/cf-sync`
6
- already discovers Cloud Foundry topology and HANA service bindings, but nothing
7
- actually executes SQL. This package does. Pass a selector, get a connected,
8
- pooled client, and run `SELECT` / `INSERT` / `UPDATE` / `DELETE` / DDL.
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** — cache-first via `@saptools/cf-sync`, with an
17
- on-demand live Cloud Foundry fetch as a fallback.
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 topology cached by
78
- `cf-sync sync`; throws if the name is ambiguous across spaces.
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`, `--role <runtime|hdi>`, `--binding <name>` /
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; `--refresh`
174
- also bypasses it because it refreshes the resolved connection identity. If
175
- metadata lookup or cache I/O fails, cf-hana preserves the original query error
176
- and simply omits suggestions.
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
- Credentials are resolved **cache-first**:
195
-
196
- 1. Read what `cf-sync db-sync` cached in `~/.saptools/cf-db-bindings.json`.
197
- 2. On a cache miss (or when `refresh: true` / `--refresh` is passed), fetch them
198
- live from Cloud Foundry. The live fetch needs `SAP_EMAIL` and `SAP_PASSWORD`
199
- (or the `email` / `password` options) and never persists anything to disk.
200
-
201
- Credential resolution writes nothing under `~/.saptools/` it only reads what
202
- `cf-sync` cached. The connection pool is in-process and in-memory only, so it is
203
- safe to run many `cf-hana` processes in parallel and alongside any `cf-sync`
204
- command.
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, or a
268
- live credential fetch, additionally needs the Cloud Foundry CLI and
269
- `SAP_EMAIL` / `SAP_PASSWORD`.
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.2";
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
- "eu10-002": "https://api.cf.eu10-002.hana.ondemand.com",
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
- in30: "https://api.cf.in30.hana.ondemand.com"
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
- const norm2 = apiEndpoint.trim().replace(/\/+$/, "").toLowerCase();
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() === norm2) {
836
+ if (endpoint.toLowerCase() === normalized) {
783
837
  return key;
784
838
  }
785
839
  }
786
- return void 0;
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
- const regionKey = getRegionKeyForApi(api);
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: api,
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
- if (!target.regionKey) {
915
- throw new Error(
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.regionKey ? getApiEndpointForRegion(current.regionKey) : void 0,
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 key "${regionKey}". Use a known region or the current CF target.`
1094
+ `Unknown SAP CF region "${regionKey}". Verify the current SAP region list or use the current CF target.`
1036
1095
  );
1037
1096
  }
1038
- target = { selector, apiEndpoint, orgName, spaceName, appName };
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", "bypass cached credentials and fetch them 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);
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(await resolveSelectorArgument(selector), toConnectOptions(opts));
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 || opts.refresh);
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 || opts.refresh
3751
+ opts.refreshMetadata
3712
3752
  );
3713
3753
  });
3714
3754
  if (result.statement === "select") {
@@ -3741,7 +3781,7 @@ async function runQuery(selector, sql, command) {
3741
3781
  }
3742
3782
  async function runTables(selector, schema, command) {
3743
3783
  const opts = command.opts();
3744
- const client = await connect(await resolveSelectorArgument(selector), toConnectOptions(opts));
3784
+ const client = await connect(selector, toConnectOptions(opts));
3745
3785
  try {
3746
3786
  const tables = await client.listTables(schema ?? client.info.schema);
3747
3787
  const rows = tables.map((table) => ({
@@ -3757,7 +3797,7 @@ async function runTables(selector, schema, command) {
3757
3797
  async function runColumns(selector, target, command) {
3758
3798
  const opts = command.opts();
3759
3799
  const { schema, table } = parseQualifiedName(target);
3760
- const client = await connect(await resolveSelectorArgument(selector), toConnectOptions(opts));
3800
+ const client = await connect(selector, toConnectOptions(opts));
3761
3801
  try {
3762
3802
  const columns = await client.listColumns(schema, table);
3763
3803
  const rows = columns.map((column) => ({
@@ -3775,7 +3815,7 @@ async function runColumns(selector, target, command) {
3775
3815
  async function runCount(selector, target, command) {
3776
3816
  const opts = command.opts();
3777
3817
  const { schema, table } = parseQualifiedName(target);
3778
- const client = await connect(await resolveSelectorArgument(selector), toConnectOptions(opts));
3818
+ const client = await connect(selector, toConnectOptions(opts));
3779
3819
  try {
3780
3820
  const total = await client.count({ schema, table });
3781
3821
  print2(String(total));
@@ -3785,7 +3825,7 @@ async function runCount(selector, target, command) {
3785
3825
  }
3786
3826
  async function runPing(selector, command) {
3787
3827
  const opts = command.opts();
3788
- const client = await connect(await resolveSelectorArgument(selector), toConnectOptions(opts));
3828
+ const client = await connect(selector, toConnectOptions(opts));
3789
3829
  try {
3790
3830
  const started = Date.now();
3791
3831
  await client.query("SELECT 1 FROM DUMMY");
@@ -3798,7 +3838,7 @@ async function runPing(selector, command) {
3798
3838
  }
3799
3839
  async function runInfo(selector, command) {
3800
3840
  const opts = command.opts();
3801
- const client = await connect(await resolveSelectorArgument(selector), toConnectOptions(opts));
3841
+ const client = await connect(selector, toConnectOptions(opts));
3802
3842
  try {
3803
3843
  print2(formatInfo(client.info));
3804
3844
  } finally {