pgsql-deparser 17.14.0 → 17.15.0

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.
package/deparser.js CHANGED
@@ -1851,8 +1851,9 @@ class Deparser {
1851
1851
  return output.join(' ');
1852
1852
  }
1853
1853
  }
1854
- const quotedNames = names.map((name) => quote_utils_1.QuoteUtils.quoteIdentifier(name));
1855
- let result = mods(quotedNames.join('.'), args);
1854
+ // Use type-name quoting for non-pg_catalog types
1855
+ // This allows keywords like 'json', 'int', 'boolean' to remain unquoted in type positions
1856
+ let result = mods(quote_utils_1.QuoteUtils.quoteTypeDottedName(names), args);
1856
1857
  if (node.arrayBounds && node.arrayBounds.length > 0) {
1857
1858
  result += formatArrayBounds(node.arrayBounds);
1858
1859
  }
package/esm/deparser.js CHANGED
@@ -1848,8 +1848,9 @@ export class Deparser {
1848
1848
  return output.join(' ');
1849
1849
  }
1850
1850
  }
1851
- const quotedNames = names.map((name) => QuoteUtils.quoteIdentifier(name));
1852
- let result = mods(quotedNames.join('.'), args);
1851
+ // Use type-name quoting for non-pg_catalog types
1852
+ // This allows keywords like 'json', 'int', 'boolean' to remain unquoted in type positions
1853
+ let result = mods(QuoteUtils.quoteTypeDottedName(names), args);
1853
1854
  if (node.arrayBounds && node.arrayBounds.length > 0) {
1854
1855
  result += formatArrayBounds(node.arrayBounds);
1855
1856
  }
@@ -168,4 +168,73 @@ export class QuoteUtils {
168
168
  }
169
169
  return QuoteUtils.quoteIdentifier(ident);
170
170
  }
171
+ /**
172
+ * Quote an identifier that appears as a type name.
173
+ *
174
+ * Type names in PostgreSQL have a less strict quoting policy than standalone identifiers.
175
+ * In type positions, COL_NAME_KEYWORD and TYPE_FUNC_NAME_KEYWORD are allowed unquoted
176
+ * (e.g., 'json', 'int', 'boolean', 'interval'). Only RESERVED_KEYWORD must be quoted.
177
+ *
178
+ * This is different from:
179
+ * - quoteIdentifier(): quotes all keywords except UNRESERVED_KEYWORD
180
+ * - quoteIdentifierAfterDot(): only quotes for lexical reasons (no keyword checking)
181
+ *
182
+ * Type names still need quoting for lexical reasons (uppercase, special chars, etc.).
183
+ */
184
+ static quoteIdentifierTypeName(ident) {
185
+ if (!ident)
186
+ return ident;
187
+ let safe = true;
188
+ // Check first character: must be lowercase letter or underscore
189
+ const firstChar = ident[0];
190
+ if (!((firstChar >= 'a' && firstChar <= 'z') || firstChar === '_')) {
191
+ safe = false;
192
+ }
193
+ // Check all characters
194
+ for (let i = 0; i < ident.length; i++) {
195
+ const ch = ident[i];
196
+ if ((ch >= 'a' && ch <= 'z') ||
197
+ (ch >= '0' && ch <= '9') ||
198
+ (ch === '_')) {
199
+ // okay
200
+ }
201
+ else {
202
+ safe = false;
203
+ }
204
+ }
205
+ if (safe) {
206
+ // For type names, only quote RESERVED_KEYWORD
207
+ // COL_NAME_KEYWORD and TYPE_FUNC_NAME_KEYWORD are allowed unquoted in type positions
208
+ const kwKind = keywordKindOf(ident);
209
+ if (kwKind === 'RESERVED_KEYWORD') {
210
+ safe = false;
211
+ }
212
+ }
213
+ if (safe) {
214
+ return ident; // no change needed
215
+ }
216
+ // Build quoted identifier with escaped embedded quotes
217
+ let result = '"';
218
+ for (let i = 0; i < ident.length; i++) {
219
+ const ch = ident[i];
220
+ if (ch === '"') {
221
+ result += '"'; // escape " as ""
222
+ }
223
+ result += ch;
224
+ }
225
+ result += '"';
226
+ return result;
227
+ }
228
+ /**
229
+ * Quote a dotted type name (e.g., schema.typename).
230
+ *
231
+ * For type names, we use type-name quoting for all parts since the entire
232
+ * qualified name is in a type context. This allows keywords like 'json',
233
+ * 'int', 'boolean' to remain unquoted in user-defined schema-qualified types.
234
+ */
235
+ static quoteTypeDottedName(parts) {
236
+ if (!parts || parts.length === 0)
237
+ return '';
238
+ return parts.map(part => QuoteUtils.quoteIdentifierTypeName(part)).join('.');
239
+ }
171
240
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgsql-deparser",
3
- "version": "17.14.0",
3
+ "version": "17.15.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PostgreSQL AST Deparser",
6
6
  "main": "index.js",
@@ -60,5 +60,5 @@
60
60
  "dependencies": {
61
61
  "@pgsql/types": "^17.6.2"
62
62
  },
