@abaplint/runtime 2.13.92 → 2.13.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
- import { String } from "../types";
1
+ import { FieldSymbol, String } from "../types";
2
2
  import { ICharacter } from "../types/_character";
3
3
  import { INumeric } from "../types/_numeric";
4
4
  export declare function escape(input: {
5
- val: ICharacter | string;
5
+ val: ICharacter | FieldSymbol | string;
6
6
  format: INumeric | number;
7
7
  }): String;
@@ -3,8 +3,35 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.escape = escape;
4
4
  const types_1 = require("../types");
5
5
  const _position_1 = require("./_position");
6
+ const JSON_SHORT = {
7
+ "\\": "\\\\",
8
+ "\"": "\\\"",
9
+ "\b": "\\b",
10
+ "\t": "\\t",
11
+ "\n": "\\n",
12
+ "\f": "\\f",
13
+ "\r": "\\r",
14
+ };
15
+ // backslash, quote, and \b \t \n \f \r by their short forms; the other control
16
+ // characters below U+0020 as \u00XX in upper case hex; U+007F and non-ASCII unchanged
17
+ function escapeJsonCharacter(char) {
18
+ return JSON_SHORT[char] ?? "\\u" + char.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0");
19
+ }
6
20
  function escape(input) {
7
- let val = typeof input.val === "string" ? input.val : input.val.get();
21
+ let source;
22
+ if (input.val instanceof types_1.FieldSymbol) {
23
+ const pointer = input.val.getPointer();
24
+ if (pointer === undefined) {
25
+ throw new Error("GETWA_NOT_ASSIGNED");
26
+ }
27
+ source = pointer;
28
+ }
29
+ else {
30
+ source = input.val;
31
+ }
32
+ // a c operand counts without its trailing blanks, as in the other string functions
33
+ let val = typeof source === "string" ? source
34
+ : source instanceof types_1.Character ? source.getTrimEnd() : source.get();
8
35
  const format = (0, _position_1.position)(input.format);
9
36
  // todo, optimize/cache regexes
10
37
  switch (format) {
@@ -34,9 +61,7 @@ function escape(input) {
34
61
  val = val.replace(/'/g, "\\'");
35
62
  break;
36
63
  case 24: // e_json_string
37
- val = val.replace(/\\/g, "\\\\");
38
- val = val.replace(/"/g, "\\\"");
39
- val = val.replace(/\n/g, "\\n");
64
+ val = val.replace(/[\\"\u0000-\u001F]/g, escapeJsonCharacter);
40
65
  break;
41
66
  default:
42
67
  // todo, runtime error
@@ -4,7 +4,6 @@ exports.eq = eq;
4
4
  /* eslint-disable max-len */
5
5
  const types_1 = require("../types");
6
6
  const _parse_1 = require("../operators/_parse");
7
- const initial_1 = require("./initial");
8
7
  function compareTables(left, right) {
9
8
  const leftArray = left.array();
10
9
  const rightArray = right.array();
@@ -35,6 +34,14 @@ function compareStructures(left, right) {
35
34
  }
36
35
  return true;
37
36
  }
37
+ // two x fields of different lengths: the shorter one is padded with 00 on the
38
+ // right, so x'AB' = x'AB00'
39
+ function eqHexPadded(left, right) {
40
+ const l = left.get();
41
+ const r = right.get();
42
+ const length = Math.max(l.length, r.length);
43
+ return l.padEnd(length, "0") === r.padEnd(length, "0");
44
+ }
38
45
  // module locals instead of the imported bindings, as the CommonJS re-export
39
46
  // getters add overhead on every access in the hot path below
40
47
  const IntegerC = types_1.Integer;
@@ -107,7 +114,7 @@ function eq(left, right) {
107
114
  }
108
115
  else if (leftConstructor === HexC || leftConstructor === HexUInt8C) {
109
116
  if (left.getLength() !== right.getLength()) {
110
- return (0, initial_1.initial)(left) && (0, initial_1.initial)(right);
117
+ return eqHexPadded(left, right);
111
118
  }
112
119
  return left.get() === right.get();
113
120
  }
@@ -211,7 +218,7 @@ function eq(left, right) {
211
218
  if (left instanceof types_1.Hex || left instanceof types_1.HexUInt8) {
212
219
  // @ts-ignore
213
220
  if (right.getLength && right.getLength() !== left.getLength()) {
214
- return (0, initial_1.initial)(left) && (0, initial_1.initial)(right);
221
+ return eqHexPadded(left, right);
215
222
  }
216
223
  return right.get() === left.get();
217
224
  }
@@ -112,8 +112,14 @@ function gt(left, right) {
112
112
  }
113
113
  ///////////////////////////////////////////////////////////////////
114
114
  function gt_with_hex(left, right) {
115
- const left_hex = get_hex_from_parameter(left);
116
- const right_hex = get_hex_from_parameter(right);
115
+ let left_hex = get_hex_from_parameter(left);
116
+ let right_hex = get_hex_from_parameter(right);
117
+ if ((left instanceof types_1.Hex || left instanceof types_1.HexUInt8) && (right instanceof types_1.Hex || right instanceof types_1.HexUInt8)) {
118
+ // two x fields of different lengths: the shorter one is padded with 00 on the right
119
+ const length = Math.max(left_hex.length, right_hex.length);
120
+ left_hex = left_hex.padEnd(length, "0");
121
+ right_hex = right_hex.padEnd(length, "0");
122
+ }
117
123
  return left_hex > right_hex;
118
124
  }
119
125
  function get_hex_from_parameter(comparison_part) {
@@ -1,42 +1,80 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.expandIN = expandIN;
4
+ function toSQLValue(value) {
5
+ if (typeof value === "number") {
6
+ return value.toString();
7
+ }
8
+ return `'` + String(value ?? "").replace(/'/g, "''") + "'";
9
+ }
10
+ // one row of a ranges table as a condition that is true when the row matches
11
+ function rowCondition(field, sign, option, low, high) {
12
+ const lowValue = toSQLValue(low);
13
+ switch (option) {
14
+ case "EQ":
15
+ return `${field} = ${lowValue}`;
16
+ case "NE":
17
+ return `${field} <> ${lowValue}`;
18
+ case "GT":
19
+ return `${field} > ${lowValue}`;
20
+ case "GE":
21
+ return `${field} >= ${lowValue}`;
22
+ case "LT":
23
+ return `${field} < ${lowValue}`;
24
+ case "LE":
25
+ return `${field} <= ${lowValue}`;
26
+ case "BT":
27
+ return `(${field} >= ${lowValue} AND ${field} <= ${toSQLValue(high)})`;
28
+ case "NB":
29
+ return `NOT (${field} >= ${lowValue} AND ${field} <= ${toSQLValue(high)})`;
30
+ case "CP":
31
+ case "NP":
32
+ {
33
+ if (sign !== "I" || option !== "CP") {
34
+ // the pattern rules come with a separate change; until then only I CP, as before
35
+ throw new Error(`IN, ${sign} ${option} not supported`);
36
+ }
37
+ const like = `${field} LIKE '` + String(low ?? "").trimEnd().replace(/'/g, "''").replace(/\*/g, "%") + "'";
38
+ return option === "CP" ? like : `NOT ${like}`;
39
+ }
40
+ default:
41
+ throw new Error(`IN, ${sign} ${option} not supported`);
42
+ }
43
+ }
4
44
  // note: must always return an expression, never return empty string
5
45
  // https://www.sqlite.org/lang_select.html
46
+ // The rows with SIGN I are ORed, AND NOT the OR of the rows with SIGN E; a
47
+ // table with E rows only excludes them from everything.
6
48
  function expandIN(fieldName, table) {
7
- let ret = "";
8
49
  if (table.array().length === 0) {
9
50
  // " NOT IN ()" does not work on postgres
10
51
  // LIKE '%' does not work in snowflake with RTRIM collation
11
- ret = `true`;
52
+ return `true`;
12
53
  }
13
- else {
14
- ret = `(`;
15
- const values = [];
16
- fieldName = fieldName.replace("~", `"."`);
17
- for (const row of table.array()) {
18
- const val = `'` + row.get().low?.get().replace(/'/g, "''") + "'";
19
- if (row.get().sign?.get() === "I" && row.get().option?.get() === "EQ") {
20
- values.push(`"${fieldName}" = ` + val);
21
- }
22
- else if (row.get().sign?.get() === "I" && row.get().option?.get() === "NE") {
23
- values.push(`"${fieldName}" <> ` + val);
24
- }
25
- else if (row.get().sign?.get() === "I" && row.get().option?.get() === "GE") {
26
- values.push(`"${fieldName}" >= ` + val);
27
- }
28
- else if (row.get().sign?.get() === "I" && row.get().option?.get() === "LE") {
29
- values.push(`"${fieldName}" <= ` + val);
30
- }
31
- else if (row.get().sign?.get() === "I" && row.get().option?.get() === "CP") {
32
- values.push(`"${fieldName}" LIKE '` + row.get().low?.get().trimEnd().replace(/'/g, "''").replace(/\*/g, "%") + "'");
33
- }
34
- else {
35
- throw new Error(`IN, ${row.get().sign?.get()} ${row.get().option?.get()} not supported`);
36
- }
54
+ const field = `"${fieldName.replace("~", `"."`)}"`;
55
+ const include = [];
56
+ const exclude = [];
57
+ for (const row of table.array()) {
58
+ const sign = row.get().sign?.get();
59
+ const option = row.get().option?.get();
60
+ if (sign !== "I" && sign !== "E") {
61
+ throw new Error(`IN, ${sign} ${option} not supported`);
62
+ }
63
+ const cond = rowCondition(field, sign, option, row.get().low?.get(), row.get().high?.get());
64
+ if (sign === "I") {
65
+ include.push(cond);
37
66
  }
38
- ret += values.join(" OR ") + ")";
67
+ else {
68
+ exclude.push(cond);
69
+ }
70
+ }
71
+ const parts = [];
72
+ if (include.length > 0) {
73
+ parts.push("(" + include.join(" OR ") + ")");
74
+ }
75
+ if (exclude.length > 0) {
76
+ parts.push("NOT (" + exclude.join(" OR ") + ")");
39
77
  }
40
- return ret;
78
+ return "(" + parts.join(" AND ") + ")";
41
79
  }
42
80
  //# sourceMappingURL=expand_in.js.map
@@ -1,6 +1,2 @@
1
1
  import { XString } from "../types";
2
- export declare function get_bit_operation_chunks(left: XString, right: XString): {
3
- leftChunk: number;
4
- rightChunk: number;
5
- chunkLen: number;
6
- }[];
2
+ export declare function bit_operation(left: XString, right: XString, op: (l: number, r: number) => number): XString;
@@ -1,29 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.get_bit_operation_chunks = get_bit_operation_chunks;
4
- function get_bit_operation_chunks(left, right) {
5
- const ret = [];
6
- let leftFull = left.get();
7
- const leftLen = leftFull.length;
8
- leftFull = leftFull.padEnd(Math.ceil(leftLen / 2) * 2, "0");
9
- let rightFull = right.get();
10
- const rightLen = rightFull.length;
11
- rightFull = rightFull.padEnd(Math.ceil(rightLen / 2) * 2, "0");
12
- const maxLen = leftFull.length > rightFull.length ? leftFull.length : rightFull.length;
13
- // Using 3-byte chunkgs (6 hex positions) to avoid JavaScript negative values for extreme cases
14
- const chunks = maxLen / 6;
15
- for (let pass = chunks; pass > 0; pass--) {
16
- const chunkStart = maxLen - pass * 6;
17
- const chunkEnd = maxLen - (pass - 1) * 6;
18
- let leftSlice = leftFull.slice(chunkStart, chunkEnd);
19
- let rightSlice = rightFull.slice(chunkStart, chunkEnd);
20
- const chunkLen = leftSlice.length > rightSlice.length ? leftSlice.length : rightSlice.length;
21
- leftSlice = leftSlice.padEnd(chunkLen, "0");
22
- rightSlice = rightSlice.padEnd(chunkLen, "0");
23
- const leftChunk = parseInt(leftSlice, 16);
24
- const rightChunk = parseInt(rightSlice, 16);
25
- ret.push({ leftChunk: leftChunk, rightChunk: rightChunk, chunkLen: chunkLen });
3
+ exports.bit_operation = bit_operation;
4
+ /*eslint no-bitwise: ["error", { "allow": [">>>"] }] */
5
+ const types_1 = require("../types");
6
+ // the shorter operand is padded with hex 0 on the right
7
+ function bit_operation(left, right, op) {
8
+ let leftHex = left.get();
9
+ let rightHex = right.get();
10
+ const maxLen = Math.ceil(Math.max(leftHex.length, rightHex.length) / 2) * 2;
11
+ leftHex = leftHex.padEnd(maxLen, "0");
12
+ rightHex = rightHex.padEnd(maxLen, "0");
13
+ let result;
14
+ if (maxLen > 0 && maxLen <= 8) {
15
+ // up to 4 bytes in one 32 bit operation, >>> 0 makes the result unsigned
16
+ result = (op(parseInt(leftHex, 16), parseInt(rightHex, 16)) >>> 0).toString(16).padStart(maxLen, "0");
26
17
  }
18
+ else {
19
+ const l = Buffer.from(leftHex, "hex");
20
+ const r = Buffer.from(rightHex, "hex");
21
+ for (let i = 0; i < l.length; i++) {
22
+ l[i] = op(l[i], r[i]);
23
+ }
24
+ result = l.toString("hex");
25
+ }
26
+ const ret = new types_1.XString();
27
+ ret.set(result.toUpperCase());
27
28
  return ret;
28
29
  }
29
30
  //# sourceMappingURL=_bit_operations.js.map
@@ -1,18 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.bitand = bitand;
4
- /*eslint no-bitwise: ["error", { "allow": ["&"] }] */
5
- const types_1 = require("../types");
6
4
  const _bit_operations_1 = require("./_bit_operations");
7
5
  function bitand(left, right) {
8
- let and = "";
9
- const chunks = (0, _bit_operations_1.get_bit_operation_chunks)(left, right);
10
- // eslint-disable-next-line no-cond-assign
11
- for (let i = 0, chunk; chunk = chunks[i]; i++) {
12
- and = and + (chunk.leftChunk & chunk.rightChunk).toString(16).toUpperCase().padStart(chunk.chunkLen, "0");
13
- }
14
- const ret = new types_1.XString();
15
- ret.set(and);
16
- return ret;
6
+ return (0, _bit_operations_1.bit_operation)(left, right, (l, r) => l & r);
17
7
  }
18
8
  //# sourceMappingURL=bit-and.js.map
@@ -1,18 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.bitor = bitor;
4
- /*eslint no-bitwise: ["error", { "allow": ["|"] }] */
5
- const types_1 = require("../types");
6
4
  const _bit_operations_1 = require("./_bit_operations");
7
5
  function bitor(left, right) {
8
- let or = "";
9
- const chunks = (0, _bit_operations_1.get_bit_operation_chunks)(left, right);
10
- // eslint-disable-next-line no-cond-assign
11
- for (let i = 0, chunk; chunk = chunks[i]; i++) {
12
- or = or + (chunk.leftChunk | chunk.rightChunk).toString(16).toUpperCase().padStart(chunk.chunkLen, "0");
13
- }
14
- const ret = new types_1.XString();
15
- ret.set(or);
16
- return ret;
6
+ return (0, _bit_operations_1.bit_operation)(left, right, (l, r) => l | r);
17
7
  }
18
8
  //# sourceMappingURL=bit-or.js.map
@@ -1,18 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.bitxor = bitxor;
4
- /*eslint no-bitwise: ["error", { "allow": ["^"] }] */
5
- const types_1 = require("../types");
6
4
  const _bit_operations_1 = require("./_bit_operations");
7
5
  function bitxor(left, right) {
8
- let xor = "";
9
- const chunks = (0, _bit_operations_1.get_bit_operation_chunks)(left, right);
10
- // eslint-disable-next-line no-cond-assign
11
- for (let i = 0, chunk; chunk = chunks[i]; i++) {
12
- xor = xor + (chunk.leftChunk ^ chunk.rightChunk).toString(16).toUpperCase().padStart(chunk.chunkLen, "0");
13
- }
14
- const ret = new types_1.XString();
15
- ret.set(xor);
16
- return ret;
6
+ return (0, _bit_operations_1.bit_operation)(left, right, (l, r) => l ^ r);
17
7
  }
18
8
  //# sourceMappingURL=bit-xor.js.map
@@ -0,0 +1,2 @@
1
+ import { Table } from "./types";
2
+ export declare function primaryKeyValues(target: Table, row: any): any[];
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.primaryKeyValues = primaryKeyValues;
4
+ const types_1 = require("./types");
5
+ // the standard key, all character-like and byte-like components, substructures expanded
6
+ function standardKeyValues(row) {
7
+ if (!(row instanceof types_1.Structure)) {
8
+ return [row];
9
+ }
10
+ const ret = [];
11
+ for (const component of Object.values(row.get())) {
12
+ if (component instanceof types_1.Structure) {
13
+ ret.push(...standardKeyValues(component));
14
+ }
15
+ else if (component instanceof types_1.Character
16
+ || component instanceof types_1.Numc
17
+ || component instanceof types_1.Date
18
+ || component instanceof types_1.Time
19
+ || component instanceof types_1.String
20
+ || component instanceof types_1.Hex
21
+ || component instanceof types_1.HexUInt8
22
+ || component instanceof types_1.XString) {
23
+ ret.push(component);
24
+ }
25
+ }
26
+ return ret;
27
+ }
28
+ // values of the primary key, an empty key gives no values
29
+ function primaryKeyValues(target, row) {
30
+ const options = target.getOptions();
31
+ if (options?.keyType === types_1.TableKeyType.empty) {
32
+ return [];
33
+ }
34
+ const keyFields = options?.primaryKey?.keyFields ?? [];
35
+ if (keyFields.length === 0) {
36
+ return standardKeyValues(row);
37
+ }
38
+ return keyFields.map(k => {
39
+ if (k.toUpperCase() === "TABLE_LINE") {
40
+ return row;
41
+ }
42
+ let value = row;
43
+ for (const name of k.toLowerCase().split("-")) {
44
+ value = value.get()[name];
45
+ }
46
+ return value;
47
+ });
48
+ }
49
+ //# sourceMappingURL=primary_key.js.map
@@ -2,6 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.concatenate = concatenate;
4
4
  const types_1 = require("../types");
5
+ // sy-subrc 4 when a c target cuts the result, else 0
6
+ function setTarget(target, result) {
7
+ let subrc = 0;
8
+ const pointer = target instanceof types_1.FieldSymbol ? target.getPointer() : target;
9
+ if (pointer instanceof types_1.Character && result.trimEnd().length > pointer.getLength()) {
10
+ subrc = 4;
11
+ }
12
+ target.set(result);
13
+ abap.builtin.sy.get().subrc.set(subrc);
14
+ }
5
15
  function concatenate(input) {
6
16
  if (input.byteMode === true) {
7
17
  concatenateByteMode(input);
@@ -32,7 +42,7 @@ function concatenate(input) {
32
42
  result += respectingBlanks === true ? value : value.trimEnd();
33
43
  }
34
44
  }
35
- input.target.set(result);
45
+ setTarget(input.target, result);
36
46
  }
37
47
  else {
38
48
  let result = "";
@@ -60,7 +70,7 @@ function concatenate(input) {
60
70
  if (sep.length > 0 && result.length > 0) {
61
71
  result = result.slice(0, result.length - sep.length);
62
72
  }
63
- input.target.set(result);
73
+ setTarget(input.target, result);
64
74
  }
65
75
  }
66
76
  function rawByteValue(source) {
@@ -99,6 +109,6 @@ function concatenateByteMode(input) {
99
109
  if (sep.length > 0 && result.length > 0) {
100
110
  result = result.slice(0, result.length - sep.length);
101
111
  }
102
- input.target.set(result);
112
+ setTarget(input.target, result);
103
113
  }
104
114
  //# sourceMappingURL=concatenate.js.map
@@ -15,14 +15,29 @@ async function deleteDatabase(table, options, context) {
15
15
  table = table.get().trimEnd();
16
16
  }
17
17
  if (options.table) {
18
+ // one DELETE per row; sy-subrc is 4 when any row was not found,
19
+ // sy-dbcnt counts the rows deleted
20
+ let subrc = 0;
21
+ let dbcnt = 0;
18
22
  for (const row of options.table.array()) {
19
23
  await deleteDatabase(table, { from: row }, context);
24
+ if (abap.builtin.sy.get().subrc.get() !== 0) {
25
+ subrc = 4;
26
+ }
27
+ dbcnt += abap.builtin.sy.get().dbcnt.get();
20
28
  }
29
+ abap.builtin.sy.get().subrc.set(subrc);
30
+ abap.builtin.sy.get().dbcnt.set(dbcnt);
21
31
  }
22
32
  else if (options.from) {
23
33
  let where = [];
34
+ // the row is found by its primary key, the other fields are not compared
35
+ const keys = abap.DDIC[table.toUpperCase()]?.keyFields;
24
36
  const structure = options.from.get();
25
37
  for (const k of Object.keys(structure)) {
38
+ if (keys !== undefined && keys.length > 0 && keys.includes(k.toUpperCase()) === false) {
39
+ continue;
40
+ }
26
41
  const str = `"${k.toLowerCase()}"` + " = " + (0, insert_database_1.toValue)(structure[k].get());
27
42
  where.push(str);
28
43
  }
@@ -5,6 +5,7 @@ export interface IDeleteInternalOptions {
5
5
  index?: INumeric;
6
6
  adjacent?: boolean;
7
7
  comparing?: string[];
8
+ allFields?: boolean;
8
9
  fromValue?: any;
9
10
  from?: any;
10
11
  to?: any;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.deleteInternal = deleteInternal;
4
4
  const types_1 = require("../types");
5
5
  const compare_1 = require("../compare");
6
+ const primary_key_1 = require("../primary_key");
6
7
  const loop_1 = require("./loop");
7
8
  async function deleteInternal(target, options) {
8
9
  let index = 0;
@@ -50,7 +51,12 @@ async function deleteInternal(target, options) {
50
51
  for (let index = array.length - 1; index > 0; index--) {
51
52
  const prev = array[index - 1];
52
53
  const i = array[index];
53
- if (options?.comparing) {
54
+ if (options?.allFields === true) {
55
+ if ((0, compare_1.eq)(prev, i) === true) {
56
+ target.deleteIndex(index);
57
+ }
58
+ }
59
+ else if (options?.comparing) {
54
60
  let match = false;
55
61
  for (const compareField of options.comparing) {
56
62
  match = (0, compare_1.eq)(prev.get()[compareField], i.get()[compareField]);
@@ -62,8 +68,13 @@ async function deleteInternal(target, options) {
62
68
  target.deleteIndex(index);
63
69
  }
64
70
  }
65
- else if ((0, compare_1.eq)(prev, i) === true) {
66
- target.deleteIndex(index);
71
+ else {
72
+ // without COMPARING, rows are compared by the primary key, nothing is deleted if the key is empty
73
+ const prevKey = (0, primary_key_1.primaryKeyValues)(target, prev);
74
+ const key = (0, primary_key_1.primaryKeyValues)(target, i);
75
+ if (key.length > 0 && key.every((value, n) => (0, compare_1.eq)(prevKey[n], value))) {
76
+ target.deleteIndex(index);
77
+ }
67
78
  }
68
79
  }
69
80
  return;
@@ -2,6 +2,22 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.describe = describe;
4
4
  const types_1 = require("../types");
5
+ // a structure is deep when a component is a string, a table or a reference,
6
+ // or a deep structure itself: type kind v, a flat structure is u
7
+ function isDeep(structure) {
8
+ for (const component of Object.values(structure.get())) {
9
+ if (component instanceof types_1.String
10
+ || component instanceof types_1.XString
11
+ || component instanceof types_1.Table
12
+ || component instanceof types_1.HashedTable
13
+ || component instanceof types_1.DataReference
14
+ || component instanceof types_1.ABAPObject
15
+ || (component instanceof types_1.Structure && isDeep(component))) {
16
+ return true;
17
+ }
18
+ }
19
+ return false;
20
+ }
5
21
  function describe(input) {
6
22
  // console.dir(input);
7
23
  if (input.type) {
@@ -49,7 +65,7 @@ function describe(input) {
49
65
  input.type.set("e");
50
66
  }
51
67
  else if (input.field instanceof types_1.Structure) {
52
- input.type.set("u");
68
+ input.type.set(isDeep(input.field) ? "v" : "u");
53
69
  }
54
70
  else if (input.field instanceof types_1.ABAPObject) {
55
71
  input.type.set("r");
@@ -6,5 +6,6 @@ export interface IInsertDatabaseOptions {
6
6
  values?: Structure;
7
7
  table?: Table;
8
8
  connection?: string;
9
+ acceptingDuplicateKeys?: boolean;
9
10
  }
10
11
  export declare function insertDatabase(table: string | ICharacter, options: IInsertDatabaseOptions, context: Context): Promise<number | undefined>;
@@ -4,6 +4,7 @@ exports.toValue = toValue;
4
4
  exports.insertDatabase = insertDatabase;
5
5
  const prefix_1 = require("../prefix");
6
6
  const types_1 = require("../types");
7
+ const throw_error_1 = require("../throw_error");
7
8
  function toValue(value) {
8
9
  if (typeof value === "string") {
9
10
  // postgres requires ' for values
@@ -21,6 +22,8 @@ async function insertDatabase(table, options, context) {
21
22
  throw new Error("insertDatabase, wrong input");
22
23
  }
23
24
  if (options.table !== undefined) {
25
+ const subrcBefore = abap.builtin.sy.get().subrc.get();
26
+ const dbcntBefore = abap.builtin.sy.get().dbcnt.get();
24
27
  let subrc = 0;
25
28
  let dbcnt = 0;
26
29
  for (const row of options.table.array()) {
@@ -28,6 +31,14 @@ async function insertDatabase(table, options, context) {
28
31
  subrc = Math.max(subrc, abap.builtin.sy.get().subrc.get());
29
32
  dbcnt += abap.builtin.sy.get().dbcnt.get();
30
33
  }
34
+ if (subrc !== 0 && options.acceptingDuplicateKeys !== true) {
35
+ // without ACCEPTING DUPLICATE KEYS a row that cannot be inserted raises
36
+ // CX_SY_OPEN_SQL_DB; the other rows stay inserted and sy-subrc and
37
+ // sy-dbcnt keep the values they had before the statement
38
+ abap.builtin.sy.get().subrc.set(subrcBefore);
39
+ abap.builtin.sy.get().dbcnt.set(dbcntBefore);
40
+ await (0, throw_error_1.throwErrorWithParameters)("CX_SY_OPEN_SQL_DB", { sqlmsg: new types_1.String().set(`INSERT ${typeof table === "string" ? table : table.get().trimEnd()} FROM TABLE: a row could not be inserted (duplicate key?)`) });
41
+ }
31
42
  abap.builtin.sy.get().subrc.set(subrc);
32
43
  abap.builtin.sy.get().dbcnt.set(dbcnt);
33
44
  return;
@@ -37,11 +37,17 @@ function shift_character_mode(target, options) {
37
37
  else if (options?.places) {
38
38
  const p = options.places.get();
39
39
  if (options.circular) {
40
- value = value.substr(p) + value.substr(0, p);
40
+ if (options.direction === "RIGHT") {
41
+ value = value.substr(value.length - p) + value.substr(0, value.length - p);
42
+ }
43
+ else {
44
+ value = value.substr(p) + value.substr(0, p);
45
+ }
41
46
  }
42
47
  else {
43
48
  if (options.direction === "RIGHT") {
44
- value = " ".repeat(options.places.get()) + value.substring(0, options.places.get());
49
+ // set() cuts a fixed length target, a string gets longer
50
+ value = " ".repeat(p) + value;
45
51
  }
46
52
  else {
47
53
  value = value.substr(p);
@@ -69,6 +75,9 @@ function shift_character_mode(target, options) {
69
75
  value = value.substr(1) + value.substr(0, 1);
70
76
  }
71
77
  }
78
+ else if (options?.direction === "RIGHT") {
79
+ value = " " + value;
80
+ }
72
81
  else {
73
82
  value = value.substr(1);
74
83
  }
@@ -107,7 +116,8 @@ function shift_byte_mode(target, options) {
107
116
  else {
108
117
  const p = options.places.get() * 2;
109
118
  if (options.direction === "RIGHT") {
110
- value = "0".repeat(p) + value.substring(0, p);
119
+ // set() cuts a fixed length target, an xstring gets longer
120
+ value = "0".repeat(p) + value;
111
121
  }
112
122
  else {
113
123
  value = value.substr(p);
@@ -128,7 +138,15 @@ function shift_byte_mode(target, options) {
128
138
  }
129
139
  }
130
140
  else if (options?.circular) {
131
- value = value.substr(2) + value.substr(0, 2);
141
+ if (options.direction === "RIGHT") {
142
+ value = value.substr(value.length - 2) + value.substr(0, value.length - 2);
143
+ }
144
+ else {
145
+ value = value.substr(2) + value.substr(0, 2);
146
+ }
147
+ }
148
+ else if (options?.direction === "RIGHT") {
149
+ value = "00" + value;
132
150
  }
133
151
  else {
134
152
  value = value.substr(2);
@@ -4,6 +4,7 @@ exports.compareRows = compareRows;
4
4
  exports.sort = sort;
5
5
  const types_1 = require("../types");
6
6
  const compare_1 = require("../compare");
7
+ const primary_key_1 = require("../primary_key");
7
8
  function compare(a, b, input) {
8
9
  const componentName = input.component;
9
10
  const descending = input.descending;
@@ -124,6 +125,27 @@ function sort(input, options) {
124
125
  return 0;
125
126
  });
126
127
  }
128
+ else if (input instanceof types_1.Table
129
+ && input.getRowType() instanceof types_1.Structure
130
+ && input.getOptions()?.keyType !== types_1.TableKeyType.empty) {
131
+ // without BY, rows of a structure are sorted by the primary key
132
+ const descending = options?.descending === true;
133
+ const keys = new Map();
134
+ for (const row of input.array()) {
135
+ keys.set(row, (0, primary_key_1.primaryKeyValues)(input, row));
136
+ }
137
+ input.sort((a, b) => {
138
+ const keyA = keys.get(a);
139
+ const keyB = keys.get(b);
140
+ for (let i = 0; i < keyA.length; i++) {
141
+ const result = compareWholeRows(keyA[i], keyB[i]);
142
+ if (result !== 0) {
143
+ return descending ? -result : result;
144
+ }
145
+ }
146
+ return 0;
147
+ });
148
+ }
127
149
  else {
128
150
  const descending = options?.descending === true;
129
151
  input.sort((a, b) => {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WriteStatement = void 0;
4
4
  const types_1 = require("../types");
5
+ const field_symbol_1 = require("../types/field_symbol");
5
6
  const NO_DEICMAL_CURRENCIES = ["HUF", "KRW", "JPY"];
6
7
  class WriteStatement {
7
8
  context;
@@ -28,19 +29,21 @@ class WriteStatement {
28
29
  const obj = source.getCharacter();
29
30
  this.write(obj, { ...options });
30
31
  }
31
- else if (source instanceof types_1.Float) {
32
+ else if (source instanceof types_1.Float || (source instanceof field_symbol_1.FieldSymbol && source.getPointer() instanceof types_1.Float)) {
33
+ // a float behind a field symbol is formatted the same way
34
+ const float = source instanceof field_symbol_1.FieldSymbol ? source.getPointer() : source;
32
35
  if (options?.exponent?.get() === 0) {
33
- const tens = source.getRaw().toFixed(0).length - 1;
34
- if (options.noSign === true && source.getRaw() < 0) {
35
- result = source.getRaw().toFixed(17 - tens).replace(".", ",");
36
+ const tens = float.getRaw().toFixed(0).length - 1;
37
+ if (options.noSign === true && float.getRaw() < 0) {
38
+ result = float.getRaw().toFixed(17 - tens).replace(".", ",");
36
39
  result = result.replace("-", "");
37
40
  }
38
41
  else {
39
- result = source.getRaw().toFixed(16 - tens).replace(".", ",");
42
+ result = float.getRaw().toFixed(16 - tens).replace(".", ",");
40
43
  }
41
44
  }
42
45
  else {
43
- result = source.get().toString();
46
+ result = float.get().toString();
44
47
  }
45
48
  }
46
49
  else if (source instanceof types_1.Packed) {
@@ -32,7 +32,13 @@ class Date {
32
32
  else if (value instanceof float_1.Float) {
33
33
  this.set(Math.round(value.getRaw()));
34
34
  }
35
+ else if (value instanceof string_1.String) {
36
+ // an empty string is the initial date, a shorter one is filled with blanks
37
+ const str = value.get();
38
+ this.value = str === "" ? "00000000" : str;
39
+ }
35
40
  else if (typeof value === "string") {
41
+ // raw character results, eg. from CONCATENATE, are filled with blanks
36
42
  this.value = value;
37
43
  }
38
44
  else {
@@ -6,7 +6,7 @@ const float_1 = require("./float");
6
6
  const xstring_1 = require("./xstring");
7
7
  const throw_error_1 = require("../throw_error");
8
8
  const integer8_1 = require("./integer8");
9
- const REGEXP = /^(?![A-F0-9])/;
9
+ const HEX_PREFIX = /^[A-F0-9]*/;
10
10
  class Hex {
11
11
  value;
12
12
  length;
@@ -73,9 +73,12 @@ class Hex {
73
73
  return this.set(v);
74
74
  }
75
75
  else {
76
- this.value = v;
77
- if (this.value.match(REGEXP)) {
78
- this.value = "";
76
+ // c -> x: the leading hexadecimal digits, upper case only, and a
77
+ // trailing half byte padded with 0; the first other character ends
78
+ // the conversion, so "ABG1" is AB, "0a1B" is 00
79
+ this.value = HEX_PREFIX.exec(v)[0];
80
+ if (this.value.length % 2 === 1) {
81
+ this.value += "0";
79
82
  }
80
83
  }
81
84
  }
@@ -93,14 +93,11 @@ class Integer {
93
93
  this.set(roundHalfAwayFromZero(value.getRaw()));
94
94
  }
95
95
  else if (value instanceof hex_1.Hex || value instanceof xstring_1.XString || value instanceof hex_uint8_1.HexUInt8) {
96
- const hex = value.get();
97
- let num = parseInt(hex, 16);
98
- // handle two complement, the first bit is the sign
99
- if (hex.length >= 8) {
100
- const maxVal = Math.pow(2, hex.length / 2 * 8);
101
- if (num > maxVal / 2 - 1) {
102
- num = num - maxVal;
103
- }
96
+ // the last four bytes, 00 on the left, as a signed integer; an empty xstring is 0
97
+ const hex = value.get().slice(-8);
98
+ let num = hex === "" ? 0 : parseInt(hex, 16);
99
+ if (hex.length === 8 && num > 0x7FFFFFFF) {
100
+ num = num - 0x100000000;
104
101
  }
105
102
  this.set(num);
106
103
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abaplint/runtime",
3
- "version": "2.13.92",
3
+ "version": "2.13.93",
4
4
  "description": "Transpiler - Runtime",
5
5
  "main": "build/src/index.js",
6
6
  "typings": "build/src/index.d.ts",