@saptools/cf-hana 0.2.2 → 0.3.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.
package/dist/index.d.ts CHANGED
@@ -124,6 +124,12 @@ declare function cfHanaBackupRoot(saptoolsRoot?: string): string;
124
124
  declare function buildWriteBackupPlan(sql: string, params?: readonly SqlParam[]): WriteBackupPlan | undefined;
125
125
  declare function writeSqlBackup(input: SqlBackupWriteInput, options?: SqlBackupWriteOptions): Promise<SqlBackupRecord>;
126
126
 
127
+ interface CatalogObjectInfo {
128
+ readonly schema: string;
129
+ readonly name: string;
130
+ readonly type: "TABLE" | "VIEW";
131
+ }
132
+
127
133
  interface DriverExecResult {
128
134
  readonly rows: readonly Record<string, SqlParam>[];
129
135
  readonly columns: readonly QueryResultColumn[];
@@ -265,6 +271,8 @@ declare class HanaClient {
265
271
  listSchemas(): Promise<readonly string[]>;
266
272
  /** List the tables in a schema. */
267
273
  listTables(schema: string): Promise<readonly TableInfo[]>;
274
+ /** List table and view names in a schema for typo suggestions. */
275
+ listCatalogObjects(schema: string): Promise<readonly CatalogObjectInfo[]>;
268
276
  /** List the columns of a table. */
269
277
  listColumns(schema: string, table: string): Promise<readonly ColumnInfo[]>;
270
278
  /** Return the HANA execution plan for a statement. */
@@ -346,4 +354,4 @@ declare class DestructiveStatementError extends CfHanaError {
346
354
  /** Narrow an unknown thrown value to a human-readable message. */
347
355
  declare function errorMessage(error: unknown): string;
348
356
 
349
- export { type BuiltStatement, CfHanaError, type CfHanaErrorCode, type ColumnInfo, type ConnectOptions, type CredentialSource, CredentialsNotFoundError, type DbUserRole, DestructiveStatementError, type DriverConnectParams, type DriverConnection, type DriverExecResult, HanaClient, type HanaClientInfo, type HanaDriver, type OutputFormat, type PoolOptions, QueryError, type QueryOptions, type QueryResult, type QueryResultColumn, type QueryRow, ReadOnlyViolationError, type SelectSpec, type SqlBackupRecord, type SqlBackupWriteInput, type SqlBackupWriteOptions, type SqlParam, type StatementKind, type TableInfo, Transaction, type WriteBackupOperation, type WriteBackupPlan, buildCount, buildDelete, buildInsert, buildSelect, buildUpdate, buildWriteBackupPlan, cfHanaBackupRoot, connect, createDriver, errorMessage, formatCsv, formatJson, formatResult, formatTable, query, withConnection, writeSqlBackup };
357
+ export { type BuiltStatement, type CatalogObjectInfo, CfHanaError, type CfHanaErrorCode, type ColumnInfo, type ConnectOptions, type CredentialSource, CredentialsNotFoundError, type DbUserRole, DestructiveStatementError, type DriverConnectParams, type DriverConnection, type DriverExecResult, HanaClient, type HanaClientInfo, type HanaDriver, type OutputFormat, type PoolOptions, QueryError, type QueryOptions, type QueryResult, type QueryResultColumn, type QueryRow, ReadOnlyViolationError, type SelectSpec, type SqlBackupRecord, type SqlBackupWriteInput, type SqlBackupWriteOptions, type SqlParam, type StatementKind, type TableInfo, Transaction, type WriteBackupOperation, type WriteBackupPlan, buildCount, buildDelete, buildInsert, buildSelect, buildUpdate, buildWriteBackupPlan, cfHanaBackupRoot, connect, createDriver, errorMessage, formatCsv, formatJson, formatResult, formatTable, query, withConnection, writeSqlBackup };
package/dist/index.js CHANGED
@@ -48,8 +48,17 @@ function errorMessage(error) {
48
48
  return String(error);
49
49
  }
50
50
 
51
+ // src/lob.ts
52
+ var TEXT_LOB_TYPES = /* @__PURE__ */ new Set(["CLOB", "NCLOB"]);
53
+ function normalizeTypeName(typeName2) {
54
+ return typeName2?.trim().toUpperCase() ?? "";
55
+ }
56
+ function isTextLobType(typeName2) {
57
+ return TEXT_LOB_TYPES.has(normalizeTypeName(typeName2));
58
+ }
59
+
51
60
  // src/format.ts
52
- function cellText(value, nullText) {
61
+ function cellText(value, nullText, column) {
53
62
  if (value === null) {
54
63
  return nullText;
55
64
  }
@@ -57,14 +66,14 @@ function cellText(value, nullText) {
57
66
  return value.toISOString();
58
67
  }
59
68
  if (Buffer.isBuffer(value)) {
60
- return `0x${value.toString("hex")}`;
69
+ return isTextLobType(column?.typeName) ? value.toString("utf8") : `0x${value.toString("hex")}`;
61
70
  }
62
71
  if (typeof value === "boolean") {
63
72
  return value ? "true" : "false";
64
73
  }
65
74
  return typeof value === "number" ? value.toString() : value;
66
75
  }
67
- function serializeCell(value) {
76
+ function serializeCell(value, column) {
68
77
  if (value === null) {
69
78
  return null;
70
79
  }
@@ -72,7 +81,7 @@ function serializeCell(value) {
72
81
  return value.toISOString();
73
82
  }
74
83
  if (Buffer.isBuffer(value)) {
75
- return `0x${value.toString("hex")}`;
84
+ return isTextLobType(column?.typeName) ? value.toString("utf8") : `0x${value.toString("hex")}`;
76
85
  }
77
86
  return value;
78
87
  }
@@ -88,7 +97,7 @@ function formatTable(result) {
88
97
  }
89
98
  const headers = result.columns.map((column) => column.name);
90
99
  const rows = result.rows.map(
91
- (row) => result.columns.map((column) => cellText(row[column.name] ?? null, "NULL"))
100
+ (row) => result.columns.map((column) => cellText(row[column.name] ?? null, "NULL", column))
92
101
  );
93
102
  const widths = headers.map((header, index) => {
94
103
  const widest = rows.reduce(
@@ -106,7 +115,8 @@ function formatJson(result) {
106
115
  const rows = result.rows.map((row) => {
107
116
  const serialized = {};
108
117
  for (const [key, value] of Object.entries(row)) {
109
- serialized[key] = serializeCell(value);
118
+ const column = result.columns.find((item) => item.name === key);
119
+ serialized[key] = serializeCell(value, column);
110
120
  }
111
121
  return serialized;
112
122
  });
@@ -117,7 +127,7 @@ function formatCsv(result) {
117
127
  const lines = [headers.map((header) => csvEscape(header)).join(",")];
118
128
  for (const row of result.rows) {
119
129
  lines.push(
120
- result.columns.map((column) => csvEscape(cellText(row[column.name] ?? null, ""))).join(",")
130
+ result.columns.map((column) => csvEscape(cellText(row[column.name] ?? null, "", column))).join(",")
121
131
  );
122
132
  }
123
133
  return lines.join("\r\n");
@@ -562,6 +572,18 @@ async function listTables(connection, schema) {
562
572
  rowCount: void 0
563
573
  }));
564
574
  }
575
+ async function listCatalogObjects(connection, schema) {
576
+ const result = await connection.query(
577
+ "SELECT SCHEMA_NAME, TABLE_NAME AS OBJECT_NAME, 'TABLE' AS OBJECT_TYPE FROM SYS.TABLES WHERE SCHEMA_NAME = ? UNION ALL SELECT SCHEMA_NAME, VIEW_NAME AS OBJECT_NAME, 'VIEW' AS OBJECT_TYPE FROM SYS.VIEWS WHERE SCHEMA_NAME = ? ORDER BY OBJECT_NAME",
578
+ [schema, schema],
579
+ { autoLimit: false }
580
+ );
581
+ return result.rows.map((row) => ({
582
+ schema: row.SCHEMA_NAME,
583
+ name: row.OBJECT_NAME,
584
+ type: row.OBJECT_TYPE
585
+ }));
586
+ }
565
587
  async function listColumns(connection, schema, table) {
566
588
  const result = await connection.query(
567
589
  "SELECT COLUMN_NAME, DATA_TYPE_NAME, LENGTH, SCALE, IS_NULLABLE, POSITION FROM SYS.TABLE_COLUMNS WHERE SCHEMA_NAME = ? AND TABLE_NAME = ? ORDER BY POSITION",
@@ -579,7 +601,7 @@ async function listColumns(connection, schema, table) {
579
601
  }
580
602
 
581
603
  // src/config.ts
582
- var CLI_VERSION = "0.2.1";
604
+ var CLI_VERSION = "0.3.0";
583
605
  var ENV_PREFIX = "CF_HANA";
584
606
  var DEFAULT_QUERY_TIMEOUT_MS = 3e4;
585
607
  var DEFAULT_CONNECT_TIMEOUT_MS = 3e4;
@@ -1037,12 +1059,57 @@ function toConnectionTarget(binding, role) {
1037
1059
 
1038
1060
  // src/driver/fake.ts
1039
1061
  import { appendFile } from "fs/promises";
1062
+ var catalogFailureInjected = false;
1063
+ function catalogObjects() {
1064
+ return [
1065
+ { SCHEMA_NAME: "APP_SCHEMA", OBJECT_NAME: "EXISTING_TABLE", OBJECT_TYPE: "TABLE" },
1066
+ { SCHEMA_NAME: "APP_SCHEMA", OBJECT_NAME: "MISSING_TABLE_FIXED", OBJECT_TYPE: "TABLE" },
1067
+ { SCHEMA_NAME: "APP_SCHEMA", OBJECT_NAME: "MISSING_TABLE_VIEW", OBJECT_TYPE: "VIEW" },
1068
+ { SCHEMA_NAME: "APP_SCHEMA", OBJECT_NAME: "STATUS_ITEMS", OBJECT_TYPE: "TABLE" }
1069
+ ];
1070
+ }
1040
1071
  function fakeExec(sql) {
1041
1072
  const kind = classifyStatement(sql);
1042
1073
  const forcedFailure = readEnv(envName("FAKE_FAIL_STATEMENT"))?.toLowerCase();
1043
1074
  if (forcedFailure === kind) {
1044
1075
  throw new Error(`fake driver forced ${kind.toUpperCase()} failure`);
1045
1076
  }
1077
+ const upperSql = sql.toUpperCase();
1078
+ if (upperSql.includes("SYS.TABLES") && upperSql.includes("SYS.VIEWS")) {
1079
+ if (readEnv(envName("FAKE_FAIL_CATALOG_ONCE")) === "1" && !catalogFailureInjected) {
1080
+ catalogFailureInjected = true;
1081
+ throw new QueryError("fake transient catalog metadata failure");
1082
+ }
1083
+ return {
1084
+ rows: catalogObjects(),
1085
+ columns: [
1086
+ { name: "SCHEMA_NAME", typeName: "NVARCHAR" },
1087
+ { name: "OBJECT_NAME", typeName: "NVARCHAR" },
1088
+ { name: "OBJECT_TYPE", typeName: "NVARCHAR" }
1089
+ ],
1090
+ affectedRows: 0
1091
+ };
1092
+ }
1093
+ if (upperSql.includes("MISSING_TABLE") || upperSql.includes("MISSING_TABLES")) {
1094
+ throw new QueryError("invalid table name: MISSING_TABLE", { sqlState: "42S02" });
1095
+ }
1096
+ if (sql.toUpperCase().includes("LOB_FIXTURE")) {
1097
+ return {
1098
+ rows: [
1099
+ {
1100
+ LOG_CONTENT: Buffer.from("Example log entry", "utf8"),
1101
+ CLOB_CONTENT: Buffer.from("Clob log entry", "utf8"),
1102
+ PAYLOAD: Buffer.from([0, 1, 2, 255])
1103
+ }
1104
+ ],
1105
+ columns: [
1106
+ { name: "LOG_CONTENT", typeName: "NCLOB" },
1107
+ { name: "CLOB_CONTENT", typeName: "CLOB" },
1108
+ { name: "PAYLOAD", typeName: "BLOB" }
1109
+ ],
1110
+ affectedRows: 0
1111
+ };
1112
+ }
1046
1113
  if (sql.toUpperCase().includes("DUMMY")) {
1047
1114
  return {
1048
1115
  rows: [{ "1": 1 }],
@@ -2042,6 +2109,10 @@ var HanaClient = class _HanaClient {
2042
2109
  async listTables(schema) {
2043
2110
  return await this.pool.withConnection((connection) => listTables(connection, schema));
2044
2111
  }
2112
+ /** List table and view names in a schema for typo suggestions. */
2113
+ async listCatalogObjects(schema) {
2114
+ return await this.pool.withConnection((connection) => listCatalogObjects(connection, schema));
2115
+ }
2045
2116
  /** List the columns of a table. */
2046
2117
  async listColumns(schema, table) {
2047
2118
  return await this.pool.withConnection(