@abaplint/runtime 2.13.95 → 2.13.97

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.
@@ -1,6 +1,7 @@
1
1
  import { Console } from "./console/console";
2
2
  import { DatabaseClient, DatabaseCursorCallbacks } from "./db/db";
3
3
  import * as RFC from "./rfc";
4
+ import { DatasetHost, OpenDataset } from "./dataset/dataset";
4
5
  export declare class Context {
5
6
  console: Console;
6
7
  cursorCounter: number;
@@ -13,5 +14,19 @@ export declare class Context {
13
14
  RFCDestinations: {
14
15
  [name: string]: RFC.RFCClient;
15
16
  };
17
+ updateTask: {
18
+ register(name: string, param: {
19
+ exporting?: any;
20
+ tables?: any;
21
+ }): Promise<void> | void;
22
+ } | undefined;
23
+ dataset: DatasetHost | undefined;
24
+ datasets: {
25
+ [name: string]: OpenDataset;
26
+ };
27
+ runTime: {
28
+ start: number;
29
+ last: number;
30
+ } | undefined;
16
31
  defaultDB(): DatabaseClient;
17
32
  }
@@ -8,6 +8,14 @@ class Context {
8
8
  // DEFAULT and secondary database connections
9
9
  databaseConnections = {};
10
10
  RFCDestinations = {};
11
+ // CALL FUNCTION IN UPDATE TASK hands the copied parameters to the host, if one is set
12
+ updateTask = undefined;
13
+ // the file system OPEN DATASET and friends read and write; none by default,
14
+ // and then every DATASET statement throws
15
+ dataset = undefined;
16
+ datasets = {};
17
+ // GET RUN TIME, the clock reading of the first call, and the last value returned
18
+ runTime = undefined;
11
19
  defaultDB() {
12
20
  if (this.databaseConnections["DEFAULT"] === undefined) {
13
21
  throw new Error("Runtime, database not initialized");
@@ -0,0 +1,51 @@
1
+ import { FieldSymbol } from "../types";
2
+ import { ICharacter } from "../types/_character";
3
+ import { INumeric } from "../types/_numeric";
4
+ export type DatasetMode = "INPUT" | "OUTPUT" | "APPENDING" | "UPDATE";
5
+ /** an open file: positional reads and writes, the runtime keeps the position */
6
+ export interface DatasetHandle {
7
+ /** up to length bytes from position; fewer at the end of the file */
8
+ read(position: number, length: number): Promise<Uint8Array>;
9
+ write(position: number, bytes: Uint8Array): Promise<void>;
10
+ size(): Promise<number>;
11
+ close(): Promise<void>;
12
+ }
13
+ export interface DatasetHost {
14
+ /** OUTPUT truncates or creates, APPENDING and UPDATE keep the content;
15
+ * a failure is returned as the text OPEN DATASET ... MESSAGE receives
16
+ * and becomes sy-subrc 8 */
17
+ open(name: string, mode: DatasetMode): Promise<DatasetHandle | {
18
+ message: string;
19
+ }>;
20
+ /** false when there was nothing to delete (sy-subrc 4) */
21
+ delete(name: string): Promise<boolean>;
22
+ }
23
+ export interface IOpenDatasetOptions {
24
+ mode: DatasetMode;
25
+ binary: boolean;
26
+ encoding?: "DEFAULT" | "UTF-8" | "NON-UNICODE";
27
+ legacy?: boolean;
28
+ message?: ICharacter | FieldSymbol;
29
+ position?: INumeric | FieldSymbol;
30
+ /** additions this runtime does not implement, by name */
31
+ unsupported?: string[];
32
+ }
33
+ export interface ITransferOptions {
34
+ length?: INumeric | FieldSymbol;
35
+ noEndOfLine?: boolean;
36
+ }
37
+ export interface IReadDatasetOptions {
38
+ maximumLength?: INumeric | FieldSymbol;
39
+ actualLength?: INumeric | FieldSymbol;
40
+ }
41
+ export interface IGetDatasetOptions {
42
+ position?: INumeric | FieldSymbol;
43
+ attributes?: any;
44
+ }
45
+ /** a file OPEN DATASET opened, as the runtime keeps it by name */
46
+ export interface OpenDataset {
47
+ handle: DatasetHandle;
48
+ mode: DatasetMode;
49
+ binary: boolean;
50
+ position: number;
51
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=dataset.js.map
@@ -0,0 +1,17 @@
1
+ import { DatasetHandle, DatasetHost, DatasetMode } from "./dataset";
2
+ /** A DatasetHost over a map of names to bytes: for tests, and for a runtime
3
+ * in a browser, where there is no file system to hand to OPEN DATASET.
4
+ * OUTPUT empties or creates a file, APPENDING and UPDATE create it when
5
+ * missing, INPUT of a missing file fails with the message a system gives. */
6
+ export declare class MemoryDataset implements DatasetHost {
7
+ readonly files: {
8
+ [name: string]: Uint8Array;
9
+ };
10
+ constructor(files?: {
11
+ [name: string]: Uint8Array;
12
+ });
13
+ open(name: string, mode: DatasetMode): Promise<DatasetHandle | {
14
+ message: string;
15
+ }>;
16
+ delete(name: string): Promise<boolean>;
17
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryDataset = void 0;
4
+ /** A DatasetHost over a map of names to bytes: for tests, and for a runtime
5
+ * in a browser, where there is no file system to hand to OPEN DATASET.
6
+ * OUTPUT empties or creates a file, APPENDING and UPDATE create it when
7
+ * missing, INPUT of a missing file fails with the message a system gives. */
8
+ class MemoryDataset {
9
+ files;
10
+ constructor(files = {}) {
11
+ this.files = files;
12
+ }
13
+ async open(name, mode) {
14
+ const files = this.files;
15
+ if (files[name] === undefined) {
16
+ if (mode === "INPUT") {
17
+ return { message: "No such file or directory" };
18
+ }
19
+ files[name] = new Uint8Array(0);
20
+ }
21
+ if (mode === "OUTPUT") {
22
+ files[name] = new Uint8Array(0);
23
+ }
24
+ return {
25
+ read: async (position, length) => files[name].slice(position, position + length),
26
+ write: async (position, bytes) => {
27
+ const end = Math.max(files[name].length, position + bytes.length);
28
+ const out = new Uint8Array(end);
29
+ out.set(files[name], 0);
30
+ out.set(bytes, position);
31
+ files[name] = out;
32
+ },
33
+ size: async () => files[name].length,
34
+ close: async () => undefined,
35
+ };
36
+ }
37
+ async delete(name) {
38
+ if (this.files[name] === undefined) {
39
+ return false;
40
+ }
41
+ delete this.files[name];
42
+ return true;
43
+ }
44
+ }
45
+ exports.MemoryDataset = MemoryDataset;
46
+ //# sourceMappingURL=memory_dataset.js.map
@@ -21,7 +21,10 @@ import { ABAPEventing } from "./abap_eventing";
21
21
  import { isLineNotFound } from "./is_line_not_found";
22
22
  import { parametersCall } from "./parameters_call";
23
23
  import { alphaIn, alphaOut } from "./alpha";
24
+ import { DatasetHost } from "./dataset/dataset";
24
25
  export { RFC, types, DB, MemoryConsole };
26
+ export type { DatasetHost, DatasetHandle, DatasetMode } from "./dataset/dataset";
27
+ export { MemoryDataset } from "./dataset/memory_dataset";
25
28
  export type RuntimeDatabaseOptions = {
26
29
  schemaPrefix?: string;
27
30
  tablePrefix?: string;
@@ -29,6 +32,7 @@ export type RuntimeDatabaseOptions = {
29
32
  export type RuntimeOptions = {
30
33
  console?: Console;
31
34
  database?: RuntimeDatabaseOptions;
35
+ dataset?: DatasetHost;
32
36
  };
33
37
  export declare class ABAP {
34
38
  Classes: {
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.ABAP = exports.MemoryConsole = exports.DB = exports.types = exports.RFC = void 0;
36
+ exports.ABAP = exports.MemoryDataset = exports.MemoryConsole = exports.DB = exports.types = exports.RFC = void 0;
37
37
  const context_1 = require("./context");
38
38
  const offset_length_1 = require("./offset_length");
39
39
  const statements_1 = require("./statements");
@@ -61,6 +61,8 @@ const abap_eventing_1 = require("./abap_eventing");
61
61
  const is_line_not_found_1 = require("./is_line_not_found");
62
62
  const parameters_call_1 = require("./parameters_call");
63
63
  const alpha_1 = require("./alpha");
64
+ var memory_dataset_1 = require("./dataset/memory_dataset");
65
+ Object.defineProperty(exports, "MemoryDataset", { enumerable: true, get: function () { return memory_dataset_1.MemoryDataset; } });
64
66
  class ABAP {
65
67
  // global objects
66
68
  Classes = {};
@@ -101,6 +103,7 @@ class ABAP {
101
103
  this.context = new context_1.Context();
102
104
  this.console = input?.console ? input?.console : new standard_out_console_1.StandardOutConsole();
103
105
  this.context.console = this.console;
106
+ this.context.dataset = input?.dataset;
104
107
  this.dbo = input?.database || { schemaPrefix: "", tablePrefix: "" };
105
108
  if (this.dbo.schemaPrefix === undefined) {
106
109
  this.dbo.schemaPrefix = "";
@@ -2,3 +2,6 @@ import { Float, Integer, Integer8 } from "../types";
2
2
  import { ICharacter } from "../types/_character";
3
3
  import { INumeric } from "../types/_numeric";
4
4
  export declare function divide(left: INumeric | ICharacter | Integer8 | string | number, right: INumeric | ICharacter | Integer8 | string | number): Float | Integer8 | Integer;
5
+ /** "/" in calculation type int8, which the transpiler knows from an int8 target: the
6
+ * operands are int8 and the quotient is rounded half away from zero, 7 / 2 = 4, -7 / 2 = -4 */
7
+ export declare function divideInt8(left: INumeric | ICharacter | Integer8 | string | number, right: INumeric | ICharacter | Integer8 | string | number): Integer8;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.divide = divide;
4
+ exports.divideInt8 = divideInt8;
4
5
  const throw_error_1 = require("../throw_error");
5
6
  const types_1 = require("../types");
6
7
  const _parse_1 = require("./_parse");
@@ -42,4 +43,22 @@ function isIntegerOperand(val) {
42
43
  }
43
44
  return val instanceof types_1.Integer8;
44
45
  }
46
+ /** "/" in calculation type int8, which the transpiler knows from an int8 target: the
47
+ * operands are int8 and the quotient is rounded half away from zero, 7 / 2 = 4, -7 / 2 = -4 */
48
+ function divideInt8(left, right) {
49
+ const l = left instanceof types_1.Integer8 ? left.get() : new types_1.Integer8().set(left).get();
50
+ const r = right instanceof types_1.Integer8 ? right.get() : new types_1.Integer8().set(right).get();
51
+ if (r === 0n) {
52
+ if (l === 0n) {
53
+ return new types_1.Integer8().set(0n);
54
+ }
55
+ (0, throw_error_1.throwError)("CX_SY_ZERODIVIDE");
56
+ }
57
+ let q = l / r;
58
+ const rem = l % r;
59
+ if ((rem < 0n ? -rem : rem) * 2n >= (r < 0n ? -r : r)) {
60
+ q = (l < 0n) === (r < 0n) ? q + 1n : q - 1n;
61
+ }
62
+ return new types_1.Integer8().set(q);
63
+ }
45
64
  //# sourceMappingURL=divide.js.map
@@ -1,2 +1,2 @@
1
- import { Table } from "./types";
2
- export declare function primaryKeyValues(target: Table, row: any): any[];
1
+ import { HashedTable, Table } from "./types";
2
+ export declare function primaryKeyValues(target: Table | HashedTable, row: any): any[];
@@ -1,6 +1,7 @@
1
1
  import { Context } from "../context";
2
2
  export interface ICallFunctionOptions {
3
3
  name: string;
4
+ updateTask?: boolean;
4
5
  destination?: string;
5
6
  calling?: (INPUT: any) => any;
6
7
  exporting?: any;
@@ -9,7 +9,7 @@ class CallFunction {
9
9
  constructor(context) {
10
10
  this.context = context;
11
11
  }
12
- // note: this is only called if DESTINIATION is supplied
12
+ // called for DESTINATION, CALLING or IN UPDATE TASK
13
13
  async callFunction(options) {
14
14
  const param = {
15
15
  exporting: options.exporting,
@@ -20,7 +20,32 @@ class CallFunction {
20
20
  };
21
21
  options.name = options.name.trimEnd();
22
22
  const fm = abap.FunctionModules[options.name];
23
- if (options.destination) {
23
+ if (options.updateTask) {
24
+ if (fm === undefined) {
25
+ (0, throw_error_1.throwError)("CX_SY_DYN_CALL_ILLEGAL_FUNC");
26
+ }
27
+ const copy = (values) => {
28
+ if (values === undefined) {
29
+ return undefined;
30
+ }
31
+ const result = {};
32
+ for (const key of Object.keys(values)) {
33
+ const value = values[key];
34
+ result[key] = value instanceof types_1.FieldSymbol ? value.getPointer().clone() : value.clone();
35
+ }
36
+ return result;
37
+ };
38
+ // IMPORTING and CHANGING do not apply to update modules.
39
+ const copied = { exporting: copy(options.exporting), tables: copy(options.tables) };
40
+ if (this.context.updateTask) {
41
+ await this.context.updateTask.register(options.name, copied);
42
+ }
43
+ else {
44
+ await fm(copied);
45
+ }
46
+ return;
47
+ }
48
+ else if (options.destination) {
24
49
  if (options.destination.trim() === "") {
25
50
  if (fm === undefined) {
26
51
  (0, throw_error_1.throwError)("CX_SY_DYN_CALL_ILLEGAL_FUNC");
@@ -1,3 +1,3 @@
1
- import { Table } from "../types";
1
+ import { HashedTable, Structure, Table } from "../types";
2
2
  import { ICharacter } from "../types/_character";
3
- export declare function collect(source: ICharacter | Table, target?: Table): void;
3
+ export declare function collect(source: ICharacter | Structure | Table, target?: Table | HashedTable): void;
@@ -2,25 +2,64 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.collect = collect;
4
4
  const compare_1 = require("../compare");
5
+ const primary_key_1 = require("../primary_key");
5
6
  const types_1 = require("../types");
6
7
  const insert_internal_1 = require("./insert_internal");
7
- const read_table_1 = require("./read_table");
8
+ function sumNumeric(found, source, keyValues) {
9
+ if (found instanceof types_1.Structure && source instanceof types_1.Structure) {
10
+ for (const name of Object.keys(source.get())) {
11
+ sumNumeric(found.get()[name], source.get()[name], keyValues);
12
+ }
13
+ }
14
+ else if (keyValues.has(source)) {
15
+ return;
16
+ }
17
+ else if (found instanceof types_1.Integer && source instanceof types_1.Integer) {
18
+ found.set(found.get() + source.get());
19
+ }
20
+ else if (found instanceof types_1.Integer8 && source instanceof types_1.Integer8) {
21
+ found.set(found.get() + source.get());
22
+ }
23
+ else if (found instanceof types_1.Packed && source instanceof types_1.Packed) {
24
+ const decimals = found.getDecimals();
25
+ const factor = 10n ** BigInt(decimals);
26
+ const left = BigInt(found.toFixed(decimals).replace(".", ""));
27
+ const right = BigInt(source.toFixed(decimals).replace(".", ""));
28
+ const total = left + right;
29
+ const magnitude = total < 0n ? -total : total;
30
+ const fraction = decimals === 0 ? "" : "." + (magnitude % factor).toString().padStart(decimals, "0");
31
+ found.set((total < 0n ? "-" : "") + (magnitude / factor).toString() + fraction);
32
+ }
33
+ else if (found instanceof types_1.Float && source instanceof types_1.Float) {
34
+ found.set(found.getRaw() + source.getRaw());
35
+ }
36
+ else if (found instanceof types_1.DecFloat34 && source instanceof types_1.DecFloat34) {
37
+ found.set(found.getRaw() + source.getRaw());
38
+ }
39
+ }
8
40
  function collect(source, target) {
9
41
  if (target === undefined && source instanceof types_1.Table) {
10
- // with header line
11
- const read = (0, read_table_1.readTable)(source, { withKey: (i) => { return (0, compare_1.eq)(i.table_line, source.getHeader()); } });
12
- if (read.subrc === 4) {
13
- (0, insert_internal_1.insertInternal)({ table: source, data: source.getHeader() });
14
- }
42
+ target = source;
43
+ source = source.getHeader();
15
44
  }
16
- else if (target !== undefined) {
17
- const read = (0, read_table_1.readTable)(target, { withKey: (i) => { return (0, compare_1.eq)(i.table_line, source); } });
18
- if (read.subrc === 4) {
19
- (0, insert_internal_1.insertInternal)({ table: target, data: source });
20
- }
45
+ if (target === undefined) {
46
+ throw new Error("COLLECT, no target specified");
47
+ }
48
+ const sourceKeys = (0, primary_key_1.primaryKeyValues)(target, source);
49
+ const matches = (row) => {
50
+ const rowKeys = (0, primary_key_1.primaryKeyValues)(target, row);
51
+ return rowKeys.length === sourceKeys.length && rowKeys.every((key, index) => (0, compare_1.eq)(key, sourceKeys[index]));
52
+ };
53
+ const rows = target.array();
54
+ let index = rows.findIndex(matches);
55
+ if (index >= 0) {
56
+ sumNumeric(rows[index], source, new Set(sourceKeys));
57
+ abap.builtin.sy.get().subrc.set(0);
21
58
  }
22
59
  else {
23
- throw new Error("COLLECT, no target specified");
60
+ (0, insert_internal_1.insertInternal)({ table: target, data: source });
61
+ index = target.array().findIndex(matches);
24
62
  }
63
+ abap.builtin.sy.get().tabix.set(target instanceof types_1.HashedTable ? 0 : index + 1);
25
64
  }
26
65
  //# sourceMappingURL=collect.js.map
@@ -0,0 +1,23 @@
1
+ import { Context } from "../context";
2
+ import { FieldSymbol } from "../types";
3
+ import { ICharacter } from "../types/_character";
4
+ import { INumeric } from "../types/_numeric";
5
+ import { IGetDatasetOptions, IOpenDatasetOptions, IReadDatasetOptions, ITransferOptions } from "../dataset/dataset";
6
+ export declare class DatasetStatements {
7
+ private readonly context;
8
+ constructor(context: Context);
9
+ private host;
10
+ private opened;
11
+ openDataset(nameIn: ICharacter | FieldSymbol | string, options: IOpenDatasetOptions): Promise<void>;
12
+ closeDataset(nameIn: ICharacter | FieldSymbol | string): Promise<void>;
13
+ deleteDataset(nameIn: ICharacter | FieldSymbol | string): Promise<void>;
14
+ transfer(sourceIn: any, nameIn: ICharacter | FieldSymbol | string, options?: ITransferOptions): Promise<void>;
15
+ readDataset(nameIn: ICharacter | FieldSymbol | string, targetIn: any, options?: IReadDatasetOptions): Promise<void>;
16
+ private readLine;
17
+ private readBytes;
18
+ getDataset(nameIn: ICharacter | FieldSymbol | string, options: IGetDatasetOptions): Promise<void>;
19
+ setDataset(nameIn: ICharacter | FieldSymbol | string, options: {
20
+ position?: INumeric | FieldSymbol;
21
+ endOfFile?: boolean;
22
+ }): Promise<void>;
23
+ }
@@ -0,0 +1,343 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatasetStatements = void 0;
4
+ const throw_error_1 = require("../throw_error");
5
+ const types_1 = require("../types");
6
+ // OPEN / READ / TRANSFER / CLOSE / DELETE / GET / SET DATASET.
7
+ //
8
+ // The ABAP semantics live here, once: text lines, the byte layout of each
9
+ // mode, ACTUAL LENGTH, sy-subrc and the exceptions. A host supplies bytes
10
+ // only (DatasetHost), the way a DatabaseClient supplies rows, so that
11
+ // a sandboxed file system, an in-memory one in a browser and a test double
12
+ // all behave the same. Without a host every statement throws, as before.
13
+ // The host and the options are defined in ../dataset/dataset.ts, and an
14
+ // in-memory host is ../dataset/memory_dataset.ts.
15
+ // what the text mode reads per host call while it looks for the end of a line
16
+ const CHUNK = 64 * 1024;
17
+ function deref(value) {
18
+ if (value instanceof types_1.FieldSymbol) {
19
+ const pointer = value.getPointer();
20
+ if (pointer === undefined) {
21
+ throw new Error("GETWA_NOT_ASSIGNED");
22
+ }
23
+ return pointer;
24
+ }
25
+ return value;
26
+ }
27
+ function nameOf(name) {
28
+ const value = typeof name === "string" ? name : deref(name).get();
29
+ // a C field holding the name is padded; the name is not
30
+ return (value + "").replace(/ +$/, "");
31
+ }
32
+ function numberOf(value) {
33
+ return value === undefined ? undefined : Number(deref(value).get());
34
+ }
35
+ function setSubrc(value) {
36
+ abap.builtin.sy.get().subrc.set(value);
37
+ }
38
+ function isHex(target) {
39
+ return target instanceof types_1.Hex || target instanceof types_1.HexUInt8;
40
+ }
41
+ function isCharLike(target) {
42
+ return typeof target?.get === "function" && typeof target.get() === "string" && !isHex(target) && !(target instanceof types_1.XString);
43
+ }
44
+ function hexToBytes(hex) {
45
+ const out = new Uint8Array(Math.floor(hex.length / 2));
46
+ for (let i = 0; i < out.length; i++) {
47
+ out[i] = parseInt(hex.substr(i * 2, 2), 16);
48
+ }
49
+ return out;
50
+ }
51
+ function bytesToHex(bytes) {
52
+ let out = "";
53
+ for (const b of bytes) {
54
+ out += b.toString(16).toUpperCase().padStart(2, "0");
55
+ }
56
+ return out;
57
+ }
58
+ // a character-like field in BINARY MODE is its code units in the system code
59
+ // page, UTF-16LE on a Unicode system (measured: c(4) 'ab' is 6100 6200
60
+ // 2000 2000)
61
+ function utf16le(text) {
62
+ const out = new Uint8Array(text.length * 2);
63
+ for (let i = 0; i < text.length; i++) {
64
+ const code = text.charCodeAt(i);
65
+ out[i * 2] = code % 256;
66
+ out[i * 2 + 1] = Math.floor(code / 256);
67
+ }
68
+ return out;
69
+ }
70
+ function fromUtf16le(bytes) {
71
+ let out = "";
72
+ for (let i = 0; i + 1 < bytes.length; i += 2) {
73
+ out += globalThis.String.fromCharCode(bytes[i] + bytes[i + 1] * 256);
74
+ }
75
+ return out;
76
+ }
77
+ function concat(a, b) {
78
+ const out = new Uint8Array(a.length + b.length);
79
+ out.set(a, 0);
80
+ out.set(b, a.length);
81
+ return out;
82
+ }
83
+ class DatasetStatements {
84
+ context;
85
+ constructor(context) {
86
+ this.context = context;
87
+ }
88
+ host(statement) {
89
+ if (this.context.dataset === undefined) {
90
+ throw new Error(`${statement}, not supported: no dataset host installed (abap.context.dataset)`);
91
+ }
92
+ return this.context.dataset;
93
+ }
94
+ opened(name) {
95
+ const file = this.context.datasets[name];
96
+ if (file === undefined) {
97
+ (0, throw_error_1.throwError)("CX_SY_FILE_OPEN_MODE");
98
+ }
99
+ return file;
100
+ }
101
+ async openDataset(nameIn, options) {
102
+ const host = this.host("OPEN DATASET");
103
+ if (options.legacy === true || options.encoding === "NON-UNICODE") {
104
+ throw new Error("OPEN DATASET, LEGACY and NON-UNICODE modes not supported");
105
+ }
106
+ if (options.unsupported !== undefined && options.unsupported.length > 0) {
107
+ throw new Error(`OPEN DATASET, not supported: ${options.unsupported.join(", ")}`);
108
+ }
109
+ const name = nameOf(nameIn);
110
+ if (this.context.datasets[name] !== undefined) {
111
+ (0, throw_error_1.throwError)("CX_SY_FILE_OPEN");
112
+ }
113
+ const opened = await host.open(name, options.mode);
114
+ if (!("read" in opened)) {
115
+ if (options.message !== undefined) {
116
+ deref(options.message).set(opened.message);
117
+ }
118
+ setSubrc(8);
119
+ return;
120
+ }
121
+ let position = 0;
122
+ if (options.mode === "APPENDING") {
123
+ position = await opened.size();
124
+ }
125
+ const at = numberOf(options.position);
126
+ if (at !== undefined) {
127
+ position = at;
128
+ }
129
+ this.context.datasets[name] = { handle: opened, mode: options.mode, binary: options.binary, position };
130
+ setSubrc(0);
131
+ }
132
+ async closeDataset(nameIn) {
133
+ this.host("CLOSE DATASET");
134
+ const name = nameOf(nameIn);
135
+ const file = this.context.datasets[name];
136
+ // closing a file that is not open is no error (measured: sy-subrc 0)
137
+ if (file !== undefined) {
138
+ delete this.context.datasets[name];
139
+ await file.handle.close();
140
+ }
141
+ setSubrc(0);
142
+ }
143
+ async deleteDataset(nameIn) {
144
+ const host = this.host("DELETE DATASET");
145
+ const name = nameOf(nameIn);
146
+ // deleting an open file answers 0 and closes it: a READ after it raises
147
+ // CX_SY_FILE_OPEN_MODE (measured)
148
+ const file = this.context.datasets[name];
149
+ if (file !== undefined) {
150
+ delete this.context.datasets[name];
151
+ await file.handle.close();
152
+ }
153
+ setSubrc(await host.delete(name) ? 0 : 4);
154
+ }
155
+ async transfer(sourceIn, nameIn, options = {}) {
156
+ this.host("TRANSFER");
157
+ const file = this.opened(nameOf(nameIn));
158
+ if (file.mode === "INPUT") {
159
+ (0, throw_error_1.throwError)("CX_SY_FILE_OPEN_MODE");
160
+ }
161
+ const source = deref(sourceIn);
162
+ const length = numberOf(options.length);
163
+ let bytes;
164
+ if (file.binary === true) {
165
+ if (isHex(source) || source instanceof types_1.XString) {
166
+ bytes = hexToBytes(source.get());
167
+ }
168
+ else if (isCharLike(source)) {
169
+ let text = source.get();
170
+ if (source instanceof types_1.Character) {
171
+ text = text.padEnd(source.getLength(), " ");
172
+ }
173
+ bytes = utf16le(length === undefined ? text : text.substring(0, length));
174
+ if (length !== undefined) {
175
+ const want = length * 2;
176
+ bytes = bytes.length >= want ? bytes : concat(bytes, utf16le(" ".repeat((want - bytes.length) / 2)));
177
+ }
178
+ }
179
+ else {
180
+ throw new Error("TRANSFER, BINARY MODE supports byte-like and character-like fields only");
181
+ }
182
+ if (length !== undefined && (isHex(source) || source instanceof types_1.XString)) {
183
+ bytes = bytes.length >= length ? bytes.slice(0, length) : concat(bytes, new Uint8Array(length - bytes.length));
184
+ }
185
+ }
186
+ else {
187
+ if (!isCharLike(source)) {
188
+ throw new Error("TRANSFER, TEXT MODE is only supported for character-type data objects");
189
+ }
190
+ let text = source.get();
191
+ if (length !== undefined) {
192
+ if (source instanceof types_1.Character) {
193
+ text = text.padEnd(source.getLength(), " ");
194
+ }
195
+ text = text.substring(0, length);
196
+ }
197
+ else if (source instanceof types_1.Character) {
198
+ // measured: c(10) 'ab' is written as 'ab' and a line end;
199
+ // a string keeps its trailing blanks
200
+ text = source.getTrimEnd();
201
+ }
202
+ if (options.noEndOfLine !== true) {
203
+ text = text + "\n";
204
+ }
205
+ bytes = new TextEncoder().encode(text);
206
+ }
207
+ await file.handle.write(file.position, bytes);
208
+ file.position += bytes.length;
209
+ setSubrc(0);
210
+ }
211
+ async readDataset(nameIn, targetIn, options = {}) {
212
+ this.host("READ DATASET");
213
+ const file = this.opened(nameOf(nameIn));
214
+ const target = deref(targetIn);
215
+ const maximum = numberOf(options.maximumLength);
216
+ const setActual = (n) => {
217
+ if (options.actualLength !== undefined) {
218
+ deref(options.actualLength).set(n);
219
+ }
220
+ };
221
+ // a file opened for writing only answers 4 (measured, OUTPUT and APPENDING)
222
+ if (file.mode === "OUTPUT" || file.mode === "APPENDING") {
223
+ setActual(0);
224
+ setSubrc(4);
225
+ return;
226
+ }
227
+ if (file.binary === false) {
228
+ await this.readLine(file, target, maximum, setActual);
229
+ }
230
+ else {
231
+ await this.readBytes(file, target, maximum, setActual);
232
+ }
233
+ }
234
+ // TEXT MODE: one line per READ, without its LF (a CR before it stays,
235
+ // measured); the last line needs no LF; at the end sy-subrc 4 and the target
236
+ // cleared. ACTUAL LENGTH is the whole line even when the target is shorter,
237
+ // and the whole line is consumed either way.
238
+ async readLine(file, target, maximum, setActual) {
239
+ if (!isCharLike(target)) {
240
+ throw new Error("READ DATASET, the current statement is only supported for character-type data objects");
241
+ }
242
+ if (maximum !== undefined) {
243
+ throw new Error("READ DATASET, MAXIMUM LENGTH in TEXT MODE not supported");
244
+ }
245
+ let line = new Uint8Array(0);
246
+ let position = file.position;
247
+ let ended = false;
248
+ for (;;) {
249
+ const chunk = await file.handle.read(position, CHUNK);
250
+ if (chunk.length === 0) {
251
+ break;
252
+ }
253
+ const lf = chunk.indexOf(0x0A);
254
+ if (lf >= 0) {
255
+ line = concat(line, chunk.subarray(0, lf));
256
+ position += lf + 1;
257
+ ended = true;
258
+ break;
259
+ }
260
+ line = concat(line, chunk);
261
+ position += chunk.length;
262
+ }
263
+ if (ended === false && line.length === 0) {
264
+ target.clear();
265
+ setActual(0);
266
+ setSubrc(4);
267
+ return;
268
+ }
269
+ file.position = position;
270
+ const text = new TextDecoder("utf-8").decode(line);
271
+ target.set(text);
272
+ setActual(text.length);
273
+ setSubrc(0);
274
+ }
275
+ // BINARY MODE: a field of fixed length takes its length in bytes (a C
276
+ // field two per character, UTF-16LE), a string or xstring the rest of the
277
+ // file; MAXIMUM LENGTH caps the bytes; fewer bytes than asked is sy-subrc 4
278
+ // and ACTUAL LENGTH counts bytes (measured)
279
+ async readBytes(file, target, maximum, setActual) {
280
+ let want;
281
+ if (isHex(target)) {
282
+ want = target.getLength();
283
+ }
284
+ else if (target instanceof types_1.Character) {
285
+ want = target.getLength() * 2;
286
+ }
287
+ else if (target instanceof types_1.XString || target instanceof types_1.String) {
288
+ want = Math.max(0, await file.handle.size() - file.position);
289
+ }
290
+ else if (isCharLike(target) && typeof target.getLength === "function") {
291
+ want = target.getLength() * 2;
292
+ }
293
+ else {
294
+ throw new Error("READ DATASET, BINARY MODE supports byte-like and character-like fields only");
295
+ }
296
+ const variable = target instanceof types_1.XString || target instanceof types_1.String;
297
+ if (maximum !== undefined) {
298
+ want = Math.min(want, maximum);
299
+ }
300
+ const bytes = want === 0 ? new Uint8Array(0) : await file.handle.read(file.position, want);
301
+ file.position += bytes.length;
302
+ if (isHex(target)) {
303
+ target.set(bytesToHex(bytes).padEnd(target.getLength() * 2, "0"));
304
+ }
305
+ else if (target instanceof types_1.XString) {
306
+ target.set(bytesToHex(bytes));
307
+ }
308
+ else {
309
+ target.set(fromUtf16le(bytes));
310
+ }
311
+ setActual(bytes.length);
312
+ if (variable === true) {
313
+ setSubrc(bytes.length > 0 ? 0 : 4);
314
+ }
315
+ else {
316
+ setSubrc(bytes.length < want || want === 0 ? 4 : 0);
317
+ }
318
+ }
319
+ async getDataset(nameIn, options) {
320
+ this.host("GET DATASET");
321
+ const file = this.opened(nameOf(nameIn));
322
+ if (options.attributes !== undefined) {
323
+ throw new Error("GET DATASET, ATTRIBUTES not supported");
324
+ }
325
+ if (options.position !== undefined) {
326
+ deref(options.position).set(file.position);
327
+ }
328
+ setSubrc(0);
329
+ }
330
+ async setDataset(nameIn, options) {
331
+ this.host("SET DATASET");
332
+ const file = this.opened(nameOf(nameIn));
333
+ if (options.endOfFile === true) {
334
+ file.position = await file.handle.size();
335
+ }
336
+ else if (options.position !== undefined) {
337
+ file.position = numberOf(options.position) ?? 0;
338
+ }
339
+ setSubrc(0);
340
+ }
341
+ }
342
+ exports.DatasetStatements = DatasetStatements;
343
+ //# sourceMappingURL=dataset.js.map
@@ -1,2 +1,3 @@
1
+ import { Context } from "../context";
1
2
  import { INumeric } from "../types/_numeric";
2
- export declare function getRunTime(value: INumeric): void;
3
+ export declare function getRunTime(context: Context, value: INumeric): void;
@@ -1,16 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getRunTime = getRunTime;
4
- let prev = undefined;
5
- function getRunTime(value) {
6
- if (prev === undefined) {
7
- value.set(0);
8
- prev = new Date().getTime();
4
+ // milliseconds with a fraction, from a monotonic clock where there is one:
5
+ // performance.now() in Node, Bun and browsers, Date.now() otherwise
6
+ function now() {
7
+ if (typeof performance === "object" && typeof performance?.now === "function") {
8
+ return performance.now();
9
9
  }
10
- else {
11
- const now = new Date().getTime();
12
- value.set(now - prev);
13
- prev = now;
10
+ return Date.now();
11
+ }
12
+ // GET RUN TIME FIELD: microseconds since the first call in the internal
13
+ // session, which gives 0. The first call is per context, so per ABAP instance
14
+ function getRunTime(context, value) {
15
+ const t = now();
16
+ if (context.runTime === undefined) {
17
+ context.runTime = { start: t, last: 0 };
18
+ }
19
+ let micro = Math.floor((t - context.runTime.start) * 1000);
20
+ // never backwards, also when the clock is Date.now()
21
+ if (micro < context.runTime.last) {
22
+ micro = context.runTime.last;
14
23
  }
24
+ context.runTime.last = micro;
25
+ value.set(micro);
15
26
  }
16
27
  //# sourceMappingURL=get_run_time.js.map
@@ -1,4 +1,5 @@
1
1
  import { append } from "./append";
2
+ import { IGetDatasetOptions, IOpenDatasetOptions, IReadDatasetOptions, ITransferOptions } from "../dataset/dataset";
2
3
  import { assert } from "./assert";
3
4
  import { assign } from "./assign";
4
5
  import { ICommitOptions } from "./commit";
@@ -22,7 +23,6 @@ import { getLocale } from "./get_locale";
22
23
  import { unpack } from "./unpack";
23
24
  import { getParameter } from "./get_parameter";
24
25
  import { setLocale } from "./set_locale";
25
- import { getRunTime } from "./get_run_time";
26
26
  import { getTime } from "./get_time";
27
27
  import { IInsertDatabaseOptions } from "./insert_database";
28
28
  import { insertInternal } from "./insert_internal";
@@ -72,7 +72,6 @@ export declare class Statements {
72
72
  getBit: typeof getBit;
73
73
  getLocale: typeof getLocale;
74
74
  getParameter: typeof getParameter;
75
- getRunTime: typeof getRunTime;
76
75
  getTime: typeof getTime;
77
76
  insertInternal: typeof insertInternal;
78
77
  loop: typeof loop;
@@ -95,6 +94,7 @@ export declare class Statements {
95
94
  private readonly context;
96
95
  constructor(context: Context);
97
96
  _setTrace(min?: number, totals?: boolean): Trace;
97
+ getRunTime(value: INumeric): void;
98
98
  openCursor(target: INumeric, select: string, options: IOpenCursorDatabaseOptions): Promise<void>;
99
99
  fetchNextCursor(cursor: INumeric, target: any, packageSize?: INumeric): Promise<void>;
100
100
  closeCursor(cursor: INumeric): Promise<void>;
@@ -110,5 +110,15 @@ export declare class Statements {
110
110
  message(options: IMessageOptions): Promise<void>;
111
111
  readReport(name: ICharacter, options: Parameters<typeof readReport>[1]): Promise<void>;
112
112
  write(source: INumeric | ICharacter | FieldSymbol | string | number, options?: IWriteOptions): void;
113
+ openDataset(name: any, options: IOpenDatasetOptions): Promise<void>;
114
+ closeDataset(name: any): Promise<void>;
115
+ deleteDataset(name: any): Promise<void>;
116
+ transfer(source: any, name: any, options?: ITransferOptions): Promise<void>;
117
+ readDataset(name: any, target: any, options?: IReadDatasetOptions): Promise<void>;
118
+ getDataset(name: any, options: IGetDatasetOptions): Promise<void>;
119
+ setDataset(name: any, options: {
120
+ position?: any;
121
+ endOfFile?: boolean;
122
+ }): Promise<void>;
113
123
  skip(options?: ISkipOptions): void;
114
124
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Statements = void 0;
4
4
  const append_1 = require("./append");
5
+ const dataset_1 = require("./dataset");
5
6
  const assert_1 = require("./assert");
6
7
  const assign_1 = require("./assign");
7
8
  const commit_1 = require("./commit");
@@ -75,7 +76,6 @@ class Statements {
75
76
  getBit = get_bit_1.getBit;
76
77
  getLocale = get_locale_1.getLocale;
77
78
  getParameter = get_parameter_1.getParameter;
78
- getRunTime = get_run_time_1.getRunTime;
79
79
  getTime = get_time_1.getTime;
80
80
  insertInternal = insert_internal_1.insertInternal;
81
81
  loop = loop_1.loop;
@@ -102,6 +102,9 @@ class Statements {
102
102
  _setTrace(min = 10, totals = false) {
103
103
  return new trace_1.Trace().setTrace(min, totals, this);
104
104
  }
105
+ getRunTime(value) {
106
+ (0, get_run_time_1.getRunTime)(this.context, value);
107
+ }
105
108
  async openCursor(target, select, options) {
106
109
  const num = await (0, open_cursor_1.openCursor)(this.context, select, options);
107
110
  target.set(num);
@@ -148,6 +151,27 @@ class Statements {
148
151
  write(source, options) {
149
152
  return new write_1.WriteStatement(this.context).write(source, options);
150
153
  }
154
+ async openDataset(name, options) {
155
+ return new dataset_1.DatasetStatements(this.context).openDataset(name, options);
156
+ }
157
+ async closeDataset(name) {
158
+ return new dataset_1.DatasetStatements(this.context).closeDataset(name);
159
+ }
160
+ async deleteDataset(name) {
161
+ return new dataset_1.DatasetStatements(this.context).deleteDataset(name);
162
+ }
163
+ async transfer(source, name, options) {
164
+ return new dataset_1.DatasetStatements(this.context).transfer(source, name, options);
165
+ }
166
+ async readDataset(name, target, options) {
167
+ return new dataset_1.DatasetStatements(this.context).readDataset(name, target, options);
168
+ }
169
+ async getDataset(name, options) {
170
+ return new dataset_1.DatasetStatements(this.context).getDataset(name, options);
171
+ }
172
+ async setDataset(name, options) {
173
+ return new dataset_1.DatasetStatements(this.context).setDataset(name, options);
174
+ }
151
175
  skip(options) {
152
176
  return new skip_1.SkipStatement(this.context).skip(options);
153
177
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loop = loop;
4
4
  const binary_search_1 = require("../binary_search");
5
+ const compare_1 = require("../compare");
5
6
  const types_1 = require("../types");
6
7
  function determineFromTo(array, topEquals, key) {
7
8
  if (topEquals === undefined) {
@@ -27,6 +28,53 @@ function determineFromTo(array, topEquals, key) {
27
28
  to: to,
28
29
  };
29
30
  }
31
+ /** -1 / 0 / 1: the key field x sorts before / equal to / after the WHERE operand
32
+ * v - only for pairs whose order agrees with the table's sort (sort.ts compares
33
+ * the fields with lt/eq): the same type, of the same length for c, n and x, or
34
+ * a c operand against a string field, which eq compares by its getTrimEnd() */
35
+ function keyComparator(sample, value) {
36
+ if (value === null || typeof value !== "object" || sample === null || typeof sample !== "object") {
37
+ return undefined;
38
+ }
39
+ if (sample.constructor === value.constructor) {
40
+ const fixed = sample instanceof types_1.Character || sample instanceof types_1.Numc || sample instanceof types_1.Hex;
41
+ if (fixed && sample.getLength() !== value.getLength()) {
42
+ return undefined;
43
+ }
44
+ return (x, v) => ((0, compare_1.eq)(x, v) ? 0 : (0, compare_1.lt)(x, v) ? -1 : 1);
45
+ }
46
+ if (sample instanceof types_1.String && value instanceof types_1.Character) {
47
+ return (x, v) => {
48
+ const s = v.getTrimEnd();
49
+ const xs = x.get();
50
+ return xs === s ? 0 : xs < s ? -1 : 1;
51
+ };
52
+ }
53
+ return undefined;
54
+ }
55
+ /** A SORTED primary key, and a WHERE that requires its first field to equal a
56
+ * value: the rows the WHERE can accept are one block of the array. Relies on
57
+ * topEquals holding only conditions every row has to meet - see the
58
+ * transpiler's LoopTranspiler, which emits it for a conjunction only */
59
+ function sortedPrimaryBlock(table, options) {
60
+ const primary = table.getOptions()?.primaryKey;
61
+ if (!(table instanceof types_1.Table) || primary?.type !== types_1.TableAccessType.sorted
62
+ || !primary.keyFields?.length || options?.topEquals === undefined) {
63
+ return undefined;
64
+ }
65
+ const field = primary.keyFields[0].toLowerCase();
66
+ const value = options.topEquals[field];
67
+ const rowType = table.getRowType();
68
+ const structured = rowType instanceof types_1.Structure;
69
+ if (value === undefined || structured === (field === "table_line")) {
70
+ return undefined;
71
+ }
72
+ const compare = keyComparator(structured ? rowType.get()[field] : rowType, value);
73
+ if (compare === undefined) {
74
+ return undefined;
75
+ }
76
+ return structured ? (row) => compare(row.get()[field], value) : (row) => compare(row, value);
77
+ }
30
78
  // todo: rewrite, this is a mess & hack & slow
31
79
  function dynamicToWhere(condition, evaluate) {
32
80
  // console.dir(condition);
@@ -103,16 +151,36 @@ async function* loop(table, options) {
103
151
  let loopFrom = options?.from && options?.from.get() > 0 ? options.from.get() - 1 : 0;
104
152
  let loopTo = options?.to && options.to.get() < length ? options.to.get() : length;
105
153
  let array = [];
106
- // a dynamic key name may come in any case, USING KEY ('PRIMARY_KEY') is the primary key too
107
- const isPrimaryKey = options?.usingKey === undefined || options.usingKey.toLowerCase() === "primary_key";
108
- if (options?.usingKey && isPrimaryKey === false) {
109
- array = table.getSecondaryIndex(options.usingKey);
110
- const { from, to } = determineFromTo(array, options.topEquals, table.getKeyByName(options.usingKey));
154
+ let block = undefined;
155
+ // the secondary key the loop runs over; undefined is the primary key, also
156
+ // when it is named - USING KEY PRIMARY_KEY, or a dynamic name holding it
157
+ const usingKey = (0, types_1.secondaryKeyName)(options?.usingKey);
158
+ if (usingKey !== undefined) {
159
+ array = table.getSecondaryIndex(usingKey);
160
+ const { from, to } = determineFromTo(array, options?.topEquals, table.getKeyByName(usingKey));
111
161
  loopFrom = Math.max(loopFrom, from) - 1;
112
162
  loopTo = Math.min(loopTo, to);
113
163
  }
114
164
  else {
115
165
  array = table.array();
166
+ if (options?.where !== undefined && options.from === undefined && options.to === undefined) {
167
+ block = sortedPrimaryBlock(table, options);
168
+ }
169
+ if (block !== undefined) {
170
+ // the first row that does not sort before the value
171
+ let lo = 0;
172
+ let hi = array.length;
173
+ while (lo < hi) {
174
+ const mid = Math.floor((lo + hi) / 2);
175
+ if (block(array[mid]) < 0) {
176
+ lo = mid + 1;
177
+ }
178
+ else {
179
+ hi = mid;
180
+ }
181
+ }
182
+ loopFrom = lo;
183
+ }
116
184
  }
117
185
  const loopController = table.startLoop(loopFrom, loopTo, array);
118
186
  let entered = false;
@@ -128,9 +196,7 @@ async function* loop(table, options) {
128
196
  // The same goes for a loop that reads an index table through a hash
129
197
  // secondary key. Handing out 1, 2, 3 there looks helpful and is a lie the
130
198
  // caller cannot tell from the truth.
131
- const usedKey = options?.usingKey === undefined || isPrimaryKey
132
- ? undefined
133
- : table.getKeyByName(options.usingKey);
199
+ const usedKey = usingKey === undefined ? undefined : table.getKeyByName(usingKey);
134
200
  const hasRowNumber = usedKey !== undefined
135
201
  ? usedKey.type !== types_1.TableAccessType.hashed
136
202
  : !(table instanceof types_1.HashedTable);
@@ -143,6 +209,10 @@ async function* loop(table, options) {
143
209
  break;
144
210
  }
145
211
  const current = array[loopController.index];
212
+ if (block !== undefined && block(current) > 0) {
213
+ // this row sorts after the value, and so does every row behind it
214
+ break;
215
+ }
146
216
  if (options?.where) {
147
217
  const row = isStructured ? current.get() : { table_line: current };
148
218
  if (await options.where(row) === false) {
@@ -155,7 +225,7 @@ async function* loop(table, options) {
155
225
  options?.atLast?.(loopController.index + 1 >= loopController.loopTo);
156
226
  yield current;
157
227
  loopController.index++;
158
- if (options?.to === undefined && options?.usingKey === undefined) {
228
+ if (options?.to === undefined && usingKey === undefined) {
159
229
  // extra rows might have been inserted inside the loop
160
230
  loopController.loopTo = array.length;
161
231
  }
@@ -169,6 +169,11 @@ function readTable(table, options) {
169
169
  // return readTable(table.getPointer(), options);
170
170
  table = table.getPointer();
171
171
  }
172
+ // WITH [TABLE] KEY primary_key COMPONENTS reads as the same READ without
173
+ // the key name; any other name, as ABAP reads it (secondaryKeyName)
174
+ if (options?.keyName !== undefined && (0, types_1.secondaryKeyName)(options.keyName) !== options.keyName) {
175
+ options = { ...options, keyName: (0, types_1.secondaryKeyName)(options.keyName) };
176
+ }
172
177
  // check if it is a primary index read specified with WITH KEY instead of WITH TABLE KEY
173
178
  if (options?.withTableKey === undefined
174
179
  && options?.withKeySimple
@@ -45,6 +45,11 @@ interface ITable {
45
45
  export declare class TableFactory {
46
46
  static construct(rowType: TableRowType, options?: ITableOptions, qualifiedName?: string): Table | HashedTable;
47
47
  }
48
+ /** The secondary key a key name selects, as ABAP reads the name: without the
49
+ * blanks a c field pads it with, in any case - and undefined for the primary
50
+ * key, which primary_key names however it is written, and so does an initial
51
+ * name (a dynamic USING KEY ( name ) that is empty). */
52
+ export declare function secondaryKeyName(name: string | undefined): string | undefined;
48
53
  export declare class HashedTable implements ITable {
49
54
  private value;
50
55
  private readonly header;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Table = exports.HashedTable = exports.TableFactory = exports.LoopController = exports.TableKeyType = exports.TableAccessType = void 0;
4
+ exports.secondaryKeyName = secondaryKeyName;
4
5
  const integer_1 = require("./integer");
5
6
  const float_1 = require("./float");
6
7
  const abap_object_1 = require("./abap_object");
@@ -75,6 +76,14 @@ export class SortedTable {
75
76
  // todo
76
77
  }
77
78
  */
79
+ /** The secondary key a key name selects, as ABAP reads the name: without the
80
+ * blanks a c field pads it with, in any case - and undefined for the primary
81
+ * key, which primary_key names however it is written, and so does an initial
82
+ * name (a dynamic USING KEY ( name ) that is empty). */
83
+ function secondaryKeyName(name) {
84
+ const trimmed = name?.trimEnd();
85
+ return trimmed === undefined || trimmed === "" || trimmed.toLowerCase() === "primary_key" ? undefined : trimmed;
86
+ }
78
87
  class HashedTable {
79
88
  value;
80
89
  header;
@@ -107,11 +116,11 @@ class HashedTable {
107
116
  return Object.keys(this.value).length;
108
117
  }
109
118
  getKeyByName(name) {
110
- return this.getOptions()?.secondary?.find(s => s.name.toUpperCase() === name.toUpperCase());
119
+ return this.getOptions()?.secondary?.find(s => s.name.toUpperCase() === name.trimEnd().toUpperCase());
111
120
  }
112
121
  getSecondaryIndex(name) {
113
- if (this.secondaryIndexes[name.toUpperCase()]) {
114
- return this.secondaryIndexes[name.toUpperCase()];
122
+ if (this.secondaryIndexes[name.trimEnd().toUpperCase()]) {
123
+ return this.secondaryIndexes[name.trimEnd().toUpperCase()];
115
124
  }
116
125
  const secondary = this.getKeyByName(name);
117
126
  if (secondary === undefined) {
@@ -120,7 +129,7 @@ class HashedTable {
120
129
  // note, array() already is a copy, so it can be used,
121
130
  const copy = secondaryDuplicatesFirst(this.array(), secondary);
122
131
  (0, sort_1.sort)(copy, { by: secondary.keyFields.map(k => { return { component: k.toLowerCase() }; }) });
123
- this.secondaryIndexes[name.toUpperCase()] = copy;
132
+ this.secondaryIndexes[name.trimEnd().toUpperCase()] = copy;
124
133
  return copy;
125
134
  }
126
135
  buildHashFromData(data) {
@@ -364,11 +373,11 @@ class Table {
364
373
  return this.value.length;
365
374
  }
366
375
  getKeyByName(name) {
367
- return this.getOptions()?.secondary?.find(s => s.name.toUpperCase() === name.toUpperCase());
376
+ return this.getOptions()?.secondary?.find(s => s.name.toUpperCase() === name.trimEnd().toUpperCase());
368
377
  }
369
378
  getSecondaryIndex(name) {
370
- if (this.secondaryIndexes[name.toUpperCase()]) {
371
- return this.secondaryIndexes[name.toUpperCase()];
379
+ if (this.secondaryIndexes[name.trimEnd().toUpperCase()]) {
380
+ return this.secondaryIndexes[name.trimEnd().toUpperCase()];
372
381
  }
373
382
  const secondary = this.getKeyByName(name);
374
383
  if (secondary === undefined) {
@@ -376,7 +385,7 @@ class Table {
376
385
  }
377
386
  const copy = secondaryDuplicatesFirst([...this.value], secondary);
378
387
  (0, sort_1.sort)(copy, { by: secondary.keyFields.map(k => { return { component: k.toLowerCase() }; }), skipSortedCheck: true });
379
- this.secondaryIndexes[name.toUpperCase()] = copy;
388
+ this.secondaryIndexes[name.trimEnd().toUpperCase()] = copy;
380
389
  return copy;
381
390
  }
382
391
  getQualifiedName() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/runtime",
3
- "version": "2.13.95",
3
+ "version": "2.13.97",
4
4
  "description": "Transpiler - Runtime",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/src/index.d.ts",