@abaplint/runtime 2.13.93 → 2.13.95
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/builtin/_extremum.d.ts +9 -0
- package/build/src/builtin/_extremum.js +75 -0
- package/build/src/builtin/find.js +33 -2
- package/build/src/builtin/nmax.d.ts +1 -2
- package/build/src/builtin/nmax.js +3 -28
- package/build/src/builtin/nmin.d.ts +1 -2
- package/build/src/builtin/nmin.js +3 -28
- package/build/src/builtin/round.d.ts +14 -5
- package/build/src/builtin/round.js +69 -21
- package/build/src/compare/cp.js +42 -29
- package/build/src/compare/eq.js +10 -0
- package/build/src/compare/in.d.ts +3 -1
- package/build/src/compare/in.js +35 -18
- package/build/src/statements/concatenate.js +4 -2
- package/build/src/statements/create_data.js +14 -2
- package/build/src/statements/loop.js +13 -5
- package/build/src/statements/read_table.js +93 -7
- package/build/src/statements/update_database.js +14 -0
- package/build/src/template_formatting.js +18 -4
- package/build/src/types/table.d.ts +1 -1
- package/build/src/types/table.js +27 -7
- package/package.json +3 -3
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DecFloat34, Float, Integer, Integer8, Packed } from "../types";
|
|
2
|
+
import { INumeric } from "../types/_numeric";
|
|
3
|
+
export type ExtremumArgument = number | INumeric | undefined;
|
|
4
|
+
/** nmax( ) and nmin( ): the result has the calculation type of the arguments,
|
|
5
|
+
* as an arithmetic expression would determine it - decfloat34 before f before
|
|
6
|
+
* p before int8 before i. So nmin( val1 = i1 val2 = i2 ) is an i, which DO
|
|
7
|
+
* nmin( ... ) TIMES and every other integer position rely on, and a p result
|
|
8
|
+
* keeps the most decimals of its arguments. */
|
|
9
|
+
export declare function extremum(args: ExtremumArgument[], isBetter: (candidate: number, best: number) => boolean): Float | Integer8 | Integer | DecFloat34 | Packed;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extremum = extremum;
|
|
4
|
+
const _parse_1 = require("../operators/_parse");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
// the calculation types in the order an arithmetic expression ranks them
|
|
7
|
+
const INT = 0;
|
|
8
|
+
const INT8 = 1;
|
|
9
|
+
const PACKED = 2;
|
|
10
|
+
const FLOAT = 3;
|
|
11
|
+
const DECFLOAT = 4;
|
|
12
|
+
/** nmax( ) and nmin( ): the result has the calculation type of the arguments,
|
|
13
|
+
* as an arithmetic expression would determine it - decfloat34 before f before
|
|
14
|
+
* p before int8 before i. So nmin( val1 = i1 val2 = i2 ) is an i, which DO
|
|
15
|
+
* nmin( ... ) TIMES and every other integer position rely on, and a p result
|
|
16
|
+
* keeps the most decimals of its arguments. */
|
|
17
|
+
function extremum(args, isBetter) {
|
|
18
|
+
let best = undefined;
|
|
19
|
+
let bestValue = 0;
|
|
20
|
+
let type = INT;
|
|
21
|
+
let decimals = 0;
|
|
22
|
+
for (let arg of args) {
|
|
23
|
+
if (arg === undefined) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (arg instanceof types_1.FieldSymbol) {
|
|
27
|
+
arg = arg.getPointer();
|
|
28
|
+
}
|
|
29
|
+
if (arg instanceof types_1.DecFloat34) {
|
|
30
|
+
type = Math.max(type, DECFLOAT);
|
|
31
|
+
}
|
|
32
|
+
else if (arg instanceof types_1.Float) {
|
|
33
|
+
type = Math.max(type, FLOAT);
|
|
34
|
+
}
|
|
35
|
+
else if (arg instanceof types_1.Packed) {
|
|
36
|
+
type = Math.max(type, PACKED);
|
|
37
|
+
decimals = Math.max(decimals, arg.getDecimals());
|
|
38
|
+
}
|
|
39
|
+
else if (arg instanceof types_1.Integer8) {
|
|
40
|
+
type = Math.max(type, INT8);
|
|
41
|
+
}
|
|
42
|
+
else if (arg instanceof types_1.Integer || (typeof arg === "number" && Number.isInteger(arg))) {
|
|
43
|
+
// i, the lowest type
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// character-like and other arguments, as before: f
|
|
47
|
+
type = Math.max(type, FLOAT);
|
|
48
|
+
}
|
|
49
|
+
const value = (0, _parse_1.parse)(arg);
|
|
50
|
+
if (best === undefined || isBetter(value, bestValue)) {
|
|
51
|
+
best = arg;
|
|
52
|
+
bestValue = value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
switch (type) {
|
|
56
|
+
case DECFLOAT:
|
|
57
|
+
return new types_1.DecFloat34().set(bestValue);
|
|
58
|
+
case FLOAT:
|
|
59
|
+
return new types_1.Float().set(bestValue);
|
|
60
|
+
case PACKED:
|
|
61
|
+
if (best instanceof types_1.Packed) {
|
|
62
|
+
// through the digits, not through a binary float
|
|
63
|
+
return new types_1.Packed({ length: 16, decimals }).set(best.toFixed(best.getDecimals()));
|
|
64
|
+
}
|
|
65
|
+
return new types_1.Packed({ length: 16, decimals }).set(bestValue);
|
|
66
|
+
case INT8:
|
|
67
|
+
if (best instanceof types_1.Integer8) {
|
|
68
|
+
return new types_1.Integer8().set(best.get());
|
|
69
|
+
}
|
|
70
|
+
return new types_1.Integer8().set(bestValue);
|
|
71
|
+
default:
|
|
72
|
+
return new types_1.Integer().set(bestValue);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=_extremum.js.map
|
|
@@ -32,7 +32,7 @@ function find(input) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
|
-
|
|
35
|
+
let sub = typeof input.sub === "string" ? input.sub : input.sub?.get() || "";
|
|
36
36
|
let off = (0, _position_1.position)(input.off) || 0;
|
|
37
37
|
let occ = (0, _position_1.position)(input.occ);
|
|
38
38
|
if (occ === 0) {
|
|
@@ -41,6 +41,26 @@ function find(input) {
|
|
|
41
41
|
else if (occ === undefined) {
|
|
42
42
|
occ = 1;
|
|
43
43
|
}
|
|
44
|
+
const caseInput = typeof input.case === "string" ? input.case : input.case?.get();
|
|
45
|
+
if (caseInput !== undefined && caseInput !== "X") {
|
|
46
|
+
val = foldCase(val);
|
|
47
|
+
sub = foldCase(sub);
|
|
48
|
+
}
|
|
49
|
+
if (occ < 0 && sub !== "" && off === 0) {
|
|
50
|
+
// search from the right, on the string as it is, so a "sub" of any length
|
|
51
|
+
// matches and the offset needs no mapping back
|
|
52
|
+
let from = val.length - sub.length;
|
|
53
|
+
let found = -1;
|
|
54
|
+
for (let i = 0; i < Math.abs(occ); i++) {
|
|
55
|
+
found = from < 0 ? -1 : val.lastIndexOf(sub, from);
|
|
56
|
+
if (found < 0) {
|
|
57
|
+
return new types_1.Integer().set(-1);
|
|
58
|
+
}
|
|
59
|
+
from = found - 1;
|
|
60
|
+
}
|
|
61
|
+
return new types_1.Integer().set(found);
|
|
62
|
+
}
|
|
63
|
+
// a negative "occ" with an empty "sub" or an "off" still comes here, unchanged
|
|
44
64
|
let negative = false;
|
|
45
65
|
if (occ < 0) {
|
|
46
66
|
negative = true;
|
|
@@ -54,7 +74,7 @@ function find(input) {
|
|
|
54
74
|
}
|
|
55
75
|
let found = -1;
|
|
56
76
|
for (let i = 0; i < occ; i++) {
|
|
57
|
-
found = val.indexOf(sub
|
|
77
|
+
found = val.indexOf(sub, off);
|
|
58
78
|
if (found >= 0) {
|
|
59
79
|
off = found + 1;
|
|
60
80
|
}
|
|
@@ -65,4 +85,15 @@ function find(input) {
|
|
|
65
85
|
return new types_1.Integer().set(found);
|
|
66
86
|
}
|
|
67
87
|
}
|
|
88
|
+
// lower case one character at a time and keep a character whose lower case form
|
|
89
|
+
// has another length, so every offset stays valid - a plain toLowerCase( )
|
|
90
|
+
// turns e.g. "\u0130" into two code units
|
|
91
|
+
function foldCase(str) {
|
|
92
|
+
let ret = "";
|
|
93
|
+
for (const character of str) {
|
|
94
|
+
const lower = character.toLowerCase();
|
|
95
|
+
ret += lower.length === character.length ? lower : character;
|
|
96
|
+
}
|
|
97
|
+
return ret;
|
|
98
|
+
}
|
|
68
99
|
//# sourceMappingURL=find.js.map
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { INumeric } from "../types/_numeric";
|
|
2
|
-
import { Float } from "../types";
|
|
3
2
|
export interface INmaxInput {
|
|
4
3
|
val1: number | INumeric;
|
|
5
4
|
val2: number | INumeric;
|
|
@@ -11,4 +10,4 @@ export interface INmaxInput {
|
|
|
11
10
|
val8?: number | INumeric;
|
|
12
11
|
val9?: number | INumeric;
|
|
13
12
|
}
|
|
14
|
-
export declare function nmax(input: INmaxInput): Float;
|
|
13
|
+
export declare function nmax(input: INmaxInput): import("../types").Float | import("../types").Integer8 | import("../types").Integer | import("../types").DecFloat34 | import("../types").Packed;
|
|
@@ -1,34 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.nmax = nmax;
|
|
4
|
-
const
|
|
5
|
-
const types_1 = require("../types");
|
|
4
|
+
const _extremum_1 = require("./_extremum");
|
|
6
5
|
function nmax(input) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
values.push((0, _parse_1.parse)(input.val2));
|
|
10
|
-
if (input.val3) {
|
|
11
|
-
values.push((0, _parse_1.parse)(input.val3));
|
|
12
|
-
}
|
|
13
|
-
if (input.val4) {
|
|
14
|
-
values.push((0, _parse_1.parse)(input.val4));
|
|
15
|
-
}
|
|
16
|
-
if (input.val5) {
|
|
17
|
-
values.push((0, _parse_1.parse)(input.val5));
|
|
18
|
-
}
|
|
19
|
-
if (input.val6) {
|
|
20
|
-
values.push((0, _parse_1.parse)(input.val6));
|
|
21
|
-
}
|
|
22
|
-
if (input.val7) {
|
|
23
|
-
values.push((0, _parse_1.parse)(input.val7));
|
|
24
|
-
}
|
|
25
|
-
if (input.val8) {
|
|
26
|
-
values.push((0, _parse_1.parse)(input.val8));
|
|
27
|
-
}
|
|
28
|
-
if (input.val9) {
|
|
29
|
-
values.push((0, _parse_1.parse)(input.val9));
|
|
30
|
-
}
|
|
31
|
-
values.sort((a, b) => (b - a));
|
|
32
|
-
return new types_1.Float().set(values[0]);
|
|
6
|
+
return (0, _extremum_1.extremum)([input.val1, input.val2, input.val3, input.val4, input.val5,
|
|
7
|
+
input.val6, input.val7, input.val8, input.val9], (candidate, best) => candidate > best);
|
|
33
8
|
}
|
|
34
9
|
//# sourceMappingURL=nmax.js.map
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { INumeric } from "../types/_numeric";
|
|
2
|
-
import { Float } from "../types";
|
|
3
2
|
export interface INminInput {
|
|
4
3
|
val1: number | INumeric;
|
|
5
4
|
val2: number | INumeric;
|
|
@@ -11,4 +10,4 @@ export interface INminInput {
|
|
|
11
10
|
val8?: number | INumeric;
|
|
12
11
|
val9?: number | INumeric;
|
|
13
12
|
}
|
|
14
|
-
export declare function nmin(input: INminInput): Float;
|
|
13
|
+
export declare function nmin(input: INminInput): import("../types").Float | import("../types").Integer8 | import("../types").Integer | import("../types").DecFloat34 | import("../types").Packed;
|
|
@@ -1,34 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.nmin = nmin;
|
|
4
|
-
const
|
|
5
|
-
const types_1 = require("../types");
|
|
4
|
+
const _extremum_1 = require("./_extremum");
|
|
6
5
|
function nmin(input) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
values.push((0, _parse_1.parse)(input.val2));
|
|
10
|
-
if (input.val3) {
|
|
11
|
-
values.push((0, _parse_1.parse)(input.val3));
|
|
12
|
-
}
|
|
13
|
-
if (input.val4) {
|
|
14
|
-
values.push((0, _parse_1.parse)(input.val4));
|
|
15
|
-
}
|
|
16
|
-
if (input.val5) {
|
|
17
|
-
values.push((0, _parse_1.parse)(input.val5));
|
|
18
|
-
}
|
|
19
|
-
if (input.val6) {
|
|
20
|
-
values.push((0, _parse_1.parse)(input.val6));
|
|
21
|
-
}
|
|
22
|
-
if (input.val7) {
|
|
23
|
-
values.push((0, _parse_1.parse)(input.val7));
|
|
24
|
-
}
|
|
25
|
-
if (input.val8) {
|
|
26
|
-
values.push((0, _parse_1.parse)(input.val8));
|
|
27
|
-
}
|
|
28
|
-
if (input.val9) {
|
|
29
|
-
values.push((0, _parse_1.parse)(input.val9));
|
|
30
|
-
}
|
|
31
|
-
values.sort((a, b) => (a - b));
|
|
32
|
-
return new types_1.Float().set(values[0]);
|
|
6
|
+
return (0, _extremum_1.extremum)([input.val1, input.val2, input.val3, input.val4, input.val5,
|
|
7
|
+
input.val6, input.val7, input.val8, input.val9], (candidate, best) => candidate < best);
|
|
33
8
|
}
|
|
34
9
|
//# sourceMappingURL=nmin.js.map
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DecFloat34 } from "../types";
|
|
2
2
|
import { ICharacter } from "../types/_character";
|
|
3
3
|
import { INumeric } from "../types/_numeric";
|
|
4
|
+
type Input = INumeric | ICharacter | string | number;
|
|
5
|
+
/** round( val = arg {dec = n}|{prec = n} [mode = m] )
|
|
6
|
+
*
|
|
7
|
+
* The argument is converted to decfloat34 and rounded as a decimal number, so
|
|
8
|
+
* round( val = '2.675' dec = 2 ) is 2.68 - not 2.67, which is what scaling the
|
|
9
|
+
* nearest binary float would give. A binary float argument brings its full
|
|
10
|
+
* binary expansion along, the way converting f to decfloat34 does. */
|
|
4
11
|
export declare function round(input: {
|
|
5
|
-
val:
|
|
6
|
-
dec
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
val: Input;
|
|
13
|
+
dec?: Input;
|
|
14
|
+
prec?: Input;
|
|
15
|
+
mode?: Input;
|
|
16
|
+
}): DecFloat34;
|
|
17
|
+
export {};
|
|
@@ -3,39 +3,87 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.round = round;
|
|
4
4
|
const _parse_1 = require("../operators/_parse");
|
|
5
5
|
const types_1 = require("../types");
|
|
6
|
+
// the rounding modes of CL_ABAP_MATH, the decNumber enumeration
|
|
7
|
+
const ROUND_CEILING = 0;
|
|
8
|
+
const ROUND_UP = 1;
|
|
9
|
+
const ROUND_HALF_UP = 2;
|
|
10
|
+
const ROUND_HALF_EVEN = 3;
|
|
11
|
+
const ROUND_HALF_DOWN = 4;
|
|
12
|
+
const ROUND_DOWN = 5;
|
|
13
|
+
const ROUND_FLOOR = 6;
|
|
14
|
+
/** round( val = arg {dec = n}|{prec = n} [mode = m] )
|
|
15
|
+
*
|
|
16
|
+
* The argument is converted to decfloat34 and rounded as a decimal number, so
|
|
17
|
+
* round( val = '2.675' dec = 2 ) is 2.68 - not 2.67, which is what scaling the
|
|
18
|
+
* nearest binary float would give. A binary float argument brings its full
|
|
19
|
+
* binary expansion along, the way converting f to decfloat34 does. */
|
|
6
20
|
function round(input) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
21
|
+
const mode = input.mode === undefined ? ROUND_HALF_UP : (0, _parse_1.parse)(input.mode);
|
|
22
|
+
let arg = input.val;
|
|
23
|
+
if (arg instanceof types_1.FieldSymbol) {
|
|
24
|
+
arg = arg.getPointer();
|
|
10
25
|
}
|
|
11
|
-
|
|
12
|
-
|
|
26
|
+
const val = (0, _parse_1.parse)(arg);
|
|
27
|
+
const text = arg instanceof types_1.Float ? val.toPrecision(34) : String(val);
|
|
28
|
+
const match = /^(-?)(\d*)\.?(\d*)(?:e([+-]?\d+))?$/i.exec(text);
|
|
29
|
+
if (match === null || !Number.isFinite(val)) {
|
|
30
|
+
return new types_1.DecFloat34().set(val);
|
|
13
31
|
}
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
const negative = match[1] === "-";
|
|
33
|
+
const digits = BigInt((match[2] + match[3]).replace(/^0+/, "") || "0");
|
|
34
|
+
// the value is digits * 10^exponent
|
|
35
|
+
const exponent = parseInt(match[4] || "0", 10) - match[3].length;
|
|
36
|
+
let dec;
|
|
37
|
+
if (input.prec !== undefined) {
|
|
38
|
+
const prec = (0, _parse_1.parse)(input.prec);
|
|
39
|
+
if (prec <= 0) {
|
|
40
|
+
throw new Error("CX_SY_ARITHMETIC_ERROR, round( ) prec must be positive");
|
|
41
|
+
}
|
|
42
|
+
// position of the most significant digit
|
|
43
|
+
const top = digits.toString().length - 1 + exponent;
|
|
44
|
+
dec = prec - 1 - top;
|
|
18
45
|
}
|
|
19
|
-
|
|
46
|
+
else {
|
|
47
|
+
dec = input.dec === undefined ? 0 : (0, _parse_1.parse)(input.dec);
|
|
48
|
+
}
|
|
49
|
+
const drop = -exponent - dec;
|
|
50
|
+
if (drop <= 0 || digits === 0n) {
|
|
51
|
+
return new types_1.DecFloat34().set(val);
|
|
52
|
+
}
|
|
53
|
+
const divisor = 10n ** BigInt(drop);
|
|
54
|
+
let kept = digits / divisor;
|
|
55
|
+
const rest = digits % divisor;
|
|
56
|
+
// compare the dropped part with one half of the last kept digit
|
|
57
|
+
const half = rest * 2n === divisor ? 0 : (rest * 2n > divisor ? 1 : -1);
|
|
58
|
+
let away;
|
|
20
59
|
switch (mode) {
|
|
21
|
-
case
|
|
22
|
-
|
|
60
|
+
case ROUND_CEILING:
|
|
61
|
+
away = rest > 0n && negative === false;
|
|
62
|
+
break;
|
|
63
|
+
case ROUND_UP:
|
|
64
|
+
away = rest > 0n;
|
|
23
65
|
break;
|
|
24
|
-
case
|
|
25
|
-
|
|
66
|
+
case ROUND_HALF_UP:
|
|
67
|
+
away = half >= 0;
|
|
26
68
|
break;
|
|
27
|
-
case
|
|
28
|
-
|
|
69
|
+
case ROUND_HALF_EVEN:
|
|
70
|
+
away = half > 0 || (half === 0 && kept % 2n === 1n);
|
|
29
71
|
break;
|
|
30
|
-
case
|
|
31
|
-
|
|
72
|
+
case ROUND_HALF_DOWN:
|
|
73
|
+
away = half > 0;
|
|
32
74
|
break;
|
|
33
|
-
case
|
|
34
|
-
|
|
75
|
+
case ROUND_DOWN:
|
|
76
|
+
away = false;
|
|
77
|
+
break;
|
|
78
|
+
case ROUND_FLOOR:
|
|
79
|
+
away = rest > 0n && negative === true;
|
|
35
80
|
break;
|
|
36
81
|
default:
|
|
37
82
|
throw new Error("round(), unknown mode: " + mode);
|
|
38
83
|
}
|
|
39
|
-
|
|
84
|
+
if (away) {
|
|
85
|
+
kept += 1n;
|
|
86
|
+
}
|
|
87
|
+
return new types_1.DecFloat34().set(Number((negative ? "-" : "") + kept.toString() + "e" + (-dec)));
|
|
40
88
|
}
|
|
41
89
|
//# sourceMappingURL=round.js.map
|
package/build/src/compare/cp.js
CHANGED
|
@@ -5,6 +5,41 @@ const types_1 = require("../types");
|
|
|
5
5
|
function escapeRegExpCharacter(input) {
|
|
6
6
|
return input.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
7
7
|
}
|
|
8
|
+
const CACHE_SIZE = 1000;
|
|
9
|
+
const cache = new Map();
|
|
10
|
+
/** `end` is the pattern's length after the last token that is not a `*`: what
|
|
11
|
+
* follows is a run of trailing `[\s\S]*`, which matches any rest of the
|
|
12
|
+
* string - so it goes, and so does the `$` that made the engine walk there */
|
|
13
|
+
function compile(r) {
|
|
14
|
+
let pattern = "";
|
|
15
|
+
let end = 0;
|
|
16
|
+
for (let i = 0; i < r.length; i++) {
|
|
17
|
+
const current = r[i];
|
|
18
|
+
if (current === "*") {
|
|
19
|
+
pattern += "[\\s\\S]*";
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (current === "#") {
|
|
23
|
+
if (i + 1 < r.length) {
|
|
24
|
+
const next = r[i + 1];
|
|
25
|
+
pattern += next === "#" ? "#" : escapeRegExpCharacter(next);
|
|
26
|
+
i++;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
pattern += "#";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (current === "+") {
|
|
33
|
+
pattern += "[\\s\\S]";
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
pattern += escapeRegExpCharacter(current);
|
|
37
|
+
}
|
|
38
|
+
end = pattern.length;
|
|
39
|
+
}
|
|
40
|
+
const open = end < pattern.length;
|
|
41
|
+
return new RegExp("^" + pattern.slice(0, end) + (open ? "" : "$"), "iu");
|
|
42
|
+
}
|
|
8
43
|
function cp(left, right) {
|
|
9
44
|
let l = "";
|
|
10
45
|
if (typeof left === "number" || typeof left === "string") {
|
|
@@ -35,36 +70,14 @@ function cp(left, right) {
|
|
|
35
70
|
else {
|
|
36
71
|
r = right.get().toString().trimEnd();
|
|
37
72
|
}
|
|
38
|
-
let
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
const next = r[i + 1];
|
|
44
|
-
if (next === "#") {
|
|
45
|
-
pattern += "#";
|
|
46
|
-
i++;
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
pattern += escapeRegExpCharacter(next);
|
|
50
|
-
i++;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
pattern += "#";
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
else if (current === "*") {
|
|
58
|
-
pattern += "[\\s\\S]*";
|
|
59
|
-
}
|
|
60
|
-
else if (current === "+") {
|
|
61
|
-
pattern += "[\\s\\S]";
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
pattern += escapeRegExpCharacter(current);
|
|
73
|
+
let reg = cache.get(r);
|
|
74
|
+
if (reg === undefined) {
|
|
75
|
+
reg = compile(r);
|
|
76
|
+
if (cache.size >= CACHE_SIZE) {
|
|
77
|
+
cache.delete(cache.keys().next().value);
|
|
65
78
|
}
|
|
79
|
+
cache.set(r, reg);
|
|
66
80
|
}
|
|
67
|
-
|
|
68
|
-
return l.match(reg) !== null;
|
|
81
|
+
return reg.test(l);
|
|
69
82
|
}
|
|
70
83
|
//# sourceMappingURL=cp.js.map
|
package/build/src/compare/eq.js
CHANGED
|
@@ -347,6 +347,16 @@ function eq(left, right) {
|
|
|
347
347
|
else if (right instanceof types_1.Float && typeof l === "number") {
|
|
348
348
|
r = right.getRaw();
|
|
349
349
|
}
|
|
350
|
+
else if ((right instanceof types_1.Float || right instanceof types_1.DecFloat34) && typeof l === "string") {
|
|
351
|
+
// a character-like operand is compared as a number: get( ) of a float
|
|
352
|
+
// writes 1,4800000000000000E+02, which no text compares equal with
|
|
353
|
+
r = right.getRaw();
|
|
354
|
+
l = Number(l);
|
|
355
|
+
}
|
|
356
|
+
else if (left instanceof types_1.DecFloat34 && typeof r === "string") {
|
|
357
|
+
l = left.getRaw();
|
|
358
|
+
r = Number(r);
|
|
359
|
+
}
|
|
350
360
|
else if (left instanceof types_1.Float) {
|
|
351
361
|
if (typeof r === "number") {
|
|
352
362
|
l = left.getRaw();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Table } from "../types";
|
|
2
2
|
import { ICharacter } from "../types/_character";
|
|
3
3
|
import { INumeric } from "../types/_numeric";
|
|
4
|
-
|
|
4
|
+
type Operand = number | string | ICharacter | INumeric;
|
|
5
|
+
export declare function compareIn(left: Operand, right: Table): boolean;
|
|
6
|
+
export {};
|
package/build/src/compare/in.js
CHANGED
|
@@ -3,32 +3,49 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.compareIn = compareIn;
|
|
4
4
|
const cp_1 = require("./cp");
|
|
5
5
|
const eq_1 = require("./eq");
|
|
6
|
+
const ge_1 = require("./ge");
|
|
7
|
+
const gt_1 = require("./gt");
|
|
8
|
+
const le_1 = require("./le");
|
|
9
|
+
const lt_1 = require("./lt");
|
|
6
10
|
const ne_1 = require("./ne");
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
return
|
|
11
|
+
function matches(left, option, low, high) {
|
|
12
|
+
switch (option) {
|
|
13
|
+
case "EQ": return (0, eq_1.eq)(left, low);
|
|
14
|
+
case "NE": return (0, ne_1.ne)(left, low);
|
|
15
|
+
case "GT": return (0, gt_1.gt)(left, low);
|
|
16
|
+
case "GE": return (0, ge_1.ge)(left, low);
|
|
17
|
+
case "LT": return (0, lt_1.lt)(left, low);
|
|
18
|
+
case "LE": return (0, le_1.le)(left, low);
|
|
19
|
+
case "BT": return (0, ge_1.ge)(left, low) && (0, le_1.le)(left, high);
|
|
20
|
+
case "NB": return !((0, ge_1.ge)(left, low) && (0, le_1.le)(left, high));
|
|
21
|
+
case "CP": return (0, cp_1.cp)(left, low);
|
|
22
|
+
case "NP": return !(0, cp_1.cp)(left, low);
|
|
23
|
+
default: throw new Error("compareIn, unknown option " + option);
|
|
10
24
|
}
|
|
25
|
+
}
|
|
26
|
+
// A value is in a range table when some I row admits it, or there is no I
|
|
27
|
+
// row at all, and no E row admits it. An empty table admits everything.
|
|
28
|
+
function compareIn(left, right) {
|
|
29
|
+
let included = false;
|
|
30
|
+
let hasInclude = false;
|
|
11
31
|
for (const row of right.array()) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
32
|
+
const r = row.get();
|
|
33
|
+
const sign = r["sign"].get().toString().toUpperCase().trim();
|
|
34
|
+
const option = r["option"].get().toString().toUpperCase().trim();
|
|
35
|
+
const hit = matches(left, option, r["low"], r["high"]);
|
|
36
|
+
if (sign === "I") {
|
|
37
|
+
hasInclude = true;
|
|
38
|
+
included = included || hit;
|
|
21
39
|
}
|
|
22
|
-
else if (
|
|
23
|
-
if (
|
|
24
|
-
return
|
|
40
|
+
else if (sign === "E") {
|
|
41
|
+
if (hit) {
|
|
42
|
+
return false;
|
|
25
43
|
}
|
|
26
44
|
}
|
|
27
45
|
else {
|
|
28
|
-
|
|
29
|
-
throw new Error("compareIn todo");
|
|
46
|
+
throw new Error("compareIn, unknown sign " + sign);
|
|
30
47
|
}
|
|
31
48
|
}
|
|
32
|
-
return false;
|
|
49
|
+
return hasInclude === false || included;
|
|
33
50
|
}
|
|
34
51
|
//# sourceMappingURL=in.js.map
|
|
@@ -38,8 +38,10 @@ function concatenate(input) {
|
|
|
38
38
|
if (hasSeparator === true && i > 0) {
|
|
39
39
|
result += sep;
|
|
40
40
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
41
|
+
const row = array[i];
|
|
42
|
+
const value = row.get();
|
|
43
|
+
// Preserve trailing blanks in string rows even without RESPECTING BLANKS.
|
|
44
|
+
result += respectingBlanks === true || row instanceof types_1.String ? value : value.trimEnd();
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
setTarget(input.target, result);
|
|
@@ -21,6 +21,18 @@ function createDataRefTo(target, name) {
|
|
|
21
21
|
}
|
|
22
22
|
target.assign(new abap.types.ABAPObject({ qualifiedName: name, RTTIName: name }));
|
|
23
23
|
}
|
|
24
|
+
/** CREATE DATA ... LIKE dobj takes the type of dobj, never its content: the
|
|
25
|
+
* created data object starts initial. A table is created empty right away
|
|
26
|
+
* instead of copying its rows first. */
|
|
27
|
+
function initialLike(like) {
|
|
28
|
+
if (like instanceof types_1.Table || like instanceof types_1.HashedTable) {
|
|
29
|
+
const TableType = like.constructor;
|
|
30
|
+
return new TableType(like.getRowType(), like.getOptions(), like.getQualifiedName());
|
|
31
|
+
}
|
|
32
|
+
const copy = like.clone();
|
|
33
|
+
copy.clear();
|
|
34
|
+
return copy;
|
|
35
|
+
}
|
|
24
36
|
function createData(target, options) {
|
|
25
37
|
// console.dir(options);
|
|
26
38
|
if (target instanceof types_1.FieldSymbol) {
|
|
@@ -208,10 +220,10 @@ function createData(target, options) {
|
|
|
208
220
|
options.like = options.like.getPointer();
|
|
209
221
|
}
|
|
210
222
|
if (options.table === true) {
|
|
211
|
-
target.assign(new abap.types.Table(options.like
|
|
223
|
+
target.assign(new abap.types.Table(initialLike(options.like), tableOptions));
|
|
212
224
|
}
|
|
213
225
|
else {
|
|
214
|
-
target.assign(options.like
|
|
226
|
+
target.assign(initialLike(options.like));
|
|
215
227
|
}
|
|
216
228
|
}
|
|
217
229
|
else {
|
|
@@ -8,13 +8,17 @@ function determineFromTo(array, topEquals, key) {
|
|
|
8
8
|
// if there is no WHERE supplied, its using the sorting of the secondary key
|
|
9
9
|
return { from: 1, to: array.length };
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
// 1-based, as the branch without topEquals above answers: when the key's
|
|
12
|
+
// first field is not in topEquals (a key over a substructure component,
|
|
13
|
+
// s-x, is never put there) nothing is narrowed, and the loop must not start
|
|
14
|
+
// at row index -1
|
|
15
|
+
let from = 1;
|
|
12
16
|
let to = array.length;
|
|
13
17
|
// todo: multi field
|
|
14
18
|
const keyField = key.keyFields[0].toLowerCase();
|
|
15
19
|
const keyValue = topEquals[keyField];
|
|
16
20
|
if (keyField && keyValue) {
|
|
17
|
-
from = (0, binary_search_1.binarySearchFrom)(array,
|
|
21
|
+
from = (0, binary_search_1.binarySearchFrom)(array, 0, to, keyField, keyValue);
|
|
18
22
|
to = (0, binary_search_1.binarySearchTo)(array, from, to, keyField, keyValue);
|
|
19
23
|
// console.dir("from: " + from + ", to: " + to);
|
|
20
24
|
}
|
|
@@ -99,7 +103,9 @@ async function* loop(table, options) {
|
|
|
99
103
|
let loopFrom = options?.from && options?.from.get() > 0 ? options.from.get() - 1 : 0;
|
|
100
104
|
let loopTo = options?.to && options.to.get() < length ? options.to.get() : length;
|
|
101
105
|
let array = [];
|
|
102
|
-
|
|
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) {
|
|
103
109
|
array = table.getSecondaryIndex(options.usingKey);
|
|
104
110
|
const { from, to } = determineFromTo(array, options.topEquals, table.getKeyByName(options.usingKey));
|
|
105
111
|
loopFrom = Math.max(loopFrom, from) - 1;
|
|
@@ -122,7 +128,7 @@ async function* loop(table, options) {
|
|
|
122
128
|
// The same goes for a loop that reads an index table through a hash
|
|
123
129
|
// secondary key. Handing out 1, 2, 3 there looks helpful and is a lie the
|
|
124
130
|
// caller cannot tell from the truth.
|
|
125
|
-
const usedKey = options?.usingKey === undefined ||
|
|
131
|
+
const usedKey = options?.usingKey === undefined || isPrimaryKey
|
|
126
132
|
? undefined
|
|
127
133
|
: table.getKeyByName(options.usingKey);
|
|
128
134
|
const hasRowNumber = usedKey !== undefined
|
|
@@ -131,7 +137,9 @@ async function* loop(table, options) {
|
|
|
131
137
|
try {
|
|
132
138
|
const isStructured = array[0] instanceof types_1.Structure;
|
|
133
139
|
while (loopController.index < loopController.loopTo) {
|
|
134
|
-
|
|
140
|
+
// the body may have deleted rows: never read past the end, where
|
|
141
|
+
// array[array.length] is undefined
|
|
142
|
+
if (loopController.index >= array.length) {
|
|
135
143
|
break;
|
|
136
144
|
}
|
|
137
145
|
const current = array[loopController.index];
|
|
@@ -90,6 +90,73 @@ async function searchWithKeyPromise(arr, withKey, startIndex = 0, usesTableLine)
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
/////////////////
|
|
93
|
+
function toRow(a, isStructured, usesTableLine) {
|
|
94
|
+
if (usesTableLine === false && isStructured === true) {
|
|
95
|
+
return a.get();
|
|
96
|
+
}
|
|
97
|
+
return isStructured ? { table_line: a, ...a.get() } : { table_line: a };
|
|
98
|
+
}
|
|
99
|
+
/** READ with a key on a SORTED table: binary search over the leading key components that are given,
|
|
100
|
+
* a miss gives sy-subrc 4 and sy-tabix of the row the key would be inserted before,
|
|
101
|
+
* or sy-subrc 8 and sy-tabix lines + 1 when it would be inserted after the last row */
|
|
102
|
+
/** true when every component of the condition is a component of the key */
|
|
103
|
+
function onlyKeyComponents(keyFields, options) {
|
|
104
|
+
const key = keyFields.map(k => k.toLowerCase());
|
|
105
|
+
return Object.keys(options.withKeySimple || {}).every(n => key.includes(n.toLowerCase()));
|
|
106
|
+
}
|
|
107
|
+
function readSortedWithKey(arr, keyFields, options) {
|
|
108
|
+
const names = Object.keys(options.withKeySimple).map(n => n.toLowerCase());
|
|
109
|
+
const prefix = [];
|
|
110
|
+
for (const keyField of keyFields) {
|
|
111
|
+
const index = names.indexOf(keyField.toLowerCase());
|
|
112
|
+
if (index < 0) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
prefix.push(options.withKeyValue[index]);
|
|
116
|
+
}
|
|
117
|
+
if (prefix.length === 0) {
|
|
118
|
+
const searchResult = searchWithKey(arr, options.withKey, 0, options.usesTableLine);
|
|
119
|
+
return { found: searchResult.found, foundIndex: searchResult.foundIndex, subrc: undefined };
|
|
120
|
+
}
|
|
121
|
+
const isStructured = arr[0] instanceof types_1.Structure;
|
|
122
|
+
const comparePrefix = (row) => {
|
|
123
|
+
for (const p of prefix) {
|
|
124
|
+
const value = p.key(row);
|
|
125
|
+
// eq first, lt and gt compare a c operand with its trailing blanks against a string
|
|
126
|
+
if ((0, compare_1.eq)(value, p.value)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
else if ((0, compare_1.lt)(value, p.value)) {
|
|
130
|
+
return -1;
|
|
131
|
+
}
|
|
132
|
+
else if ((0, compare_1.gt)(value, p.value)) {
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return 0;
|
|
137
|
+
};
|
|
138
|
+
let left = 0;
|
|
139
|
+
let right = arr.length;
|
|
140
|
+
while (left < right) {
|
|
141
|
+
const middle = Math.floor((left + right) / 2);
|
|
142
|
+
if (comparePrefix(toRow(arr[middle], isStructured, options.usesTableLine)) < 0) {
|
|
143
|
+
left = middle + 1;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
right = middle;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (let index = left; index < arr.length; index++) {
|
|
150
|
+
const row = toRow(arr[index], isStructured, options.usesTableLine);
|
|
151
|
+
if (comparePrefix(row) !== 0) {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
if (options.withKey(row) === true) {
|
|
155
|
+
return { found: arr[index], foundIndex: index + 1, subrc: undefined };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { found: undefined, foundIndex: left + 1, subrc: left >= arr.length ? 8 : 4 };
|
|
159
|
+
}
|
|
93
160
|
function readTable(table, options) {
|
|
94
161
|
let found = undefined;
|
|
95
162
|
let foundIndex = 0;
|
|
@@ -187,15 +254,34 @@ function readTable(table, options) {
|
|
|
187
254
|
// fallback
|
|
188
255
|
return readTable(table, { ...options, withKeySimple: undefined });
|
|
189
256
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
257
|
+
if (keyInformation?.type === types_1.TableAccessType.sorted
|
|
258
|
+
&& options.withKeyValue
|
|
259
|
+
&& onlyKeyComponents(keyInformation.keyFields, options)) {
|
|
260
|
+
const result = readSortedWithKey(arr, keyInformation.keyFields, options);
|
|
261
|
+
found = result.found;
|
|
262
|
+
foundIndex = result.foundIndex;
|
|
263
|
+
binarySubrc = result.subrc;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
const startIndex = (0, binary_search_1.binarySearchFrom)(arr, 0, arr.length, firstKeyName.toLowerCase(), firstValue) - 1;
|
|
267
|
+
if (startIndex >= 0) {
|
|
268
|
+
const searchResult = searchWithKeyEarlyExit(arr, options.withKey, startIndex, options.usesTableLine, firstKeyName, firstValue);
|
|
269
|
+
found = searchResult.found;
|
|
270
|
+
foundIndex = searchResult.foundIndex;
|
|
271
|
+
}
|
|
197
272
|
}
|
|
198
273
|
}
|
|
274
|
+
else if (options?.withTableKey === true
|
|
275
|
+
&& options.withKeyValue
|
|
276
|
+
&& options.withKeySimple
|
|
277
|
+
&& options.withKey
|
|
278
|
+
&& table.getOptions().primaryKey?.type === types_1.TableAccessType.sorted
|
|
279
|
+
&& onlyKeyComponents(table.getOptions().primaryKey?.keyFields || [], options)) {
|
|
280
|
+
const result = readSortedWithKey(table.array(), table.getOptions().primaryKey?.keyFields || [], options);
|
|
281
|
+
found = result.found;
|
|
282
|
+
foundIndex = result.foundIndex;
|
|
283
|
+
binarySubrc = result.subrc;
|
|
284
|
+
}
|
|
199
285
|
else if ((options?.binarySearch === true || options?.withTableKey === true)
|
|
200
286
|
&& options.withKeyValue
|
|
201
287
|
&& (options?.binarySearch === true || table.getOptions().primaryKey?.type !== types_1.TableAccessType.standard)
|
|
@@ -15,6 +15,20 @@ async function updateDatabase(table, options, context) {
|
|
|
15
15
|
if (typeof table !== "string") {
|
|
16
16
|
table = table.get();
|
|
17
17
|
}
|
|
18
|
+
if (options.table !== undefined) {
|
|
19
|
+
// row by row, as a work area each: sy-subrc 4 when a row found nothing
|
|
20
|
+
// to update, sy-dbcnt the rows updated
|
|
21
|
+
let subrc = 0;
|
|
22
|
+
let dbcnt = 0;
|
|
23
|
+
for (const row of options.table.array()) {
|
|
24
|
+
await updateDatabase(table, { from: row }, context);
|
|
25
|
+
subrc = Math.max(subrc, abap.builtin.sy.get().subrc.get());
|
|
26
|
+
dbcnt += abap.builtin.sy.get().dbcnt.get();
|
|
27
|
+
}
|
|
28
|
+
abap.builtin.sy.get().subrc.set(subrc);
|
|
29
|
+
abap.builtin.sy.get().dbcnt.set(dbcnt);
|
|
30
|
+
return subrc;
|
|
31
|
+
}
|
|
18
32
|
const tabl = abap.DDIC[table.toUpperCase()];
|
|
19
33
|
if (tabl === undefined) {
|
|
20
34
|
await (0, throw_error_1.throwErrorWithParameters)("CX_SY_DYNAMIC_OSQL_SEMANTICS", { sqlmsg: new types_1.String().set(`Table ${table} not found`) });
|
|
@@ -3,6 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.templateFormatting = templateFormatting;
|
|
4
4
|
const alpha_1 = require("./alpha");
|
|
5
5
|
const types_1 = require("./types");
|
|
6
|
+
/** DECIMALS for a decimal number: its decimal digits are rounded, half away
|
|
7
|
+
* from zero, the way ABAP rounds commercially - 2.675 gives 2.68, where
|
|
8
|
+
* toFixed( ) on the nearest binary float gives 2.67 */
|
|
9
|
+
function decimalsOfDecimal(value, decimals) {
|
|
10
|
+
const shift = (x, places) => {
|
|
11
|
+
const [mantissa, exponent] = (x + "").split("e");
|
|
12
|
+
return Number(mantissa + "e" + (Number(exponent || 0) + places));
|
|
13
|
+
};
|
|
14
|
+
const rounded = shift(Math.round(shift(Math.abs(value), decimals)), -decimals);
|
|
15
|
+
return (value < 0 && rounded !== 0 ? "-" : "") + rounded.toFixed(decimals);
|
|
16
|
+
}
|
|
6
17
|
function templateFormatting(source, options) {
|
|
7
18
|
let text = "";
|
|
8
19
|
if (source instanceof types_1.FieldSymbol) {
|
|
@@ -22,7 +33,10 @@ function templateFormatting(source, options) {
|
|
|
22
33
|
}
|
|
23
34
|
else if (source instanceof types_1.DecFloat34) {
|
|
24
35
|
const raw = source.getRaw();
|
|
25
|
-
if (
|
|
36
|
+
if (options?.decimals !== undefined) {
|
|
37
|
+
text = decimalsOfDecimal(raw, options.decimals);
|
|
38
|
+
}
|
|
39
|
+
else if (Number.isInteger(raw)) {
|
|
26
40
|
text = raw.toFixed(0);
|
|
27
41
|
}
|
|
28
42
|
else {
|
|
@@ -43,7 +57,7 @@ function templateFormatting(source, options) {
|
|
|
43
57
|
}
|
|
44
58
|
}
|
|
45
59
|
else if (source instanceof types_1.Packed) {
|
|
46
|
-
if (options?.decimals) {
|
|
60
|
+
if (options?.decimals !== undefined) {
|
|
47
61
|
text = source.toFixed(options.decimals);
|
|
48
62
|
}
|
|
49
63
|
else {
|
|
@@ -127,10 +141,10 @@ function templateFormatting(source, options) {
|
|
|
127
141
|
else if (options.width) {
|
|
128
142
|
text = text.trimEnd().padEnd(options.width, " ");
|
|
129
143
|
}
|
|
130
|
-
else if (options.decimals && source instanceof types_1.Integer) {
|
|
144
|
+
else if (options.decimals !== undefined && source instanceof types_1.Integer) {
|
|
131
145
|
text = source.get().toFixed(options.decimals);
|
|
132
146
|
}
|
|
133
|
-
else if (options.decimals && source instanceof types_1.Float) {
|
|
147
|
+
else if (options.decimals !== undefined && source instanceof types_1.Float) {
|
|
134
148
|
text = source.getRaw().toFixed(options.decimals);
|
|
135
149
|
}
|
|
136
150
|
}
|
|
@@ -97,7 +97,7 @@ export declare class Table implements ITable {
|
|
|
97
97
|
clone(): Table;
|
|
98
98
|
getArrayLength(): number;
|
|
99
99
|
getKeyByName(name: string): ITableKey | undefined;
|
|
100
|
-
getSecondaryIndex(name: string):
|
|
100
|
+
getSecondaryIndex(name: string): any[];
|
|
101
101
|
getQualifiedName(): string | undefined;
|
|
102
102
|
getOptions(): ITableOptions;
|
|
103
103
|
startLoop(from: number, to: number, array: any[]): LoopController;
|
package/build/src/types/table.js
CHANGED
|
@@ -27,6 +27,15 @@ var TableKeyType;
|
|
|
27
27
|
TableKeyType["user"] = "USER";
|
|
28
28
|
TableKeyType["empty"] = "EMPTY";
|
|
29
29
|
})(TableKeyType || (exports.TableKeyType = TableKeyType = {}));
|
|
30
|
+
/* rows are handed to a sorted secondary key in table order, and a duplicate of a
|
|
31
|
+
* non-unique key goes in front of its equals, as INSERT ... INTO TABLE does: reversed
|
|
32
|
+
* before the stable sort, the later of two equal rows comes first */
|
|
33
|
+
function secondaryDuplicatesFirst(rows, key) {
|
|
34
|
+
if (key.type === TableAccessType.sorted && key.isUnique !== true) {
|
|
35
|
+
rows.reverse();
|
|
36
|
+
}
|
|
37
|
+
return rows;
|
|
38
|
+
}
|
|
30
39
|
class LoopController {
|
|
31
40
|
index;
|
|
32
41
|
loopTo;
|
|
@@ -109,7 +118,7 @@ class HashedTable {
|
|
|
109
118
|
throw `Table, secondary key "${name}" not found`;
|
|
110
119
|
}
|
|
111
120
|
// note, array() already is a copy, so it can be used,
|
|
112
|
-
const copy = this.array();
|
|
121
|
+
const copy = secondaryDuplicatesFirst(this.array(), secondary);
|
|
113
122
|
(0, sort_1.sort)(copy, { by: secondary.keyFields.map(k => { return { component: k.toLowerCase() }; }) });
|
|
114
123
|
this.secondaryIndexes[name.toUpperCase()] = copy;
|
|
115
124
|
return copy;
|
|
@@ -365,7 +374,7 @@ class Table {
|
|
|
365
374
|
if (secondary === undefined) {
|
|
366
375
|
throw `Table, secondary key "${name}" not found`;
|
|
367
376
|
}
|
|
368
|
-
const copy = [...this.value];
|
|
377
|
+
const copy = secondaryDuplicatesFirst([...this.value], secondary);
|
|
369
378
|
(0, sort_1.sort)(copy, { by: secondary.keyFields.map(k => { return { component: k.toLowerCase() }; }), skipSortedCheck: true });
|
|
370
379
|
this.secondaryIndexes[name.toUpperCase()] = copy;
|
|
371
380
|
return copy;
|
|
@@ -482,24 +491,25 @@ class Table {
|
|
|
482
491
|
return { value: this.insertIndex(val, 0, true), subrc: 0 };
|
|
483
492
|
}
|
|
484
493
|
const lastComparison = compare(this.value[lastIndex], val);
|
|
485
|
-
if (lastComparison < 0
|
|
494
|
+
if (lastComparison < 0) {
|
|
486
495
|
return { value: this.insertIndex(val, this.value.length, true), subrc: 0 };
|
|
487
496
|
}
|
|
488
|
-
else if (lastComparison === 0) {
|
|
497
|
+
else if (lastComparison === 0 && unique === true) {
|
|
489
498
|
return { value: undefined, subrc: 4 };
|
|
490
499
|
}
|
|
491
500
|
let low = 0;
|
|
492
501
|
let high = this.value.length;
|
|
493
502
|
while (low < high) {
|
|
494
503
|
const middle = Math.floor((low + high) / 2);
|
|
495
|
-
|
|
504
|
+
// a duplicate of a non-unique key goes in front of the rows it duplicates
|
|
505
|
+
if (compare(this.value[middle], val) < 0) {
|
|
496
506
|
low = middle + 1;
|
|
497
507
|
}
|
|
498
508
|
else {
|
|
499
509
|
high = middle;
|
|
500
510
|
}
|
|
501
511
|
}
|
|
502
|
-
if (unique === true && low
|
|
512
|
+
if (unique === true && low < this.value.length && compare(this.value[low], val) === 0) {
|
|
503
513
|
return { value: undefined, subrc: 4 };
|
|
504
514
|
}
|
|
505
515
|
return { value: this.insertIndex(val, low, true), subrc: 0 };
|
|
@@ -550,8 +560,18 @@ class Table {
|
|
|
550
560
|
return val;
|
|
551
561
|
}
|
|
552
562
|
}
|
|
553
|
-
/* appends and returns this
|
|
563
|
+
/* appends and returns this - how VALUE #( ) builds a table, row by row.
|
|
564
|
+
* A sorted table gets each row at its sorted place, as INSERT ... INTO
|
|
565
|
+
* TABLE puts it: pushed, it was in insertion order until something
|
|
566
|
+
* assigned it, so a VALUE #( ) handed straight to a method or looped over
|
|
567
|
+
* was a SORTED table unsorted, and every binary search on it could miss.
|
|
568
|
+
* A duplicate of a unique key is kept, as before */
|
|
554
569
|
appendThis(item) {
|
|
570
|
+
const primary = this.getOptions()?.primaryKey;
|
|
571
|
+
if (primary?.type === TableAccessType.sorted && !(item instanceof data_reference_1.DataReference)) {
|
|
572
|
+
this.insertSorted(item, (a, b) => (0, sort_1.compareRows)(a, b, primary.keyFields), false);
|
|
573
|
+
return this;
|
|
574
|
+
}
|
|
555
575
|
this.append(item);
|
|
556
576
|
return this;
|
|
557
577
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abaplint/runtime",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.95",
|
|
4
4
|
"description": "Transpiler - Runtime",
|
|
5
5
|
"main": "build/src/index.js",
|
|
6
6
|
"typings": "build/src/index.d.ts",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"@types/mocha": "^10.0.10",
|
|
34
34
|
"@types/node": "^26.4.1",
|
|
35
35
|
"chai": "^6.2.2",
|
|
36
|
-
"mocha": "^12.0.
|
|
36
|
+
"mocha": "^12.0.3",
|
|
37
37
|
"source-map-support": "^0.5.21",
|
|
38
38
|
"typescript": "^6.0.3"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"temporal-polyfill": "^1.0.
|
|
41
|
+
"temporal-polyfill": "^1.0.5"
|
|
42
42
|
}
|
|
43
43
|
}
|