|
| 1 | +function checkEndOfField(value: string, i: number) { |
| 2 | + switch (value[i]) { |
| 3 | + case ",": |
| 4 | + return true; |
| 5 | + |
| 6 | + case ")": |
| 7 | + if (i < value.length - 1) { |
| 8 | + throw new RangeError( |
| 9 | + "Invalid composite literal: end of input expected" |
| 10 | + ); |
| 11 | + } |
| 12 | + return false; |
| 13 | + |
| 14 | + default: |
| 15 | + throw new RangeError("Invalid composite literal: ) or , expected"); |
| 16 | + } |
| 17 | +} |
| 18 | + |
1 | 19 | /**
|
2 | 20 | * Parses a composite value into the list of attributes.
|
3 | 21 | * `NULL` attributes are returned as JavaScript `null`, every other value
|
|
20 | 38 | * {@link https://www.postgresql.org/docs/current/rowtypes.html#ROWTYPES-IO-SYNTAX Source}
|
21 | 39 | */
|
22 | 40 | export function* parse(value: string) {
|
23 |
| - let i = 0; |
24 |
| - let end; |
| 41 | + if (typeof value !== "string") { |
| 42 | + throw new TypeError("Invalid input: string expected"); |
| 43 | + } |
| 44 | + |
| 45 | + if (value[0] !== "(") { |
| 46 | + throw new RangeError("Invalid composite literal: ( expected"); |
| 47 | + } |
25 | 48 |
|
26 |
| - // remove leading and trailing whitespace and parentheses |
27 |
| - value = value.trim(); |
28 |
| - value = value.substring(1, value.length - 1); |
| 49 | + let i = 1; |
| 50 | + let end; |
29 | 51 |
|
30 | 52 | while (true) {
|
| 53 | + // expecting a new field |
31 | 54 | switch (value[i]) {
|
32 | 55 | case undefined:
|
33 |
| - yield null; |
34 |
| - return; |
| 56 | + throw new RangeError( |
| 57 | + "Invalid composite literal: unexpected end of input" |
| 58 | + ); |
35 | 59 |
|
| 60 | + case ")": // fallthrough |
36 | 61 | case ",":
|
37 | 62 | yield null;
|
38 |
| - i += 1; |
39 |
| - continue; |
| 63 | + |
| 64 | + if (checkEndOfField(value, i)) { |
| 65 | + i += 1; |
| 66 | + continue; |
| 67 | + } |
| 68 | + return; |
40 | 69 |
|
41 | 70 | case '"':
|
42 | 71 | i += 1;
|
43 |
| - // find the next double quote not escaped by doubling |
44 |
| - end = i + value.substring(i).search(/(?<=([^"]|^)("")*)"(?!")/); |
| 72 | + end = i + value.substring(i).search(/(?<=([^"]|^)("")*)"[^"]/); |
45 | 73 | if (end < i) {
|
46 |
| - throw new RangeError("couldn't find closing double quote"); |
| 74 | + throw new RangeError( |
| 75 | + "Invalid composite literal: unterminated double quotes" |
| 76 | + ); |
47 | 77 | }
|
48 | 78 |
|
| 79 | + // Should we check if there are odd number of \ before the closing doulbe quotes? |
| 80 | + // Postgres doesn't use \ to escape " in it's output, but accepts it in the input. |
| 81 | + |
49 | 82 | yield value
|
50 | 83 | .substring(i, end)
|
51 | 84 | .replace(/""/g, '"')
|
52 | 85 | .replace(/\\\\/g, "\\");
|
53 | 86 |
|
54 |
| - switch (value[end + 1]) { |
55 |
| - case undefined: |
56 |
| - return; |
57 |
| - |
58 |
| - case ",": |
59 |
| - i = end + 2; |
60 |
| - continue; |
61 |
| - |
62 |
| - default: |
63 |
| - throw new RangeError( |
64 |
| - "comma or end-of-string expected after closing double quote" |
65 |
| - ); |
| 87 | + if (checkEndOfField(value, end + 1)) { |
| 88 | + i = end + 2; |
| 89 | + continue; |
66 | 90 | }
|
| 91 | + return; |
67 | 92 |
|
68 | 93 | default:
|
69 |
| - // find the next comma or go to the end of the string |
70 |
| - end = value.indexOf(",", i); |
| 94 | + end = value.indexOf(",", i + 1); |
71 | 95 | if (end === -1) {
|
72 |
| - yield value.substring(i); |
73 |
| - return; |
| 96 | + end = value.indexOf(")", i + 1); |
| 97 | + if (end === -1) { |
| 98 | + throw new RangeError( |
| 99 | + "Invalid composite literal: unexpected end of input" |
| 100 | + ); |
| 101 | + } |
74 | 102 | }
|
75 | 103 |
|
| 104 | + // Should we check if there are any \ in the field value? |
| 105 | + // Postgres doesn't use escaped characters in unquoted fields, but accepts it in the input. |
| 106 | + |
76 | 107 | yield value.substring(i, end);
|
77 | 108 |
|
78 |
| - i = end + 1; |
79 |
| - continue; |
| 109 | + if (checkEndOfField(value, end)) { |
| 110 | + i = end + 1; |
| 111 | + continue; |
| 112 | + } |
| 113 | + return; |
80 | 114 | }
|
81 | 115 | }
|
82 | 116 | }
|
|
0 commit comments