UNPKG

@clickup/ent-framework

Version:

A PostgreSQL graph-database-alike library with microsharding and row-level security

94 lines (81 loc) 2.68 kB
/** * Parses composite row input into parts. See references at: * - https://www.postgresql.org/docs/current/rowtypes.html * - https://github.com/DmitryKoterov/db_type/blob/master/lib/DB/Type/Pgsql/Row.php * - unit tests of this function */ export function parseCompositeRow(str: string): Array<string | null> { let p = 0; let c = ""; function readCharAfterSpaces(): string { while (p < str.length) { const c = str[p]; if (c !== " " && c !== "\t" && c !== "\r" && c !== "\n") { break; } } return p < str.length ? str[p] : ""; } function throwError(message: string): void { throw Error(`${message} at position ${p} of "${str}"`); } // Leading "(". c = readCharAfterSpaces(); if (c !== "(") { throwError('An anonymous composite type row must start with "("'); } p++; // Check for immediate trailing ')': by convention, "()" translates to // ROW(NULL) tuple and not to an empty tuple (because ROW() and ROW(NULL) have // the same stringified representation which is "()" unfortunately). c = readCharAfterSpaces(); if (c === ")") { return [null]; } // Row may contain: // - "-quoted strings (escaping: ["] is doubled) // - unquoted strings (before first "," or ")") // - empty string (it is treated as NULL) // Nested rows and all other things are represented as strings. const reTillNext = /[^,)]*/sy; const reQuoted = /"((?:[^"]+|"")*)"/sy; const result: Array<string | null> = []; while (true) { // We read a value in this iteration, then - delimiter. c = readCharAfterSpaces(); // Always read a next element value. if (c === "," || c === ")") { // Comma or end of row instead of value: treat as NULL. result.push(null); } else if (c !== '"') { // Unquoted string. Notice that NULL here is treated as "NULL" string, but // NOT as a null value! This is how composite values are encoded by PG. reTillNext.lastIndex = p; const matches = reTillNext.exec(str)!; result.push(matches[0]); p += matches[0].length; } else { reQuoted.lastIndex = p; const matches = reQuoted.exec(str); if (matches) { // Quoted string. result.push(matches[1].replace(/""/g, '"').replace(/\\\\/g, "\\")); p += matches[0].length; } else { throwError("Expected a balanced quoted string"); } } // Delimiter or the end of row. c = readCharAfterSpaces(); if (c === ",") { p++; continue; } else if (c === ")") { p++; break; } else { throwError('Expected a delimiter "," or ")"'); } } return result; }