@saptools/cf-hana 0.1.4 → 0.1.6

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.6 - 2026-06-23
4
+
5
+ - Expand fake-backed E2E coverage for complex `UPDATE` and `DELETE` backups,
6
+ including mixed-case keywords, comments, quoted identifiers, nested queries,
7
+ placeholder filtering, and unscoped writes.
8
+ - Verify backup SELECT failures, write failures, filesystem failures, read-only
9
+ mode, malformed SQL, and parameter mismatches cannot bypass backup safety.
10
+ - Add opt-in fake-driver statement tracing and deterministic failure injection
11
+ without recording parameter values.
12
+
13
+ ## 0.1.5 - 2026-06-23
14
+
15
+ - Remove the CLI `--no-backup` opt-out so `cf-hana query` always attempts a
16
+ local backup before running `UPDATE` or `DELETE`.
17
+ - Keep backup paths on stderr and keep stdout parseable for table, JSON, and CSV
18
+ output.
19
+
3
20
  ## 0.1.4 - 2026-06-23
4
21
 
5
22
  - Add automatic local CSV backups before CLI `query` runs an `UPDATE` or
@@ -7,7 +24,7 @@
7
24
  - Derive the backup `SELECT` from the write target and top-level `WHERE`
8
25
  clause, preserving only the WHERE parameters for `UPDATE` statements.
9
26
  - Save each backup in its own non-expiring folder with `statement.sql` and
10
- `backup.csv`, and add `--no-backup` for explicit CLI opt-out.
27
+ `backup.csv`.
11
28
 
12
29
  ## 0.1.3 - 2026-06-23
13
30
 
package/README.md CHANGED
@@ -88,8 +88,8 @@ cf-hana info <selector> Print the resolved connection metada
88
88
  Common options: `--format <table|json|csv>`, `--refresh`, `--role <runtime|hdi>`,
89
89
  `--binding <name>` / `--binding-index <n>`, `--timeout <ms>`, `--read-only`,
90
90
  `--allow-destructive`, `--limit <n>`, `--no-auto-limit`. The `query` command also
91
- accepts `--param <value>` (repeatable) to bind `?` placeholders and
92
- `--no-backup` to skip the default write backup.
91
+ accepts `--param <value>` (repeatable) to bind `?` placeholders. CLI `UPDATE`
92
+ and `DELETE` statements are backed up automatically before the write runs.
93
93
 
