|
1 | | -import { RESERVED_KEYWORDS, TYPE_FUNC_NAME_KEYWORDS } from '../kwlist'; |
| 1 | +import { RESERVED_KEYWORDS, TYPE_FUNC_NAME_KEYWORDS, UNRESERVED_KEYWORDS, keywordKindOf } from '../kwlist'; |
2 | 2 |
|
3 | 3 | export class QuoteUtils { |
4 | 4 | /** |
@@ -105,5 +105,88 @@ export class QuoteUtils { |
105 | 105 | // unless it's a raw \x... bytea-style literal. |
106 | 106 | return !/^\\x[0-9a-fA-F]+$/i.test(value) && value.includes('\\'); |
107 | 107 | } |
| 108 | + |
| 109 | + /** |
| 110 | + * Quote an identifier only if needed |
| 111 | + * |
| 112 | + * This is a TypeScript port of PostgreSQL's quote_identifier() function from ruleutils.c |
| 113 | + * https://github.com/postgres/postgres/blob/fab5cd3dd1323f9e66efeb676c4bb212ff340204/src/backend/utils/adt/ruleutils.c#L13055-L13137 |
| 114 | + * |
| 115 | + * Can avoid quoting if ident starts with a lowercase letter or underscore |
| 116 | + * and contains only lowercase letters, digits, and underscores, *and* is |
| 117 | + * not any SQL keyword. Otherwise, supply quotes. |
| 118 | + * |
| 119 | + * When quotes are needed, embedded double quotes are properly escaped as "". |
| 120 | + */ |
| 121 | + static quoteIdentifier(ident: string): string { |
| 122 | + if (!ident) return ident; |
| 123 | + |
| 124 | + let nquotes = 0; |
| 125 | + let safe = true; |
| 126 | + |
| 127 | + // Check first character: must be lowercase letter or underscore |
| 128 | + const firstChar = ident[0]; |
| 129 | + if (!((firstChar >= 'a' && firstChar <= 'z') || firstChar === '_')) { |
| 130 | + safe = false; |
| 131 | + } |
| 132 | + |
| 133 | + // Check all characters |
| 134 | + for (let i = 0; i < ident.length; i++) { |
| 135 | + const ch = ident[i]; |
| 136 | + if ((ch >= 'a' && ch <= 'z') || |
| 137 | + (ch >= '0' && ch <= '9') || |
| 138 | + (ch === '_')) { |
| 139 | + // okay |
| 140 | + } else { |
| 141 | + safe = false; |
| 142 | + if (ch === '"') { |
| 143 | + nquotes++; |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + if (safe) { |
| 149 | + // Check for keyword. We quote keywords except for unreserved ones. |
| 150 | + // (In some cases we could avoid quoting a col_name or type_func_name |
| 151 | + // keyword, but it seems much harder than it's worth to tell that.) |
| 152 | + const kwKind = keywordKindOf(ident); |
| 153 | + if (kwKind !== 'NO_KEYWORD' && kwKind !== 'UNRESERVED_KEYWORD') { |
| 154 | + safe = false; |
| 155 | + } |
| 156 | + } |
| 157 | + |
| 158 | + if (safe) { |
| 159 | + return ident; // no change needed |
| 160 | + } |
| 161 | + |
| 162 | + // Build quoted identifier with escaped embedded quotes |
| 163 | + let result = '"'; |
| 164 | + for (let i = 0; i < ident.length; i++) { |
| 165 | + const ch = ident[i]; |
| 166 | + if (ch === '"') { |
| 167 | + result += '"'; // escape " as "" |
| 168 | + } |
| 169 | + result += ch; |
| 170 | + } |
| 171 | + result += '"'; |
| 172 | + |
| 173 | + return result; |
| 174 | + } |
| 175 | + |
| 176 | + /** |
| 177 | + * Quote a possibly-qualified identifier |
| 178 | + * |
| 179 | + * This is a TypeScript port of PostgreSQL's quote_qualified_identifier() function from ruleutils.c |
| 180 | + * https://github.com/postgres/postgres/blob/fab5cd3dd1323f9e66efeb676c4bb212ff340204/src/backend/utils/adt/ruleutils.c#L13139-L13156 |
| 181 | + * |
| 182 | + * Return a name of the form qualifier.ident, or just ident if qualifier |
| 183 | + * is null/undefined, quoting each component if necessary. |
| 184 | + */ |
| 185 | + static quoteQualifiedIdentifier(qualifier: string | null | undefined, ident: string): string { |
| 186 | + if (qualifier) { |
| 187 | + return `${QuoteUtils.quoteIdentifier(qualifier)}.${QuoteUtils.quoteIdentifier(ident)}`; |
| 188 | + } |
| 189 | + return QuoteUtils.quoteIdentifier(ident); |
| 190 | + } |
108 | 191 |
|
109 | 192 | } |
0 commit comments