sonamu 0.9.17 → 0.9.18

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.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Migration DDL 출력에 사용할 index predicate를 정리한다.
3
+ * 원본 SQL 표현은 보존하고, 빈 문자열과 전체를 감싼 괄호만 제거한다.
4
+ */
5
+ export declare function normalizeIndexWherePredicate(where: string | undefined): string | undefined;
6
+ /**
7
+ * Index diff 비교에 사용할 predicate identity를 만든다.
8
+ * PostgreSQL canonical 표현 차이는 AST 기반 정규화로 흡수한다.
9
+ */
10
+ export declare function normalizeIndexWherePredicateForComparison(where: string | undefined): string | undefined;
11
+ //# sourceMappingURL=index-where-predicate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-where-predicate.d.ts","sourceRoot":"","sources":["../../src/migration/index-where-predicate.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAY1F;AAED;;;GAGG;AACH,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,GAAG,SAAS,CAQpB"}
@@ -0,0 +1,305 @@
1
+ import { __esmMin } from "../_virtual/rolldown_runtime.js";
2
+ import SqlParser from "node-sql-parser";
3
+
4
+ //#region src/migration/index-where-predicate.ts
5
+ /**
6
+ * Migration DDL 출력에 사용할 index predicate를 정리한다.
7
+ * 원본 SQL 표현은 보존하고, 빈 문자열과 전체를 감싼 괄호만 제거한다.
8
+ */
9
+ function normalizeIndexWherePredicate(where) {
10
+ if (!where) {
11
+ return undefined;
12
+ }
13
+ const trimmed = removeOuterSqlParentheses(where.trim());
14
+ if (trimmed.length === 0) {
15
+ return undefined;
16
+ }
17
+ return trimmed;
18
+ }
19
+ /**
20
+ * Index diff 비교에 사용할 predicate identity를 만든다.
21
+ * PostgreSQL canonical 표현 차이는 AST 기반 정규화로 흡수한다.
22
+ */
23
+ function normalizeIndexWherePredicateForComparison(where) {
24
+ const normalized = normalizeIndexWherePredicate(where);
25
+ if (!normalized) {
26
+ return undefined;
27
+ }
28
+ return normalizeIndexWherePredicateByAst(normalized) ?? normalized;
29
+ }
30
+ function removeOuterSqlParentheses(source) {
31
+ let trimmed = source.trim();
32
+ while (trimmed.startsWith("(") && trimmed.endsWith(")")) {
33
+ const closeIndex = findMatchingParenthesisInSql(trimmed, 0);
34
+ if (closeIndex === trimmed.length - 1) {
35
+ trimmed = trimmed.slice(1, -1).trim();
36
+ continue;
37
+ }
38
+ break;
39
+ }
40
+ return trimmed;
41
+ }
42
+ function normalizeIndexWherePredicateByAst(where) {
43
+ try {
44
+ const parsed = INDEX_WHERE_SQL_PARSER.astify(`SELECT * FROM __sonamu_index_predicate_source WHERE ${where}`, { database: "postgresql" });
45
+ const statement = Array.isArray(parsed) ? parsed[0] : parsed;
46
+ if (!isSqlAstRecord(statement)) {
47
+ return undefined;
48
+ }
49
+ return serializeIndexWhereAst(statement.where);
50
+ } catch {
51
+ return undefined;
52
+ }
53
+ }
54
+ function serializeIndexWhereAst(node) {
55
+ const membership = serializeSqlMembershipPredicate(node);
56
+ if (membership) {
57
+ return membership;
58
+ }
59
+ if (!isSqlAstRecord(node)) {
60
+ return JSON.stringify(node);
61
+ }
62
+ const type = getSqlAstString(node, "type");
63
+ switch (type) {
64
+ case "binary_expr": {
65
+ const operator = normalizeSqlOperator(getSqlAstString(node, "operator"));
66
+ const left = serializeIndexWhereAst(node.left);
67
+ const right = serializeIndexWhereAst(node.right);
68
+ if (operator === "AND" || operator === "OR") {
69
+ return `(${[left, right].toSorted().join(` ${operator} `)})`;
70
+ }
71
+ return `(${left} ${operator} ${right})`;
72
+ }
73
+ case "unary_expr": {
74
+ const operator = normalizeSqlOperator(getSqlAstString(node, "operator"));
75
+ return `(${operator} ${serializeIndexWhereAst(node.expr)})`;
76
+ }
77
+ case "column_ref": {
78
+ return serializeSqlColumnRef(node);
79
+ }
80
+ case "single_quote_string": {
81
+ return `string:${JSON.stringify(getSqlAstString(node, "value"))}`;
82
+ }
83
+ case "number": {
84
+ return `number:${String(node.value)}`;
85
+ }
86
+ case "bool": {
87
+ return `bool:${String(node.value).toLowerCase()}`;
88
+ }
89
+ case "null": {
90
+ return "null";
91
+ }
92
+ case "cast": {
93
+ const targetType = getSqlCastTargetType(node);
94
+ const expr = node.expr;
95
+ if (targetType && isTextLikeSqlType(targetType) && isSqlStringLiteralLike(expr)) {
96
+ return serializeIndexWhereAst(stripTextLikeSqlCast(expr));
97
+ }
98
+ return `cast(${serializeIndexWhereAst(expr)} as ${targetType ?? "unknown"})`;
99
+ }
100
+ case "function": {
101
+ return `fn:${getSqlFunctionName(node)}(${serializeSqlExprList(node.args).join(",")})`;
102
+ }
103
+ case "array": {
104
+ return `array:[${serializeSqlArrayElements(node).map(serializeIndexWhereAst).join(",")}]`;
105
+ }
106
+ case "expr_list": {
107
+ return `list:[${serializeSqlExprList(node).map(serializeIndexWhereAst).join(",")}]`;
108
+ }
109
+ case "default": {
110
+ return `ident:${normalizeSqlIdentifier(getSqlAstString(node, "value") ?? "")}`;
111
+ }
112
+ default: {
113
+ return serializeUnknownSqlAstRecord(node);
114
+ }
115
+ }
116
+ }
117
+ function serializeSqlMembershipPredicate(node) {
118
+ if (!isSqlAstRecord(node) || getSqlAstString(node, "type") !== "binary_expr") {
119
+ return undefined;
120
+ }
121
+ const operator = normalizeSqlOperator(getSqlAstString(node, "operator"));
122
+ const left = serializeIndexWhereAst(stripTextLikeSqlCast(node.left));
123
+ const values = (() => {
124
+ if (operator === "IN") {
125
+ return serializeSqlMembershipValues(serializeSqlExprList(node.right));
126
+ }
127
+ if (operator === "=") {
128
+ return serializeSqlMembershipValues(getSqlAnyArrayElements(node.right));
129
+ }
130
+ return undefined;
131
+ })();
132
+ if (!values) {
133
+ return undefined;
134
+ }
135
+ return `membership:${left}:in:[${values.join(",")}]`;
136
+ }
137
+ function serializeSqlMembershipValues(values) {
138
+ if (!values) {
139
+ return undefined;
140
+ }
141
+ const serialized = values.map((value) => serializeIndexWhereAst(stripTextLikeSqlCast(value)));
142
+ return [...new Set(serialized)].toSorted();
143
+ }
144
+ function getSqlAnyArrayElements(node) {
145
+ if (!isSqlAstRecord(node) || getSqlAstString(node, "type") !== "function") {
146
+ return undefined;
147
+ }
148
+ if (getSqlFunctionName(node) !== "any") {
149
+ return undefined;
150
+ }
151
+ const [arg] = serializeSqlExprList(node.args);
152
+ if (!arg) {
153
+ return undefined;
154
+ }
155
+ return serializeSqlArrayElements(stripTextLikeSqlCast(arg));
156
+ }
157
+ function serializeSqlArrayElements(node) {
158
+ if (!isSqlAstRecord(node)) {
159
+ return [];
160
+ }
161
+ if (getSqlAstString(node, "type") === "array") {
162
+ return serializeSqlExprList(node.expr_list);
163
+ }
164
+ return serializeSqlExprList(node);
165
+ }
166
+ function serializeSqlExprList(node) {
167
+ if (!isSqlAstRecord(node)) {
168
+ return [];
169
+ }
170
+ if (getSqlAstString(node, "type") === "expr_list" && Array.isArray(node.value)) {
171
+ return node.value;
172
+ }
173
+ return [node];
174
+ }
175
+ function stripTextLikeSqlCast(node) {
176
+ if (!isSqlAstRecord(node) || getSqlAstString(node, "type") !== "cast") {
177
+ return node;
178
+ }
179
+ const targetType = getSqlCastTargetType(node);
180
+ if (!targetType || !isTextLikeSqlType(targetType)) {
181
+ return node;
182
+ }
183
+ return stripTextLikeSqlCast(node.expr);
184
+ }
185
+ function isSqlStringLiteralLike(node) {
186
+ const stripped = stripTextLikeSqlCast(node);
187
+ return isSqlAstRecord(stripped) && getSqlAstString(stripped, "type") === "single_quote_string";
188
+ }
189
+ function getSqlCastTargetType(node) {
190
+ if (!Array.isArray(node.target)) {
191
+ return undefined;
192
+ }
193
+ const dataTypes = node.target.map((target) => isSqlAstRecord(target) ? getSqlAstString(target, "dataType") : undefined).filter((dataType) => dataType !== undefined);
194
+ if (dataTypes.length === 0) {
195
+ return undefined;
196
+ }
197
+ return normalizeSqlDataType(dataTypes.join(" "));
198
+ }
199
+ function isTextLikeSqlType(type) {
200
+ const normalized = normalizeSqlDataType(type).replace(/\[\]$/g, "");
201
+ return normalized === "text" || normalized === "varchar";
202
+ }
203
+ function normalizeSqlDataType(type) {
204
+ return type.trim().toLowerCase().replace(/\s+/g, " ").replace(/^character varying$/, "varchar");
205
+ }
206
+ function serializeSqlColumnRef(node) {
207
+ const table = serializeSqlIdentifierNode(node.table);
208
+ const column = serializeSqlIdentifierNode(node.column);
209
+ return `column:${table ? `${table}.` : ""}${column}`;
210
+ }
211
+ function serializeSqlIdentifierNode(node) {
212
+ if (typeof node === "string") {
213
+ return normalizeSqlIdentifier(node);
214
+ }
215
+ if (isSqlAstRecord(node)) {
216
+ if (typeof node.value === "string") {
217
+ return normalizeSqlIdentifier(node.value);
218
+ }
219
+ if (isSqlAstRecord(node.expr) && typeof node.expr.value === "string") {
220
+ return normalizeSqlIdentifier(node.expr.value);
221
+ }
222
+ }
223
+ return "";
224
+ }
225
+ function getSqlFunctionName(node) {
226
+ const name = node.name;
227
+ if (typeof name === "string") {
228
+ return normalizeSqlIdentifier(name);
229
+ }
230
+ if (isSqlAstRecord(name)) {
231
+ if (Array.isArray(name.name)) {
232
+ return name.name.map(serializeSqlIdentifierNode).join(".");
233
+ }
234
+ const nameParts = serializeSqlExprList(name);
235
+ if (nameParts.length > 0) {
236
+ return nameParts.map(serializeSqlIdentifierNode).join(".");
237
+ }
238
+ }
239
+ return "";
240
+ }
241
+ function normalizeSqlIdentifier(identifier) {
242
+ return identifier.trim().toLowerCase();
243
+ }
244
+ function normalizeSqlOperator(operator) {
245
+ return (operator ?? "").trim().toUpperCase().replace(/\s+/g, " ");
246
+ }
247
+ function serializeUnknownSqlAstRecord(node) {
248
+ const entries = Object.entries(node).filter(([key]) => key !== "parentheses").toSorted(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}:${serializeIndexWhereAst(value)}`);
249
+ return `record:{${entries.join(",")}}`;
250
+ }
251
+ function isSqlAstRecord(value) {
252
+ return typeof value === "object" && value !== null && !Array.isArray(value);
253
+ }
254
+ function getSqlAstString(node, key) {
255
+ const value = node[key];
256
+ return typeof value === "string" ? value : undefined;
257
+ }
258
+ function findMatchingParenthesisInSql(source, openIndex) {
259
+ let depth = 0;
260
+ let inSingleQuote = false;
261
+ let inDoubleQuote = false;
262
+ for (let index = openIndex; index < source.length; index += 1) {
263
+ const char = source[index];
264
+ const nextChar = source[index + 1];
265
+ if (char === "'" && !inDoubleQuote) {
266
+ if (inSingleQuote && nextChar === "'") {
267
+ index += 1;
268
+ continue;
269
+ }
270
+ inSingleQuote = !inSingleQuote;
271
+ continue;
272
+ }
273
+ if (char === "\"" && !inSingleQuote) {
274
+ if (inDoubleQuote && nextChar === "\"") {
275
+ index += 1;
276
+ continue;
277
+ }
278
+ inDoubleQuote = !inDoubleQuote;
279
+ continue;
280
+ }
281
+ if (inSingleQuote || inDoubleQuote) {
282
+ continue;
283
+ }
284
+ if (char === "(") {
285
+ depth += 1;
286
+ continue;
287
+ }
288
+ if (char === ")") {
289
+ depth -= 1;
290
+ if (depth === 0) {
291
+ return index;
292
+ }
293
+ }
294
+ }
295
+ return -1;
296
+ }
297
+ var INDEX_WHERE_SQL_PARSER;
298
+ var init_index_where_predicate = __esmMin((() => {
299
+ INDEX_WHERE_SQL_PARSER = new SqlParser.Parser();
300
+ }));
301
+
302
+ //#endregion
303
+ init_index_where_predicate();
304
+ export { init_index_where_predicate, normalizeIndexWherePredicate, normalizeIndexWherePredicateForComparison };
305
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXgtd2hlcmUtcHJlZGljYXRlLmpzIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9taWdyYXRpb24vaW5kZXgtd2hlcmUtcHJlZGljYXRlLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBTcWxQYXJzZXIgZnJvbSBcIm5vZGUtc3FsLXBhcnNlclwiO1xuXG5jb25zdCBJTkRFWF9XSEVSRV9TUUxfUEFSU0VSID0gbmV3IFNxbFBhcnNlci5QYXJzZXIoKTtcblxuLyoqXG4gKiBNaWdyYXRpb24gRERMIOy2nOugpeyXkCDsgqzsmqntlaAgaW5kZXggcHJlZGljYXRl66W8IOygleumrO2VnOuLpC5cbiAqIOybkOuzuCBTUUwg7ZGc7ZiE7J2AIOuztOyhtO2VmOqzoCwg67mIIOusuOyekOyXtOqzvCDsoITssrTrpbwg6rCQ7Iu8IOq0hO2YuOunjCDsoJzqsbDtlZzri6QuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBub3JtYWxpemVJbmRleFdoZXJlUHJlZGljYXRlKHdoZXJlOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuICBpZiAoIXdoZXJlKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIC8vIOy2nOugpSDqsr3roZzsl5DshJzripQg7IKs7Jqp7J6Q6rCAIOyekeyEse2VnCBwcmVkaWNhdGXrpbwg7LWc64yA7ZWcIOq3uOuMgOuhnCDsnKDsp4DtlZzri6QuXG4gIGNvbnN0IHRyaW1tZWQgPSByZW1vdmVPdXRlclNxbFBhcmVudGhlc2VzKHdoZXJlLnRyaW0oKSk7XG4gIGlmICh0cmltbWVkLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICByZXR1cm4gdHJpbW1lZDtcbn1cblxuLyoqXG4gKiBJbmRleCBkaWZmIOu5hOq1kOyXkCDsgqzsmqntlaAgcHJlZGljYXRlIGlkZW50aXR566W8IOunjOuToOuLpC5cbiAqIFBvc3RncmVTUUwgY2Fub25pY2FsIO2RnO2YhCDssKjsnbTripQgQVNUIOq4sOuwmCDsoJXqt5ztmZTroZwg7Z2h7IiY7ZWc64ukLlxuICovXG5leHBvcnQgZnVuY3Rpb24gbm9ybWFsaXplSW5kZXhXaGVyZVByZWRpY2F0ZUZvckNvbXBhcmlzb24oXG4gIHdoZXJlOiBzdHJpbmcgfCB1bmRlZmluZWQsXG4pOiBzdHJpbmcgfCB1bmRlZmluZWQge1xuICBjb25zdCBub3JtYWxpemVkID0gbm9ybWFsaXplSW5kZXhXaGVyZVByZWRpY2F0ZSh3aGVyZSk7XG4gIGlmICghbm9ybWFsaXplZCkge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICAvLyDtjIzsi7Eg6rCA64ql7ZWcIHByZWRpY2F0ZeunjCBjYW5vbmljYWwgZm9ybeycvOuhnCDrsJTqvrjqs6AsIOyLpO2MqO2VmOuptCDquLDsobQgc3RyaWN0IOu5hOq1kOulvCDsnKDsp4DtlZzri6QuXG4gIHJldHVybiBub3JtYWxpemVJbmRleFdoZXJlUHJlZGljYXRlQnlBc3Qobm9ybWFsaXplZCkgPz8gbm9ybWFsaXplZDtcbn1cblxuZnVuY3Rpb24gcmVtb3ZlT3V0ZXJTcWxQYXJlbnRoZXNlcyhzb3VyY2U6IHN0cmluZyk6IHN0cmluZyB7XG4gIGxldCB0cmltbWVkID0gc291cmNlLnRyaW0oKTtcblxuICAvLyDsoITssrQgcHJlZGljYXRl66W8IOqwkOyLvCDqtITtmLjrp4wg7KCc6rGw7ZWY6rOgIOuCtOu2gCBncm91cGluZ+ydgCDqsbTrk5zrpqzsp4Ag7JWK64qU64ukLlxuICB3aGlsZSAodHJpbW1lZC5zdGFydHNXaXRoKFwiKFwiKSAmJiB0cmltbWVkLmVuZHNXaXRoKFwiKVwiKSkge1xuICAgIGNvbnN0IGNsb3NlSW5kZXggPSBmaW5kTWF0Y2hpbmdQYXJlbnRoZXNpc0luU3FsKHRyaW1tZWQsIDApO1xuICAgIGlmIChjbG9zZUluZGV4ID09PSB0cmltbWVkLmxlbmd0aCAtIDEpIHtcbiAgICAgIHRyaW1tZWQgPSB0cmltbWVkLnNsaWNlKDEsIC0xKS50cmltKCk7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBicmVhaztcbiAgfVxuXG4gIHJldHVybiB0cmltbWVkO1xufVxuXG5mdW5jdGlvbiBub3JtYWxpemVJbmRleFdoZXJlUHJlZGljYXRlQnlBc3Qod2hlcmU6IHN0cmluZyk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIHRyeSB7XG4gICAgLy8gbm9kZS1zcWwtcGFyc2Vy64qUIHJhdyBwcmVkaWNhdGXrpbwg7KeB7KCRIO2MjOyLse2VmOyngCDslYrsnLzrr4DroZwg7J6E7IucIFNFTEVDVCBXSEVSReuhnCDqsJDsi7zri6QuXG4gICAgY29uc3QgcGFyc2VkID0gSU5ERVhfV0hFUkVfU1FMX1BBUlNFUi5hc3RpZnkoXG4gICAgICBgU0VMRUNUICogRlJPTSBfX3NvbmFtdV9pbmRleF9wcmVkaWNhdGVfc291cmNlIFdIRVJFICR7d2hlcmV9YCxcbiAgICAgIHsgZGF0YWJhc2U6IFwicG9zdGdyZXNxbFwiIH0sXG4gICAgKTtcbiAgICBjb25zdCBzdGF0ZW1lbnQgPSBBcnJheS5pc0FycmF5KHBhcnNlZCkgPyBwYXJzZWRbMF0gOiBwYXJzZWQ7XG4gICAgaWYgKCFpc1NxbEFzdFJlY29yZChzdGF0ZW1lbnQpKSB7XG4gICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICAgIH1cblxuICAgIHJldHVybiBzZXJpYWxpemVJbmRleFdoZXJlQXN0KHN0YXRlbWVudC53aGVyZSk7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplSW5kZXhXaGVyZUFzdChub2RlOiB1bmtub3duKTogc3RyaW5nIHtcbiAgLy8gUG9zdGdyZVNRTOydtCBJTuydhCBBTlkoQVJSQVlbLi4uXSnroZwg7J6s7J6R7ISx7ZWY66+A66GcIG1lbWJlcnNoaXDsnYAg66i87KCAIO2KueyImCDsspjrpqztlZzri6QuXG4gIGNvbnN0IG1lbWJlcnNoaXAgPSBzZXJpYWxpemVTcWxNZW1iZXJzaGlwUHJlZGljYXRlKG5vZGUpO1xuICBpZiAobWVtYmVyc2hpcCkge1xuICAgIHJldHVybiBtZW1iZXJzaGlwO1xuICB9XG5cbiAgaWYgKCFpc1NxbEFzdFJlY29yZChub2RlKSkge1xuICAgIHJldHVybiBKU09OLnN0cmluZ2lmeShub2RlKTtcbiAgfVxuXG4gIGNvbnN0IHR5cGUgPSBnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJ0eXBlXCIpO1xuICBzd2l0Y2ggKHR5cGUpIHtcbiAgICBjYXNlIFwiYmluYXJ5X2V4cHJcIjoge1xuICAgICAgY29uc3Qgb3BlcmF0b3IgPSBub3JtYWxpemVTcWxPcGVyYXRvcihnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJvcGVyYXRvclwiKSk7XG4gICAgICBjb25zdCBsZWZ0ID0gc2VyaWFsaXplSW5kZXhXaGVyZUFzdChub2RlLmxlZnQpO1xuICAgICAgY29uc3QgcmlnaHQgPSBzZXJpYWxpemVJbmRleFdoZXJlQXN0KG5vZGUucmlnaHQpO1xuICAgICAgaWYgKG9wZXJhdG9yID09PSBcIkFORFwiIHx8IG9wZXJhdG9yID09PSBcIk9SXCIpIHtcbiAgICAgICAgLy8gQU5EL09S64qUIOyInOyEnOqwgCBkaWZmIOydmOuvuOulvCDrsJTqvrjsp4Ag7JWK7Jy866+A66GcIHN0YWJsZSBpZGVudGl0eeuhnCDsoJXroKztlZzri6QuXG4gICAgICAgIHJldHVybiBgKCR7W2xlZnQsIHJpZ2h0XS50b1NvcnRlZCgpLmpvaW4oYCAke29wZXJhdG9yfSBgKX0pYDtcbiAgICAgIH1cbiAgICAgIHJldHVybiBgKCR7bGVmdH0gJHtvcGVyYXRvcn0gJHtyaWdodH0pYDtcbiAgICB9XG4gICAgY2FzZSBcInVuYXJ5X2V4cHJcIjoge1xuICAgICAgY29uc3Qgb3BlcmF0b3IgPSBub3JtYWxpemVTcWxPcGVyYXRvcihnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJvcGVyYXRvclwiKSk7XG4gICAgICByZXR1cm4gYCgke29wZXJhdG9yfSAke3NlcmlhbGl6ZUluZGV4V2hlcmVBc3Qobm9kZS5leHByKX0pYDtcbiAgICB9XG4gICAgY2FzZSBcImNvbHVtbl9yZWZcIjoge1xuICAgICAgcmV0dXJuIHNlcmlhbGl6ZVNxbENvbHVtblJlZihub2RlKTtcbiAgICB9XG4gICAgY2FzZSBcInNpbmdsZV9xdW90ZV9zdHJpbmdcIjoge1xuICAgICAgcmV0dXJuIGBzdHJpbmc6JHtKU09OLnN0cmluZ2lmeShnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJ2YWx1ZVwiKSl9YDtcbiAgICB9XG4gICAgY2FzZSBcIm51bWJlclwiOiB7XG4gICAgICByZXR1cm4gYG51bWJlcjoke1N0cmluZyhub2RlLnZhbHVlKX1gO1xuICAgIH1cbiAgICBjYXNlIFwiYm9vbFwiOiB7XG4gICAgICByZXR1cm4gYGJvb2w6JHtTdHJpbmcobm9kZS52YWx1ZSkudG9Mb3dlckNhc2UoKX1gO1xuICAgIH1cbiAgICBjYXNlIFwibnVsbFwiOiB7XG4gICAgICByZXR1cm4gXCJudWxsXCI7XG4gICAgfVxuICAgIGNhc2UgXCJjYXN0XCI6IHtcbiAgICAgIGNvbnN0IHRhcmdldFR5cGUgPSBnZXRTcWxDYXN0VGFyZ2V0VHlwZShub2RlKTtcbiAgICAgIGNvbnN0IGV4cHIgPSBub2RlLmV4cHI7XG4gICAgICBpZiAodGFyZ2V0VHlwZSAmJiBpc1RleHRMaWtlU3FsVHlwZSh0YXJnZXRUeXBlKSAmJiBpc1NxbFN0cmluZ0xpdGVyYWxMaWtlKGV4cHIpKSB7XG4gICAgICAgIHJldHVybiBzZXJpYWxpemVJbmRleFdoZXJlQXN0KHN0cmlwVGV4dExpa2VTcWxDYXN0KGV4cHIpKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBgY2FzdCgke3NlcmlhbGl6ZUluZGV4V2hlcmVBc3QoZXhwcil9IGFzICR7dGFyZ2V0VHlwZSA/PyBcInVua25vd25cIn0pYDtcbiAgICB9XG4gICAgY2FzZSBcImZ1bmN0aW9uXCI6IHtcbiAgICAgIHJldHVybiBgZm46JHtnZXRTcWxGdW5jdGlvbk5hbWUobm9kZSl9KCR7c2VyaWFsaXplU3FsRXhwckxpc3Qobm9kZS5hcmdzKS5qb2luKFwiLFwiKX0pYDtcbiAgICB9XG4gICAgY2FzZSBcImFycmF5XCI6IHtcbiAgICAgIHJldHVybiBgYXJyYXk6WyR7c2VyaWFsaXplU3FsQXJyYXlFbGVtZW50cyhub2RlKS5tYXAoc2VyaWFsaXplSW5kZXhXaGVyZUFzdCkuam9pbihcIixcIil9XWA7XG4gICAgfVxuICAgIGNhc2UgXCJleHByX2xpc3RcIjoge1xuICAgICAgcmV0dXJuIGBsaXN0Olske3NlcmlhbGl6ZVNxbEV4cHJMaXN0KG5vZGUpLm1hcChzZXJpYWxpemVJbmRleFdoZXJlQXN0KS5qb2luKFwiLFwiKX1dYDtcbiAgICB9XG4gICAgY2FzZSBcImRlZmF1bHRcIjoge1xuICAgICAgcmV0dXJuIGBpZGVudDoke25vcm1hbGl6ZVNxbElkZW50aWZpZXIoZ2V0U3FsQXN0U3RyaW5nKG5vZGUsIFwidmFsdWVcIikgPz8gXCJcIil9YDtcbiAgICB9XG4gICAgZGVmYXVsdDoge1xuICAgICAgcmV0dXJuIHNlcmlhbGl6ZVVua25vd25TcWxBc3RSZWNvcmQobm9kZSk7XG4gICAgfVxuICB9XG59XG5cbmZ1bmN0aW9uIHNlcmlhbGl6ZVNxbE1lbWJlcnNoaXBQcmVkaWNhdGUobm9kZTogdW5rbm93bik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGlmICghaXNTcWxBc3RSZWNvcmQobm9kZSkgfHwgZ2V0U3FsQXN0U3RyaW5nKG5vZGUsIFwidHlwZVwiKSAhPT0gXCJiaW5hcnlfZXhwclwiKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIC8vIElOICguLi4p6rO8ID0gQU5ZKEFSUkFZWy4uLl0p66W8IOuPmeydvO2VnCB1bm9yZGVyZWQgbWVtYmVyc2hpcCDtkZztmITsnLzroZwg66ee7LaY64ukLlxuICBjb25zdCBvcGVyYXRvciA9IG5vcm1hbGl6ZVNxbE9wZXJhdG9yKGdldFNxbEFzdFN0cmluZyhub2RlLCBcIm9wZXJhdG9yXCIpKTtcbiAgY29uc3QgbGVmdCA9IHNlcmlhbGl6ZUluZGV4V2hlcmVBc3Qoc3RyaXBUZXh0TGlrZVNxbENhc3Qobm9kZS5sZWZ0KSk7XG4gIGNvbnN0IHZhbHVlcyA9ICgoKSA9PiB7XG4gICAgaWYgKG9wZXJhdG9yID09PSBcIklOXCIpIHtcbiAgICAgIHJldHVybiBzZXJpYWxpemVTcWxNZW1iZXJzaGlwVmFsdWVzKHNlcmlhbGl6ZVNxbEV4cHJMaXN0KG5vZGUucmlnaHQpKTtcbiAgICB9XG5cbiAgICBpZiAob3BlcmF0b3IgPT09IFwiPVwiKSB7XG4gICAgICByZXR1cm4gc2VyaWFsaXplU3FsTWVtYmVyc2hpcFZhbHVlcyhnZXRTcWxBbnlBcnJheUVsZW1lbnRzKG5vZGUucmlnaHQpKTtcbiAgICB9XG5cbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9KSgpO1xuXG4gIGlmICghdmFsdWVzKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIHJldHVybiBgbWVtYmVyc2hpcDoke2xlZnR9OmluOlske3ZhbHVlcy5qb2luKFwiLFwiKX1dYDtcbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplU3FsTWVtYmVyc2hpcFZhbHVlcyh2YWx1ZXM6IHVua25vd25bXSB8IHVuZGVmaW5lZCk6IHN0cmluZ1tdIHwgdW5kZWZpbmVkIHtcbiAgaWYgKCF2YWx1ZXMpIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgLy8gbWVtYmVyc2hpcCDqsJLsnZgg7Iic7ISc7JmAIOykkeuzteydgCDsnZjrr7jqsIAg7JeG7Jy866+A66GcIOyViOygleyggeyduCBpZGVudGl0eeuhnCDsoJXroKztlZzri6QuXG4gIGNvbnN0IHNlcmlhbGl6ZWQgPSB2YWx1ZXMubWFwKCh2YWx1ZSkgPT4gc2VyaWFsaXplSW5kZXhXaGVyZUFzdChzdHJpcFRleHRMaWtlU3FsQ2FzdCh2YWx1ZSkpKTtcbiAgcmV0dXJuIFsuLi5uZXcgU2V0KHNlcmlhbGl6ZWQpXS50b1NvcnRlZCgpO1xufVxuXG5mdW5jdGlvbiBnZXRTcWxBbnlBcnJheUVsZW1lbnRzKG5vZGU6IHVua25vd24pOiB1bmtub3duW10gfCB1bmRlZmluZWQge1xuICBpZiAoIWlzU3FsQXN0UmVjb3JkKG5vZGUpIHx8IGdldFNxbEFzdFN0cmluZyhub2RlLCBcInR5cGVcIikgIT09IFwiZnVuY3Rpb25cIikge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbiAgaWYgKGdldFNxbEZ1bmN0aW9uTmFtZShub2RlKSAhPT0gXCJhbnlcIikge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICAvLyBBTlkoKeydmCDssqsg67KI7Ke4IOyduOyekOunjCBQb3N0Z3JlU1FMIOuwsOyXtCBtZW1iZXJzaGlwIOuMgOyDgeycvOuhnCDtlbTshJ3tlZzri6QuXG4gIGNvbnN0IFthcmddID0gc2VyaWFsaXplU3FsRXhwckxpc3Qobm9kZS5hcmdzKTtcbiAgaWYgKCFhcmcpIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgcmV0dXJuIHNlcmlhbGl6ZVNxbEFycmF5RWxlbWVudHMoc3RyaXBUZXh0TGlrZVNxbENhc3QoYXJnKSk7XG59XG5cbmZ1bmN0aW9uIHNlcmlhbGl6ZVNxbEFycmF5RWxlbWVudHMobm9kZTogdW5rbm93bik6IHVua25vd25bXSB7XG4gIGlmICghaXNTcWxBc3RSZWNvcmQobm9kZSkpIHtcbiAgICByZXR1cm4gW107XG4gIH1cblxuICAvLyBwYXJzZXIg67KE7KCE7JeQIOuUsOudvCDrsLDsl7Qg7JuQ7IaM6rCAIGFycmF5LmV4cHJfbGlzdCDrmJDripQgZXhwcl9saXN066GcIOuTpOyWtOyYqOuLpC5cbiAgaWYgKGdldFNxbEFzdFN0cmluZyhub2RlLCBcInR5cGVcIikgPT09IFwiYXJyYXlcIikge1xuICAgIHJldHVybiBzZXJpYWxpemVTcWxFeHByTGlzdChub2RlLmV4cHJfbGlzdCk7XG4gIH1cblxuICByZXR1cm4gc2VyaWFsaXplU3FsRXhwckxpc3Qobm9kZSk7XG59XG5cbmZ1bmN0aW9uIHNlcmlhbGl6ZVNxbEV4cHJMaXN0KG5vZGU6IHVua25vd24pOiB1bmtub3duW10ge1xuICBpZiAoIWlzU3FsQXN0UmVjb3JkKG5vZGUpKSB7XG4gICAgcmV0dXJuIFtdO1xuICB9XG5cbiAgLy8gZXhwcl9saXN064qUIOyLpOygnCBBU1Qg64W465OcIOuwsOyXtOydtOqzoCwg64uo7J28IO2RnO2YhOyLneydgCDrsLDsl7Qg7ZWY64KY66GcIOqwkOyLuCDthrXsnbztlZzri6QuXG4gIGlmIChnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJ0eXBlXCIpID09PSBcImV4cHJfbGlzdFwiICYmIEFycmF5LmlzQXJyYXkobm9kZS52YWx1ZSkpIHtcbiAgICByZXR1cm4gbm9kZS52YWx1ZTtcbiAgfVxuXG4gIHJldHVybiBbbm9kZV07XG59XG5cbmZ1bmN0aW9uIHN0cmlwVGV4dExpa2VTcWxDYXN0KG5vZGU6IHVua25vd24pOiB1bmtub3duIHtcbiAgaWYgKCFpc1NxbEFzdFJlY29yZChub2RlKSB8fCBnZXRTcWxBc3RTdHJpbmcobm9kZSwgXCJ0eXBlXCIpICE9PSBcImNhc3RcIikge1xuICAgIHJldHVybiBub2RlO1xuICB9XG5cbiAgLy8gUG9zdGdyZVNRTOydgCB2YXJjaGFyL3RleHQgY2FzdOulvCDrsLDsl7Qg65iQ64qUIOybkOyGjCDsqr3snLzroZwg7Jiu6ri4IOyImCDsnojslrQg67mE6rWQ7JeQ7IScIOygnOqxsO2VnOuLpC5cbiAgY29uc3QgdGFyZ2V0VHlwZSA9IGdldFNxbENhc3RUYXJnZXRUeXBlKG5vZGUpO1xuICBpZiAoIXRhcmdldFR5cGUgfHwgIWlzVGV4dExpa2VTcWxUeXBlKHRhcmdldFR5cGUpKSB7XG4gICAgcmV0dXJuIG5vZGU7XG4gIH1cblxuICByZXR1cm4gc3RyaXBUZXh0TGlrZVNxbENhc3Qobm9kZS5leHByKTtcbn1cblxuZnVuY3Rpb24gaXNTcWxTdHJpbmdMaXRlcmFsTGlrZShub2RlOiB1bmtub3duKTogYm9vbGVhbiB7XG4gIC8vIOusuOyekOyXtCBsaXRlcmFs7JeQIOu2meydgCB0ZXh0IOqzhOyXtCBjYXN064qUIG1lbWJlcnNoaXAg67mE6rWQ7JeQ7IScIOydmOuvuOqwgCDqsJnri6QuXG4gIGNvbnN0IHN0cmlwcGVkID0gc3RyaXBUZXh0TGlrZVNxbENhc3Qobm9kZSk7XG4gIHJldHVybiBpc1NxbEFzdFJlY29yZChzdHJpcHBlZCkgJiYgZ2V0U3FsQXN0U3RyaW5nKHN0cmlwcGVkLCBcInR5cGVcIikgPT09IFwic2luZ2xlX3F1b3RlX3N0cmluZ1wiO1xufVxuXG5mdW5jdGlvbiBnZXRTcWxDYXN0VGFyZ2V0VHlwZShub2RlOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gIGlmICghQXJyYXkuaXNBcnJheShub2RlLnRhcmdldCkpIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgLy8gY2hhcmFjdGVyIHZhcnlpbmfsspjrn7wg7Jes65+sIHRva2Vu7Jy866GcIOyYpOuKlCDtg4DsnoXrqoXsnYQg7ZWY64KY7J2YIOygleq3nO2ZlCDrrLjsnpDsl7TroZwg7ZWp7Lmc64ukLlxuICBjb25zdCBkYXRhVHlwZXMgPSBub2RlLnRhcmdldFxuICAgIC5tYXAoKHRhcmdldCkgPT4gKGlzU3FsQXN0UmVjb3JkKHRhcmdldCkgPyBnZXRTcWxBc3RTdHJpbmcodGFyZ2V0LCBcImRhdGFUeXBlXCIpIDogdW5kZWZpbmVkKSlcbiAgICAuZmlsdGVyKChkYXRhVHlwZSk6IGRhdGFUeXBlIGlzIHN0cmluZyA9PiBkYXRhVHlwZSAhPT0gdW5kZWZpbmVkKTtcblxuICBpZiAoZGF0YVR5cGVzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICByZXR1cm4gbm9ybWFsaXplU3FsRGF0YVR5cGUoZGF0YVR5cGVzLmpvaW4oXCIgXCIpKTtcbn1cblxuZnVuY3Rpb24gaXNUZXh0TGlrZVNxbFR5cGUodHlwZTogc3RyaW5nKTogYm9vbGVhbiB7XG4gIC8vIOuwsOyXtCBjYXN0KHRleHRbXSnrj4Qg7JuQ7IaMIG1lbWJlcnNoaXAg67mE6rWQ7JeQ7ISc64qUIHRleHQg6rOE7Je0IGNhc3TroZwg7Leo6riJ7ZWc64ukLlxuICBjb25zdCBub3JtYWxpemVkID0gbm9ybWFsaXplU3FsRGF0YVR5cGUodHlwZSkucmVwbGFjZSgvXFxbXFxdJC9nLCBcIlwiKTtcbiAgcmV0dXJuIG5vcm1hbGl6ZWQgPT09IFwidGV4dFwiIHx8IG5vcm1hbGl6ZWQgPT09IFwidmFyY2hhclwiO1xufVxuXG5mdW5jdGlvbiBub3JtYWxpemVTcWxEYXRhVHlwZSh0eXBlOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gdHlwZVxuICAgIC50cmltKClcbiAgICAudG9Mb3dlckNhc2UoKVxuICAgIC5yZXBsYWNlKC9cXHMrL2csIFwiIFwiKVxuICAgIC5yZXBsYWNlKC9eY2hhcmFjdGVyIHZhcnlpbmckLywgXCJ2YXJjaGFyXCIpO1xufVxuXG5mdW5jdGlvbiBzZXJpYWxpemVTcWxDb2x1bW5SZWYobm9kZTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pOiBzdHJpbmcge1xuICBjb25zdCB0YWJsZSA9IHNlcmlhbGl6ZVNxbElkZW50aWZpZXJOb2RlKG5vZGUudGFibGUpO1xuICBjb25zdCBjb2x1bW4gPSBzZXJpYWxpemVTcWxJZGVudGlmaWVyTm9kZShub2RlLmNvbHVtbik7XG4gIHJldHVybiBgY29sdW1uOiR7dGFibGUgPyBgJHt0YWJsZX0uYCA6IFwiXCJ9JHtjb2x1bW59YDtcbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplU3FsSWRlbnRpZmllck5vZGUobm9kZTogdW5rbm93bik6IHN0cmluZyB7XG4gIGlmICh0eXBlb2Ygbm9kZSA9PT0gXCJzdHJpbmdcIikge1xuICAgIHJldHVybiBub3JtYWxpemVTcWxJZGVudGlmaWVyKG5vZGUpO1xuICB9XG5cbiAgLy8gcXVvdGVkIGlkZW50aWZpZXLsmYAgZnVuY3Rpb24gbmFtZeydmCBBU1Qgc2hhcGUg7LCo7J2066W8IOqwmeydgCBpZGVudGlmaWVyIOusuOyekOyXtOuhnCDtnaHsiJjtlZzri6QuXG4gIGlmIChpc1NxbEFzdFJlY29yZChub2RlKSkge1xuICAgIGlmICh0eXBlb2Ygbm9kZS52YWx1ZSA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgcmV0dXJuIG5vcm1hbGl6ZVNxbElkZW50aWZpZXIobm9kZS52YWx1ZSk7XG4gICAgfVxuXG4gICAgaWYgKGlzU3FsQXN0UmVjb3JkKG5vZGUuZXhwcikgJiYgdHlwZW9mIG5vZGUuZXhwci52YWx1ZSA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgcmV0dXJuIG5vcm1hbGl6ZVNxbElkZW50aWZpZXIobm9kZS5leHByLnZhbHVlKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gXCJcIjtcbn1cblxuZnVuY3Rpb24gZ2V0U3FsRnVuY3Rpb25OYW1lKG5vZGU6IFJlY29yZDxzdHJpbmcsIHVua25vd24+KTogc3RyaW5nIHtcbiAgY29uc3QgbmFtZSA9IG5vZGUubmFtZTtcbiAgaWYgKHR5cGVvZiBuYW1lID09PSBcInN0cmluZ1wiKSB7XG4gICAgcmV0dXJuIG5vcm1hbGl6ZVNxbElkZW50aWZpZXIobmFtZSk7XG4gIH1cblxuICAvLyBBTlnsspjrn7wgbmFtZS5uYW1lIOuwsOyXtOuhnCDsmKTripQgZnVuY3Rpb24gQVNU66W8IHNjaGVtYS1xdWFsaWZpZWQg7J2066aE6rmM7KeAIOyngOybkO2VnOuLpC5cbiAgaWYgKGlzU3FsQXN0UmVjb3JkKG5hbWUpKSB7XG4gICAgaWYgKEFycmF5LmlzQXJyYXkobmFtZS5uYW1lKSkge1xuICAgICAgcmV0dXJuIG5hbWUubmFtZS5tYXAoc2VyaWFsaXplU3FsSWRlbnRpZmllck5vZGUpLmpvaW4oXCIuXCIpO1xuICAgIH1cblxuICAgIGNvbnN0IG5hbWVQYXJ0cyA9IHNlcmlhbGl6ZVNxbEV4cHJMaXN0KG5hbWUpO1xuICAgIGlmIChuYW1lUGFydHMubGVuZ3RoID4gMCkge1xuICAgICAgcmV0dXJuIG5hbWVQYXJ0cy5tYXAoc2VyaWFsaXplU3FsSWRlbnRpZmllck5vZGUpLmpvaW4oXCIuXCIpO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBcIlwiO1xufVxuXG5mdW5jdGlvbiBub3JtYWxpemVTcWxJZGVudGlmaWVyKGlkZW50aWZpZXI6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBpZGVudGlmaWVyLnRyaW0oKS50b0xvd2VyQ2FzZSgpO1xufVxuXG5mdW5jdGlvbiBub3JtYWxpemVTcWxPcGVyYXRvcihvcGVyYXRvcjogc3RyaW5nIHwgdW5kZWZpbmVkKTogc3RyaW5nIHtcbiAgcmV0dXJuIChvcGVyYXRvciA/PyBcIlwiKS50cmltKCkudG9VcHBlckNhc2UoKS5yZXBsYWNlKC9cXHMrL2csIFwiIFwiKTtcbn1cblxuZnVuY3Rpb24gc2VyaWFsaXplVW5rbm93blNxbEFzdFJlY29yZChub2RlOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IHN0cmluZyB7XG4gIC8vIOyVhOyngSDrqoXsi5zsoIHsnLzroZwg64uk66Oo7KeAIOyViuuKlCBBU1Qg64W465Oc64qUIGtleSDsoJXroKzroZwg7LWc7IaM7ZWcIOyViOygleyggeyduCDruYTqtZDqsJLsnYQg66eM65Og64ukLlxuICBjb25zdCBlbnRyaWVzID0gT2JqZWN0LmVudHJpZXMobm9kZSlcbiAgICAuZmlsdGVyKChba2V5XSkgPT4ga2V5ICE9PSBcInBhcmVudGhlc2VzXCIpXG4gICAgLnRvU29ydGVkKChbbGVmdF0sIFtyaWdodF0pID0+IGxlZnQubG9jYWxlQ29tcGFyZShyaWdodCkpXG4gICAgLm1hcCgoW2tleSwgdmFsdWVdKSA9PiBgJHtrZXl9OiR7c2VyaWFsaXplSW5kZXhXaGVyZUFzdCh2YWx1ZSl9YCk7XG5cbiAgcmV0dXJuIGByZWNvcmQ6eyR7ZW50cmllcy5qb2luKFwiLFwiKX19YDtcbn1cblxuZnVuY3Rpb24gaXNTcWxBc3RSZWNvcmQodmFsdWU6IHVua25vd24pOiB2YWx1ZSBpcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB7XG4gIHJldHVybiB0eXBlb2YgdmFsdWUgPT09IFwib2JqZWN0XCIgJiYgdmFsdWUgIT09IG51bGwgJiYgIUFycmF5LmlzQXJyYXkodmFsdWUpO1xufVxuXG5mdW5jdGlvbiBnZXRTcWxBc3RTdHJpbmcobm9kZTogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sIGtleTogc3RyaW5nKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgdmFsdWUgPSBub2RlW2tleV07XG4gIHJldHVybiB0eXBlb2YgdmFsdWUgPT09IFwic3RyaW5nXCIgPyB2YWx1ZSA6IHVuZGVmaW5lZDtcbn1cblxuZnVuY3Rpb24gZmluZE1hdGNoaW5nUGFyZW50aGVzaXNJblNxbChzb3VyY2U6IHN0cmluZywgb3BlbkluZGV4OiBudW1iZXIpOiBudW1iZXIge1xuICBsZXQgZGVwdGggPSAwO1xuICBsZXQgaW5TaW5nbGVRdW90ZSA9IGZhbHNlO1xuICBsZXQgaW5Eb3VibGVRdW90ZSA9IGZhbHNlO1xuXG4gIC8vIFNRTCDrrLjsnpDsl7Tqs7wgcXVvdGVkIGlkZW50aWZpZXIg64K067aAIOq0hO2YuOuKlCBncm91cGluZyDqtITtmLjroZwg7IS47KeAIOyViuuKlOuLpC5cbiAgZm9yIChsZXQgaW5kZXggPSBvcGVuSW5kZXg7IGluZGV4IDwgc291cmNlLmxlbmd0aDsgaW5kZXggKz0gMSkge1xuICAgIGNvbnN0IGNoYXIgPSBzb3VyY2VbaW5kZXhdO1xuICAgIGNvbnN0IG5leHRDaGFyID0gc291cmNlW2luZGV4ICsgMV07XG5cbiAgICBpZiAoY2hhciA9PT0gXCInXCIgJiYgIWluRG91YmxlUXVvdGUpIHtcbiAgICAgIGlmIChpblNpbmdsZVF1b3RlICYmIG5leHRDaGFyID09PSBcIidcIikge1xuICAgICAgICBpbmRleCArPSAxO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGluU2luZ2xlUXVvdGUgPSAhaW5TaW5nbGVRdW90ZTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGlmIChjaGFyID09PSAnXCInICYmICFpblNpbmdsZVF1b3RlKSB7XG4gICAgICBpZiAoaW5Eb3VibGVRdW90ZSAmJiBuZXh0Q2hhciA9PT0gJ1wiJykge1xuICAgICAgICBpbmRleCArPSAxO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cbiAgICAgIGluRG91YmxlUXVvdGUgPSAhaW5Eb3VibGVRdW90ZTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGlmIChpblNpbmdsZVF1b3RlIHx8IGluRG91YmxlUXVvdGUpIHtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGlmIChjaGFyID09PSBcIihcIikge1xuICAgICAgZGVwdGggKz0gMTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGlmIChjaGFyID09PSBcIilcIikge1xuICAgICAgZGVwdGggLT0gMTtcbiAgICAgIGlmIChkZXB0aCA9PT0gMCkge1xuICAgICAgICByZXR1cm4gaW5kZXg7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIC0xO1xufVxuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7OztBQVFBLFNBQWdCLDZCQUE2QixPQUErQztBQUMxRixLQUFJLENBQUMsT0FBTztBQUNWLFNBQU87O0NBSVQsTUFBTSxVQUFVLDBCQUEwQixNQUFNLE1BQU0sQ0FBQztBQUN2RCxLQUFJLFFBQVEsV0FBVyxHQUFHO0FBQ3hCLFNBQU87O0FBR1QsUUFBTzs7Ozs7O0FBT1QsU0FBZ0IsMENBQ2QsT0FDb0I7Q0FDcEIsTUFBTSxhQUFhLDZCQUE2QixNQUFNO0FBQ3RELEtBQUksQ0FBQyxZQUFZO0FBQ2YsU0FBTzs7QUFJVCxRQUFPLGtDQUFrQyxXQUFXLElBQUk7O0FBRzFELFNBQVMsMEJBQTBCLFFBQXdCO0NBQ3pELElBQUksVUFBVSxPQUFPLE1BQU07QUFHM0IsUUFBTyxRQUFRLFdBQVcsSUFBSSxJQUFJLFFBQVEsU0FBUyxJQUFJLEVBQUU7RUFDdkQsTUFBTSxhQUFhLDZCQUE2QixTQUFTLEVBQUU7QUFDM0QsTUFBSSxlQUFlLFFBQVEsU0FBUyxHQUFHO0FBQ3JDLGFBQVUsUUFBUSxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUMsTUFBTTtBQUNyQzs7QUFHRjs7QUFHRixRQUFPOztBQUdULFNBQVMsa0NBQWtDLE9BQW1DO0FBQzVFLEtBQUk7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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonamu",
3
- "version": "0.9.17",
3
+ "version": "0.9.18",
4
4
  "description": "Sonamu — TypeScript Fullstack API Framework",
5
5
  "keywords": [
6
6
  "framework",
@@ -129,10 +129,10 @@
129
129
  "tsicli": "^1.0.5",
130
130
  "vite": "8.0.5",
131
131
  "vitest": "^4.1.2",
132
- "@sonamu-kit/hmr-hook": "^0.5.1",
133
- "@sonamu-kit/tasks": "^0.3.0",
132
+ "@sonamu-kit/hmr-runner": "^0.2.0",
134
133
  "@sonamu-kit/ts-loader": "^2.2.0",
135
- "@sonamu-kit/hmr-runner": "^0.2.0"
134
+ "@sonamu-kit/hmr-hook": "^0.5.1",
135
+ "@sonamu-kit/tasks": "^0.3.0"
136
136
  },
137
137
  "devDependencies": {
138
138
  "@types/bcrypt": "^6",
@@ -488,6 +488,46 @@ describe("code-generation partial index DDL", () => {
488
488
  expect(alterIndexesTo.drop).toHaveLength(0);
489
489
  });
490
490
 
491
+ test("partial index predicate의 text cast 위치 차이는 alter diff에서 no-op이어야 한다", () => {
492
+ const entityIndex: MigrationIndex = {
493
+ type: "index",
494
+ name: "file_descriptions_idx_context_created_at",
495
+ using: "btree",
496
+ columns: [{ name: "context" }, { name: "created_at" }],
497
+ where:
498
+ "(context)::text = ANY ((ARRAY['ast_result'::character varying, 'culture_result'::character varying])::text[])",
499
+ };
500
+ const dbIndex: MigrationIndex = {
501
+ ...setMigrationIndexDefaults(entityIndex),
502
+ where:
503
+ "context = ANY (ARRAY[('ast_result'::character varying)::text, ('culture_result'::character varying)::text])",
504
+ };
505
+
506
+ const alterIndexesTo = getAlterIndexesTo([entityIndex], [dbIndex]);
507
+
508
+ expect(alterIndexesTo.add).toHaveLength(0);
509
+ expect(alterIndexesTo.drop).toHaveLength(0);
510
+ });
511
+
512
+ test("partial index predicate의 IN과 ANY 동치 표현은 alter diff에서 no-op이어야 한다", () => {
513
+ const entityIndex: MigrationIndex = {
514
+ type: "index",
515
+ name: "file_descriptions_idx_context_created_at",
516
+ columns: [{ name: "context" }, { name: "created_at" }],
517
+ where: "context IN ('ast_result', 'culture_result')",
518
+ };
519
+ const dbIndex: MigrationIndex = {
520
+ ...setMigrationIndexDefaults(entityIndex),
521
+ where:
522
+ "context = ANY (ARRAY[('ast_result'::character varying)::text, ('culture_result'::character varying)::text])",
523
+ };
524
+
525
+ const alterIndexesTo = getAlterIndexesTo([entityIndex], [dbIndex]);
526
+
527
+ expect(alterIndexesTo.add).toHaveLength(0);
528
+ expect(alterIndexesTo.drop).toHaveLength(0);
529
+ });
530
+
491
531
  test("partial index predicate 변경은 alter diff에서 drop/add 대상이어야 한다", async () => {
492
532
  const previousIndex: MigrationIndex = {
493
533
  type: "index",
@@ -15,6 +15,10 @@ import {
15
15
  import { isSearchTextProp } from "../types/types";
16
16
  import { formatCode } from "../utils/formatter";
17
17
  import { differenceWith, intersectionBy } from "../utils/utils";
18
+ import {
19
+ normalizeIndexWherePredicate,
20
+ normalizeIndexWherePredicateForComparison,
21
+ } from "./index-where-predicate";
18
22
  import { PostgreSQLSchemaReader } from "./postgresql-schema-reader";
19
23
 
20
24
  /**
@@ -1612,15 +1616,28 @@ export function getAlterIndexesTo(entityIndexes: MigrationIndex[], dbIndexes: Mi
1612
1616
 
1613
1617
  const normalizedEntityIndexes = entityIndexes.map(setMigrationIndexDefaults);
1614
1618
  const normalizedDbIndexes = dbIndexes.map(setMigrationIndexDefaults);
1619
+ // 비교에는 정규화된 predicate를 쓰되, 실제 migration 출력은 원본 SQL을 보존한다.
1620
+ const comparisonEntityIndexes = entityIndexes.map(setMigrationIndexComparisonDefaults);
1621
+ const comparisonDbIndexes = dbIndexes.map(setMigrationIndexComparisonDefaults);
1615
1622
  const extraIndexes = {
1616
- db: diff(normalizedDbIndexes, normalizedEntityIndexes, identity),
1617
- entity: diff(normalizedEntityIndexes, normalizedDbIndexes, identity),
1623
+ db: diff(comparisonDbIndexes, comparisonEntityIndexes, identity),
1624
+ entity: diff(comparisonEntityIndexes, comparisonDbIndexes, identity),
1618
1625
  };
1619
1626
  if (extraIndexes.entity.length > 0) {
1620
- indexesTo.add = indexesTo.add.concat(extraIndexes.entity);
1627
+ const addIdentities = new Set(extraIndexes.entity.map(identity));
1628
+ indexesTo.add = indexesTo.add.concat(
1629
+ normalizedEntityIndexes.filter((_, index) =>
1630
+ addIdentities.has(identity(comparisonEntityIndexes[index])),
1631
+ ),
1632
+ );
1621
1633
  }
1622
1634
  if (extraIndexes.db.length > 0) {
1623
- indexesTo.drop = indexesTo.drop.concat(extraIndexes.db);
1635
+ const dropIdentities = new Set(extraIndexes.db.map(identity));
1636
+ indexesTo.drop = indexesTo.drop.concat(
1637
+ normalizedDbIndexes.filter((_, index) =>
1638
+ dropIdentities.has(identity(comparisonDbIndexes[index])),
1639
+ ),
1640
+ );
1624
1641
  }
1625
1642
 
1626
1643
  return indexesTo;
@@ -1639,10 +1656,24 @@ function genIndexDropDefinition(index: MigrationIndex) {
1639
1656
  * DB 조회 결과와 비교하기 위한 인덱스 기본값 설정
1640
1657
  */
1641
1658
  export function setMigrationIndexDefaults(index: MigrationIndex): MigrationIndex {
1659
+ return setMigrationIndexDefaultsBase(index, false);
1660
+ }
1661
+
1662
+ function setMigrationIndexComparisonDefaults(index: MigrationIndex): MigrationIndex {
1663
+ // DB canonical SQL과 entity SQL의 표현 차이를 diff identity에서만 흡수한다.
1664
+ return setMigrationIndexDefaultsBase(index, true);
1665
+ }
1666
+
1667
+ function setMigrationIndexDefaultsBase(
1668
+ index: MigrationIndex,
1669
+ normalizeWhereForComparison: boolean,
1670
+ ): MigrationIndex {
1642
1671
  const isVectorIndex = index.type === "hnsw" || index.type === "ivfflat";
1643
1672
  const supportsOrdering = !isVectorIndex && (!index.using || index.using === "btree");
1644
1673
  const normalizedUsing = isVectorIndex ? index.using : (index.using ?? "btree");
1645
- const normalizedWhere = normalizeIndexWherePredicate(index.where);
1674
+ const normalizedWhere = normalizeWhereForComparison
1675
+ ? normalizeIndexWherePredicateForComparison(index.where)
1676
+ : normalizeIndexWherePredicate(index.where);
1646
1677
 
1647
1678
  return {
1648
1679
  ...index,
@@ -1662,73 +1693,6 @@ export function setMigrationIndexDefaults(index: MigrationIndex): MigrationIndex
1662
1693
  };
1663
1694
  }
1664
1695
 
1665
- function normalizeIndexWherePredicate(where: string | undefined): string | undefined {
1666
- if (!where) {
1667
- return undefined;
1668
- }
1669
-
1670
- const trimmed = where.trim();
1671
- if (trimmed.length === 0) {
1672
- return undefined;
1673
- }
1674
-
1675
- if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
1676
- const closeIndex = findMatchingParenthesisInSql(trimmed, 0);
1677
- if (closeIndex === trimmed.length - 1) {
1678
- return trimmed.slice(1, -1).trim();
1679
- }
1680
- }
1681
-
1682
- return trimmed;
1683
- }
1684
-
1685
- function findMatchingParenthesisInSql(source: string, openIndex: number): number {
1686
- let depth = 0;
1687
- let inSingleQuote = false;
1688
- let inDoubleQuote = false;
1689
-
1690
- for (let index = openIndex; index < source.length; index += 1) {
1691
- const char = source[index];
1692
- const nextChar = source[index + 1];
1693
-
1694
- if (char === "'" && !inDoubleQuote) {
1695
- if (inSingleQuote && nextChar === "'") {
1696
- index += 1;
1697
- continue;
1698
- }
1699
- inSingleQuote = !inSingleQuote;
1700
- continue;
1701
- }
1702
-
1703
- if (char === '"' && !inSingleQuote) {
1704
- if (inDoubleQuote && nextChar === '"') {
1705
- index += 1;
1706
- continue;
1707
- }
1708
- inDoubleQuote = !inDoubleQuote;
1709
- continue;
1710
- }
1711
-
1712
- if (inSingleQuote || inDoubleQuote) {
1713
- continue;
1714
- }
1715
-
1716
- if (char === "(") {
1717
- depth += 1;
1718
- continue;
1719
- }
1720
-
1721
- if (char === ")") {
1722
- depth -= 1;
1723
- if (depth === 0) {
1724
- return index;
1725
- }
1726
- }
1727
- }
1728
-
1729
- return -1;
1730
- }
1731
-
1732
1696
  /**
1733
1697
  * 테이블 변경 케이스 - Foreign Key 변경
1734
1698
  */