@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/CHANGELOG.md +6 -0
- package/README.md +32 -5
- package/dist/cli.js +563 -25
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +79 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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(
|