94
94
  ```bash
95
95
  cf-hana query eu10/example-org/space-demo/app-demo "SELECT ID, STATUS FROM ORDERS WHERE STATUS = ?" \
@@ -169,13 +169,6 @@ rows returned by the derived `SELECT`. Backup folders are not deleted by
169
169
  `cf-hana`; clean them up manually when they are no longer needed. The backup path
170
170
  is printed to stderr so JSON and CSV stdout remain parseable.
171
171
 
172
- Use `--no-backup` when an external backup or transaction workflow already covers
173
- the write:
174
-
175
- ```bash
176
- cf-hana query app-demo "DELETE FROM ORDERS WHERE ID = ?" --param 42 --no-backup
177
- ```
178
-
179
172
  ## Safety
180
173
 
181
174
  - **Read-only mode** (`readOnly` / `--read-only`) rejects every DML and DDL statement.
package/dist/cli.js CHANGED
@@ -514,7 +514,7 @@ async function listColumns(connection, schema, table) {
514
514
 
515
515
  // src/config.ts
516
516
  var CLI_NAME = "cf-hana";
517
- var CLI_VERSION = "0.1.4";
517
+ var CLI_VERSION = "0.1.6";
518
518
  var ENV_PREFIX = "CF_HANA";
519
519
  var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
520
520
  var DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
@@ -633,7 +633,13 @@ function toConnectionTarget(binding, role) {
633
633
  }
634
634
 
635
635
  // src/driver/fake.ts
636
+ import { appendFile } from "fs/promises";
636
637
  function fakeExec(sql) {
638
+ const kind = classifyStatement(sql);
639
+ const forcedFailure = readEnv(envName("FAKE_FAIL_STATEMENT"))?.toLowerCase();
640
+ if (forcedFailure === kind) {
641
+ throw new Error(`fake driver forced ${kind.toUpperCase()} failure`);
642
+ }
637
643
  if (sql.toUpperCase().includes("DUMMY")) {
638
644
  return {
639
645
  rows: [{ "1": 1 }],
@@ -641,7 +647,6 @@ function fakeExec(sql) {
641
647
  affectedRows: 0
642
648
  };
643
649
  }
644
- const kind = classifyStatement(sql);
645
650
  if (kind === "select") {
646
651
  return {
647
652
  rows: [
@@ -660,10 +665,23 @@ function fakeExec(sql) {
660
665
  }
661
666
  return { rows: [], columns: [], affectedRows: 0 };
662
667
  }
668
+ async function traceFakeExec(sql, params) {
669
+ const tracePath = readEnv(envName("FAKE_TRACE_FILE"));
670
+ if (tracePath === void 0) {
671
+ return;
672
+ }
673
+ const entry = { sql, paramCount: params.length };
674
+ await appendFile(tracePath, `${JSON.stringify(entry)}
675
+ `, {
676
+ encoding: "utf8",
677
+ mode: 384
678
+ });
679
+ }
663
680
  var FakeConnection = class {
664
681
  closed = false;
665
- exec(sql, _params) {
666
- return Promise.resolve(fakeExec(sql));
682
+ async exec(sql, params) {
683
+ await traceFakeExec(sql, params);
684
+ return fakeExec(sql);
667
685
  }
668
686
  setAutoCommit(_enabled) {
669
687
  return Promise.resolve();
@@ -937,7 +955,7 @@ function createDriver(name) {
937
955
  }
938
956
 
939
957
  // src/history.ts
940
- import { appendFile, mkdir as mkdir2, readdir, rm } from "fs/promises";
958
+ import { appendFile as appendFile2, mkdir as mkdir2, readdir, rm } from "fs/promises";
941
959
  import { homedir as homedir2 } from "os";
942
960
  import { join as join2 } from "path";
943
961
  var SAPTOOLS_DIR_NAME2 = ".saptools";
@@ -999,7 +1017,7 @@ async function appendSqlHistory(input, options = {}) {
999
1017
  ...input
1000
1018
  };
1001
1019
  await mkdir2(historyDir, { recursive: true, mode: 448 });
1002
- await appendFile(sqlHistoryFilePath(now, options.saptoolsRoot), `${JSON.stringify(entry)}
1020
+ await appendFile2(sqlHistoryFilePath(now, options.saptoolsRoot), `${JSON.stringify(entry)}
1003
1021
  `, {
1004
1022
  encoding: "utf8",
1005
1023
  mode: 384
@@ -1780,7 +1798,7 @@ async function runQuery(selector, sql, command) {
1780
1798
  const client = await connect(selector, toConnectOptions(opts));
1781
1799
  try {
1782
1800
  const params = opts.param ?? [];
1783
- const backup = opts.backup ? await client.backupWriteStatement(sql, params) : void 0;
1801
+ const backup = await client.backupWriteStatement(sql, params);
1784
1802
  if (backup !== void 0) {
1785
1803
  process.stderr.write(`${CLI_NAME}: backup saved to ${backup.directory}
1786
1804
  `);
@@ -1861,7 +1879,7 @@ function buildProgram() {
1861
1879
  const program = new Command();
1862
1880
  program.name(CLI_NAME).description("Run SQL against SAP HANA Cloud databases bound to a Cloud Foundry app").version(CLI_VERSION);
1863
1881
  withConnectionOptions(
1864
- program.command("query <selector> <sql>").description("run a single SQL statement").option("--param <value>", "bind a SQL parameter (repeatable)", collectParam, []).option("--no-backup", "skip the local CSV backup before UPDATE or DELETE")
1882
+ program.command("query <selector> <sql>").description("run a single SQL statement").option("--param <value>", "bind a SQL parameter (repeatable)", collectParam, [])
1865
1883
  ).action(async (selector, sql, _options, command) => {
1866
1884
  await runQuery(selector, sql, command);
1867
1885
  });