63
- "gitHead": "48ea4210dc676c26c3ca4de8650cdd64c4eb3bd3"
63
+ "gitHead": "df8eb8b89aee325cd9005599bc2ac67a746aade7"
64
64
  }
@@ -62,4 +62,26 @@ export declare class QuoteUtils {
62
62
  * is null/undefined, quoting each component if necessary.
63
63
  */
64
64
  static quoteQualifiedIdentifier(qualifier: string | null | undefined, ident: string): string;
65
+ /**
66
+ * Quote an identifier that appears as a type name.
67
+ *
68
+ * Type names in PostgreSQL have a less strict quoting policy than standalone identifiers.
69
+ * In type positions, COL_NAME_KEYWORD and TYPE_FUNC_NAME_KEYWORD are allowed unquoted
70
+ * (e.g., 'json', 'int', 'boolean', 'interval'). Only RESERVED_KEYWORD must be quoted.
71
+ *
72
+ * This is different from:
73
+ * - quoteIdentifier(): quotes all keywords except UNRESERVED_KEYWORD
74
+ * - quoteIdentifierAfterDot(): only quotes for lexical reasons (no keyword checking)
75
+ *
76
+ * Type names still need quoting for lexical reasons (uppercase, special chars, etc.).
77
+ */
78
+ static quoteIdentifierTypeName(ident: string): string;
79
+ /**
80
+ * Quote a dotted type name (e.g., schema.typename).
81
+ *
82
+ * For type names, we use type-name quoting for all parts since the entire
83
+ * qualified name is in a type context. This allows keywords like 'json',
84
+ * 'int', 'boolean' to remain unquoted in user-defined schema-qualified types.
85
+ */
86
+ static quoteTypeDottedName(parts: string[]): string;
65
87
  }
@@ -171,5 +171,74 @@ class QuoteUtils {
171
171
  }
172
172
  return QuoteUtils.quoteIdentifier(ident);
173
173
  }
174
+ /**
175
+ * Quote an identifier that appears as a type name.
176
+ *
177
+ * Type names in PostgreSQL have a less strict quoting policy than standalone identifiers.
178
+ * In type positions, COL_NAME_KEYWORD and TYPE_FUNC_NAME_KEYWORD are allowed unquoted
179
+ * (e.g., 'json', 'int', 'boolean', 'interval'). Only RESERVED_KEYWORD must be quoted.
180
+ *
181
+ * This is different from:
182
+ * - quoteIdentifier(): quotes all keywords except UNRESERVED_KEYWORD
183
+ * - quoteIdentifierAfterDot(): only quotes for lexical reasons (no keyword checking)
184
+ *
185
+ * Type names still need quoting for lexical reasons (uppercase, special chars, etc.).
186
+ */
187
+ static quoteIdentifierTypeName(ident) {
188
+ if (!ident)
189
+ return ident;
190
+ let safe = true;
191
+ // Check first character: must be lowercase letter or underscore
192
+ const firstChar = ident[0];
193
+ if (!((firstChar >= 'a' && firstChar <= 'z') || firstChar === '_')) {
194
+ safe = false;
195
+ }
196
+ // Check all characters
197
+ for (let i = 0; i < ident.length; i++) {
198
+ const ch = ident[i];
199
+ if ((ch >= 'a' && ch <= 'z') ||
200
+ (ch >= '0' && ch <= '9') ||
201
+ (ch === '_')) {
202
+ // okay
203
+ }
204
+ else {
205
+ safe = false;
206
+ }
207
+ }
208
+ if (safe) {
209
+ // For type names, only quote RESERVED_KEYWORD
210
+ // COL_NAME_KEYWORD and TYPE_FUNC_NAME_KEYWORD are allowed unquoted in type positions
211
+ const kwKind = (0, kwlist_1.keywordKindOf)(ident);
212
+ if (kwKind === 'RESERVED_KEYWORD') {
213
+ safe = false;
214
+ }
215
+ }
216
+ if (safe) {
217
+ return ident; // no change needed
218
+ }
219
+ // Build quoted identifier with escaped embedded quotes
220
+ let result = '"';
221
+ for (let i = 0; i < ident.length; i++) {
222
+ const ch = ident[i];
223
+ if (ch === '"') {
224
+ result += '"'; // escape " as ""
225
+ }
226
+ result += ch;
227
+ }
228
+ result += '"';
229
+ return result;
230
+ }
231
+ /**
232
+ * Quote a dotted type name (e.g., schema.typename).
233
+ *
234
+ * For type names, we use type-name quoting for all parts since the entire
235
+ * qualified name is in a type context. This allows keywords like 'json',
236
+ * 'int', 'boolean' to remain unquoted in user-defined schema-qualified types.
237
+ */
238
+ static quoteTypeDottedName(parts) {
239
+ if (!parts || parts.length === 0)
240
+ return '';
241
+ return parts.map(part => QuoteUtils.quoteIdentifierTypeName(part)).join('.');
242
+ }
174
243
  }
175
244
  exports.QuoteUtils = QuoteUtils;