@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.
- package/build/src/context.d.ts +15 -0
- package/build/src/context.js +8 -0
- package/build/src/dataset/dataset.d.ts +51 -0
- package/build/src/dataset/dataset.js +3 -0
- package/build/src/dataset/memory_dataset.d.ts +17 -0
- package/build/src/dataset/memory_dataset.js +46 -0
- package/build/src/index.d.ts +4 -0
- package/build/src/index.js +4 -1
- package/build/src/operators/divide.d.ts +3 -0
- package/build/src/operators/divide.js +19 -0
- package/build/src/primary_key.d.ts +2 -2
- package/build/src/statements/call_function.d.ts +1 -0
- package/build/src/statements/call_function.js +27 -2
- package/build/src/statements/collect.d.ts +2 -2
- package/build/src/statements/collect.js +51 -12
- package/build/src/statements/dataset.d.ts +23 -0
- package/build/src/statements/dataset.js +343 -0
- package/build/src/statements/get_run_time.d.ts +2 -1
- package/build/src/statements/get_run_time.js +20 -9
- package/build/src/statements/index.d.ts +12 -2
- package/build/src/statements/index.js +25 -1
- package/build/src/statements/loop.js +79 -9
- package/build/src/statements/read_table.js +5 -0
- package/build/src/types/table.d.ts +5 -0
- package/build/src/types/table.js +17 -8
- package/package.json +1 -1
package/build/src/context.d.ts
CHANGED
|
@@ -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
|
}
|
package/build/src/context.js
CHANGED
|
@@ -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,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
|
package/build/src/index.d.ts
CHANGED
|
@@ -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: {
|
package/build/src/index.js
CHANGED
|
@@ -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[];
|
|
@@ -9,7 +9,7 @@ class CallFunction {
|
|
|
9
9
|
constructor(context) {
|
|
10
10
|
this.context = context;
|
|
11
11
|
}
|
|
12
|
-
//
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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,16 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getRunTime = getRunTime;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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 =
|
|
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 &&
|
|
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;
|
package/build/src/types/table.js
CHANGED
|
@@ -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() {
|