From c6a3d3a1854d62733c34dafe1593d4701805c8a7 Mon Sep 17 00:00:00 2001 From: Pavel Kudinov Date: Sat, 28 Feb 2026 20:20:46 -0800 Subject: [PATCH 1/7] fix(orm): fallback to compact temp aliases for overlong names --- .../executor/zenstack-query-executor.ts | 29 +++- packages/orm/src/client/options.ts | 5 +- tests/regression/test/issue-2424.test.ts | 161 ++++++++++++++++++ 3 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 tests/regression/test/issue-2424.test.ts diff --git a/packages/orm/src/client/executor/zenstack-query-executor.ts b/packages/orm/src/client/executor/zenstack-query-executor.ts index b0aab7627..a01d80493 100644 --- a/packages/orm/src/client/executor/zenstack-query-executor.ts +++ b/packages/orm/src/client/executor/zenstack-query-executor.ts @@ -20,6 +20,8 @@ import { ValueNode, ValuesNode, WhereNode, + IdentifierNode, + OperationNodeTransformer, type ConnectionProvider, type DatabaseConnection, type DialectAdapter, @@ -37,12 +39,32 @@ import { getCrudDialect } from '../crud/dialects'; import type { BaseCrudDialect } from '../crud/dialects/base-dialect'; import { createDBQueryError, createInternalError, ORMError } from '../errors'; import type { AfterEntityMutationCallback, OnKyselyQueryCallback } from '../plugin'; -import { requireIdFields, stripAlias } from '../query-utils'; +import { requireIdFields, stripAlias, TEMP_ALIAS_PREFIX } from '../query-utils'; import { QueryNameMapper } from './name-mapper'; import { TempAliasTransformer } from './temp-alias-transformer'; import type { ZenStackDriver } from './zenstack-driver'; type MutationQueryNode = InsertQueryNode | UpdateQueryNode | DeleteQueryNode; +// PostgreSQL limits identifier length to 63 bytes and silently truncates longer names. +// Overlong temp aliases can collide after truncation, so we switch to compact aliases past this threshold. +const MAX_SAFE_TEMP_ALIAS_LENGTH = 63; + +class TempAliasLengthChecker extends OperationNodeTransformer { + private maxTempAliasLength = 0; + + run(node: OperationNode) { + this.maxTempAliasLength = 0; + this.transformNode(node); + return this.maxTempAliasLength; + } + + protected override transformIdentifier(node: IdentifierNode, queryId?: QueryId): IdentifierNode { + if (node.name.startsWith(TEMP_ALIAS_PREFIX)) { + this.maxTempAliasLength = Math.max(this.maxTempAliasLength, node.name.length); + } + return super.transformIdentifier(node, queryId); + } +} type MutationInfo = { model: string; @@ -634,7 +656,10 @@ In such cases, ZenStack cannot reliably determine the IDs of the mutated entitie private processTempAlias(query: Node): Node { if (this.options.useCompactAliasNames === false) { - return query; + const maxTempAliasLength = new TempAliasLengthChecker().run(query); + if (maxTempAliasLength <= MAX_SAFE_TEMP_ALIAS_LENGTH) { + return query; + } } return new TempAliasTransformer().run(query); } diff --git a/packages/orm/src/client/options.ts b/packages/orm/src/client/options.ts index 4601598b2..3e3f6c57a 100644 --- a/packages/orm/src/client/options.ts +++ b/packages/orm/src/client/options.ts @@ -199,8 +199,11 @@ export type ClientOptions = QueryOptions & { validateInput?: boolean; /** - * Whether to use compact alias names (e.g., "$t1", "$t2") when transforming ORM queries to SQL. + * Whether to use compact alias names (e.g., "$$t1", "$$t2") when transforming ORM queries to SQL. * Defaults to `true`. + * + * When set to `false`, original aliases are kept unless temporary aliases become too long for + * safe SQL identifier handling, in which case compact aliases are used as a fallback. */ useCompactAliasNames?: boolean; } & (HasComputedFields extends true diff --git a/tests/regression/test/issue-2424.test.ts b/tests/regression/test/issue-2424.test.ts new file mode 100644 index 000000000..0bfd3e4da --- /dev/null +++ b/tests/regression/test/issue-2424.test.ts @@ -0,0 +1,161 @@ +import { createPolicyTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +describe('Regression for issue #2424', () => { + it('deep nested include with PolicyPlugin works with non-compact alias mode', async () => { + const db = await createPolicyTestClient( + ` +model Store { + id String @id + customerOrders CustomerOrder[] + productCatalogItems ProductCatalogItem[] + @@allow('all', true) +} + +model CustomerOrder { + id String @id + storeId String + store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) + customerOrderPaymentSummary CustomerOrderPaymentSummary[] + @@allow('all', true) +} + +model CustomerOrderPaymentSummary { + id String @id + customerOrderId String + customerOrder CustomerOrder @relation(fields: [customerOrderId], references: [id], onDelete: Cascade) + customerOrderPaymentSummaryLine CustomerOrderPaymentSummaryLine[] + @@allow('all', true) +} + +model PaymentTransaction { + id String @id + customerOrderPaymentSummaryLine CustomerOrderPaymentSummaryLine[] + paymentTransactionLineItem PaymentTransactionLineItem[] + @@allow('all', true) +} + +model CustomerOrderPaymentSummaryLine { + customerOrderPaymentSummaryId String + lineIndex Int + paymentTransactionId String + customerOrderPaymentSummary CustomerOrderPaymentSummary @relation(fields: [customerOrderPaymentSummaryId], references: [id], onDelete: Cascade) + paymentTransaction PaymentTransaction @relation(fields: [paymentTransactionId], references: [id], onDelete: Cascade) + @@id([customerOrderPaymentSummaryId, lineIndex]) + @@allow('all', true) +} + +model ProductCatalogItem { + storeId String + sku String + store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) + paymentTransactionLineItem PaymentTransactionLineItem[] + @@id([storeId, sku]) + @@allow('all', true) +} + +model InventoryReservation { + id String @id + paymentTransactionLineItem PaymentTransactionLineItem[] + @@allow('all', true) +} + +model PaymentTransactionLineItem { + paymentTransactionId String + lineNumber Int + storeId String + productSku String + inventoryReservationId String? + paymentTransaction PaymentTransaction @relation(fields: [paymentTransactionId], references: [id], onDelete: Cascade) + productCatalogItem ProductCatalogItem @relation(fields: [storeId, productSku], references: [storeId, sku]) + inventoryReservation InventoryReservation? @relation(fields: [inventoryReservationId], references: [id], onDelete: SetNull) + @@id([paymentTransactionId, lineNumber]) + @@allow('all', true) +} + `, + { provider: 'postgresql', useCompactAliasNames: false }, + ); + + const rawDb = db.$unuseAll(); + + await rawDb.store.create({ data: { id: 'store_1' } }); + await rawDb.customerOrder.create({ data: { id: 'order_1', storeId: 'store_1' } }); + await rawDb.customerOrderPaymentSummary.create({ data: { id: 'summary_1', customerOrderId: 'order_1' } }); + await rawDb.paymentTransaction.create({ data: { id: 'payment_1' } }); + await rawDb.customerOrderPaymentSummaryLine.create({ + data: { + customerOrderPaymentSummaryId: 'summary_1', + lineIndex: 0, + paymentTransactionId: 'payment_1', + }, + }); + await rawDb.productCatalogItem.create({ data: { storeId: 'store_1', sku: 'sku_1' } }); + await rawDb.inventoryReservation.create({ data: { id: 'reservation_1' } }); + await rawDb.paymentTransactionLineItem.create({ + data: { + paymentTransactionId: 'payment_1', + lineNumber: 0, + storeId: 'store_1', + productSku: 'sku_1', + inventoryReservationId: 'reservation_1', + }, + }); + + const result = await db.customerOrderPaymentSummary.findUnique({ + where: { id: 'summary_1' }, + include: { + customerOrder: true, + customerOrderPaymentSummaryLine: { + include: { + paymentTransaction: { + include: { + paymentTransactionLineItem: { + include: { + productCatalogItem: true, + inventoryReservation: true, + }, + }, + }, + }, + }, + }, + }, + }); + + expect(result).toMatchObject({ + id: 'summary_1', + customerOrder: { + id: 'order_1', + storeId: 'store_1', + }, + customerOrderPaymentSummaryLine: [ + { + customerOrderPaymentSummaryId: 'summary_1', + lineIndex: 0, + paymentTransactionId: 'payment_1', + paymentTransaction: { + id: 'payment_1', + paymentTransactionLineItem: [ + { + paymentTransactionId: 'payment_1', + lineNumber: 0, + storeId: 'store_1', + productSku: 'sku_1', + inventoryReservationId: 'reservation_1', + productCatalogItem: { + storeId: 'store_1', + sku: 'sku_1', + }, + inventoryReservation: { + id: 'reservation_1', + }, + }, + ], + }, + }, + ], + }); + + await db.$disconnect(); + }); +}); From ff685cdc003a3414064621b0baab874206fbb3f4 Mon Sep 17 00:00:00 2001 From: Pavel Kudinov Date: Tue, 3 Mar 2026 18:25:58 -0800 Subject: [PATCH 2/7] refactor(orm): move temp alias length check into transformer --- .../client/executor/temp-alias-transformer.ts | 43 +++++++++++++++++++ .../executor/zenstack-query-executor.ts | 34 ++------------- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/packages/orm/src/client/executor/temp-alias-transformer.ts b/packages/orm/src/client/executor/temp-alias-transformer.ts index 1a66a260a..52eff84ca 100644 --- a/packages/orm/src/client/executor/temp-alias-transformer.ts +++ b/packages/orm/src/client/executor/temp-alias-transformer.ts @@ -1,15 +1,58 @@ import { IdentifierNode, OperationNodeTransformer, type OperationNode, type QueryId } from 'kysely'; import { TEMP_ALIAS_PREFIX } from '../query-utils'; +type TempAliasTransformerMode = 'alwaysCompact' | 'compactLongNames'; + +type TempAliasTransformerOptions = { + mode?: TempAliasTransformerMode; + maxIdentifierLength?: number; +}; + +class TempAliasLengthChecker extends OperationNodeTransformer { + private hasOverlongTempAlias = false; + + constructor(private readonly maxIdentifierLength: number) { + super(); + } + + run(node: OperationNode) { + this.hasOverlongTempAlias = false; + this.transformNode(node); + return this.hasOverlongTempAlias; + } + + protected override transformIdentifier(node: IdentifierNode): IdentifierNode { + if (node.name.startsWith(TEMP_ALIAS_PREFIX) && node.name.length > this.maxIdentifierLength) { + this.hasOverlongTempAlias = true; + } + return node; + } +} + /** * Kysely node transformer that replaces temporary aliases created during query construction with * shorter names while ensuring the same temp alias gets replaced with the same name. */ export class TempAliasTransformer extends OperationNodeTransformer { private aliasMap = new Map(); + private readonly mode: TempAliasTransformerMode; + private readonly maxIdentifierLength: number; + + constructor(options: TempAliasTransformerOptions = {}) { + super(); + this.mode = options.mode ?? 'alwaysCompact'; + // PostgreSQL limits identifier length to 63 bytes and silently truncates overlong aliases. + this.maxIdentifierLength = options.maxIdentifierLength ?? 63; + } run(node: T): T { this.aliasMap.clear(); + if (this.mode === 'compactLongNames') { + const hasOverlongAliases = new TempAliasLengthChecker(this.maxIdentifierLength).run(node); + if (!hasOverlongAliases) { + return node; + } + } return this.transformNode(node); } diff --git a/packages/orm/src/client/executor/zenstack-query-executor.ts b/packages/orm/src/client/executor/zenstack-query-executor.ts index a01d80493..52afde140 100644 --- a/packages/orm/src/client/executor/zenstack-query-executor.ts +++ b/packages/orm/src/client/executor/zenstack-query-executor.ts @@ -20,8 +20,6 @@ import { ValueNode, ValuesNode, WhereNode, - IdentifierNode, - OperationNodeTransformer, type ConnectionProvider, type DatabaseConnection, type DialectAdapter, @@ -39,32 +37,12 @@ import { getCrudDialect } from '../crud/dialects'; import type { BaseCrudDialect } from '../crud/dialects/base-dialect'; import { createDBQueryError, createInternalError, ORMError } from '../errors'; import type { AfterEntityMutationCallback, OnKyselyQueryCallback } from '../plugin'; -import { requireIdFields, stripAlias, TEMP_ALIAS_PREFIX } from '../query-utils'; +import { requireIdFields, stripAlias } from '../query-utils'; import { QueryNameMapper } from './name-mapper'; import { TempAliasTransformer } from './temp-alias-transformer'; import type { ZenStackDriver } from './zenstack-driver'; type MutationQueryNode = InsertQueryNode | UpdateQueryNode | DeleteQueryNode; -// PostgreSQL limits identifier length to 63 bytes and silently truncates longer names. -// Overlong temp aliases can collide after truncation, so we switch to compact aliases past this threshold. -const MAX_SAFE_TEMP_ALIAS_LENGTH = 63; - -class TempAliasLengthChecker extends OperationNodeTransformer { - private maxTempAliasLength = 0; - - run(node: OperationNode) { - this.maxTempAliasLength = 0; - this.transformNode(node); - return this.maxTempAliasLength; - } - - protected override transformIdentifier(node: IdentifierNode, queryId?: QueryId): IdentifierNode { - if (node.name.startsWith(TEMP_ALIAS_PREFIX)) { - this.maxTempAliasLength = Math.max(this.maxTempAliasLength, node.name.length); - } - return super.transformIdentifier(node, queryId); - } -} type MutationInfo = { model: string; @@ -655,13 +633,9 @@ In such cases, ZenStack cannot reliably determine the IDs of the mutated entitie } private processTempAlias(query: Node): Node { - if (this.options.useCompactAliasNames === false) { - const maxTempAliasLength = new TempAliasLengthChecker().run(query); - if (maxTempAliasLength <= MAX_SAFE_TEMP_ALIAS_LENGTH) { - return query; - } - } - return new TempAliasTransformer().run(query); + return new TempAliasTransformer({ + mode: this.options.useCompactAliasNames === false ? 'compactLongNames' : 'alwaysCompact', + }).run(query); } private createClientForConnection(connection: DatabaseConnection, inTx: boolean) { From 5ec19f3417699b4c57e2e01f5f0e6ca804367735 Mon Sep 17 00:00:00 2001 From: Pavel Kudinov Date: Tue, 3 Mar 2026 19:49:40 -0800 Subject: [PATCH 3/7] chore(orm): validate temp alias maxIdentifierLength option --- packages/orm/src/client/executor/temp-alias-transformer.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/orm/src/client/executor/temp-alias-transformer.ts b/packages/orm/src/client/executor/temp-alias-transformer.ts index 52eff84ca..62e0d89e7 100644 --- a/packages/orm/src/client/executor/temp-alias-transformer.ts +++ b/packages/orm/src/client/executor/temp-alias-transformer.ts @@ -42,7 +42,11 @@ export class TempAliasTransformer extends OperationNodeTransformer { super(); this.mode = options.mode ?? 'alwaysCompact'; // PostgreSQL limits identifier length to 63 bytes and silently truncates overlong aliases. - this.maxIdentifierLength = options.maxIdentifierLength ?? 63; + const maxIdentifierLength = options.maxIdentifierLength ?? 63; + if (!Number.isFinite(maxIdentifierLength) || !Number.isInteger(maxIdentifierLength) || maxIdentifierLength <= 0) { + throw new RangeError('maxIdentifierLength must be a positive integer'); + } + this.maxIdentifierLength = maxIdentifierLength; } run(node: T): T { From 560c557372bf2d80f13c484093b0c64c4bf7f138 Mon Sep 17 00:00:00 2001 From: Pavel Kudinov Date: Tue, 3 Mar 2026 19:58:41 -0800 Subject: [PATCH 4/7] fix(orm): use utf-8 byte length for temp alias limit --- .../orm/src/client/executor/temp-alias-transformer.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/orm/src/client/executor/temp-alias-transformer.ts b/packages/orm/src/client/executor/temp-alias-transformer.ts index 62e0d89e7..3297d2dbd 100644 --- a/packages/orm/src/client/executor/temp-alias-transformer.ts +++ b/packages/orm/src/client/executor/temp-alias-transformer.ts @@ -10,6 +10,7 @@ type TempAliasTransformerOptions = { class TempAliasLengthChecker extends OperationNodeTransformer { private hasOverlongTempAlias = false; + private readonly textEncoder = new TextEncoder(); constructor(private readonly maxIdentifierLength: number) { super(); @@ -22,7 +23,12 @@ class TempAliasLengthChecker extends OperationNodeTransformer { } protected override transformIdentifier(node: IdentifierNode): IdentifierNode { - if (node.name.startsWith(TEMP_ALIAS_PREFIX) && node.name.length > this.maxIdentifierLength) { + if (!node.name.startsWith(TEMP_ALIAS_PREFIX)) { + return node; + } + + const aliasByteLength = this.textEncoder.encode(node.name).length; + if (aliasByteLength > this.maxIdentifierLength) { this.hasOverlongTempAlias = true; } return node; From 6cb00881db5809e245d779e1dfa0d3197f56ed87 Mon Sep 17 00:00:00 2001 From: Pavel Kudinov Date: Tue, 3 Mar 2026 20:06:37 -0800 Subject: [PATCH 5/7] perf(orm): short-circuit temp alias length traversal --- .../orm/src/client/executor/temp-alias-transformer.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/orm/src/client/executor/temp-alias-transformer.ts b/packages/orm/src/client/executor/temp-alias-transformer.ts index 3297d2dbd..d3fd87e1c 100644 --- a/packages/orm/src/client/executor/temp-alias-transformer.ts +++ b/packages/orm/src/client/executor/temp-alias-transformer.ts @@ -22,7 +22,18 @@ class TempAliasLengthChecker extends OperationNodeTransformer { return this.hasOverlongTempAlias; } + override transformNode(node: T, queryId?: QueryId): T { + if (this.hasOverlongTempAlias) { + return node; + } + return super.transformNode(node, queryId); + } + protected override transformIdentifier(node: IdentifierNode): IdentifierNode { + if (this.hasOverlongTempAlias) { + return node; + } + if (!node.name.startsWith(TEMP_ALIAS_PREFIX)) { return node; } From 76e9656847d1a272e36c2f3b14978b2c497bf869 Mon Sep 17 00:00:00 2001 From: Pavel Kudinov Date: Wed, 4 Mar 2026 11:13:29 -0800 Subject: [PATCH 6/7] refactor(orm): inline temp alias overflow detection in transformer --- .../client/executor/temp-alias-transformer.ts | 63 +++++++------------ 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/packages/orm/src/client/executor/temp-alias-transformer.ts b/packages/orm/src/client/executor/temp-alias-transformer.ts index d3fd87e1c..43e311954 100644 --- a/packages/orm/src/client/executor/temp-alias-transformer.ts +++ b/packages/orm/src/client/executor/temp-alias-transformer.ts @@ -8,52 +8,17 @@ type TempAliasTransformerOptions = { maxIdentifierLength?: number; }; -class TempAliasLengthChecker extends OperationNodeTransformer { - private hasOverlongTempAlias = false; - private readonly textEncoder = new TextEncoder(); - - constructor(private readonly maxIdentifierLength: number) { - super(); - } - - run(node: OperationNode) { - this.hasOverlongTempAlias = false; - this.transformNode(node); - return this.hasOverlongTempAlias; - } - - override transformNode(node: T, queryId?: QueryId): T { - if (this.hasOverlongTempAlias) { - return node; - } - return super.transformNode(node, queryId); - } - - protected override transformIdentifier(node: IdentifierNode): IdentifierNode { - if (this.hasOverlongTempAlias) { - return node; - } - - if (!node.name.startsWith(TEMP_ALIAS_PREFIX)) { - return node; - } - - const aliasByteLength = this.textEncoder.encode(node.name).length; - if (aliasByteLength > this.maxIdentifierLength) { - this.hasOverlongTempAlias = true; - } - return node; - } -} - /** * Kysely node transformer that replaces temporary aliases created during query construction with * shorter names while ensuring the same temp alias gets replaced with the same name. */ export class TempAliasTransformer extends OperationNodeTransformer { private aliasMap = new Map(); + private readonly textEncoder = new TextEncoder(); private readonly mode: TempAliasTransformerMode; private readonly maxIdentifierLength: number; + private detectOnly = false; + private hasOverlongTempAlias = false; constructor(options: TempAliasTransformerOptions = {}) { super(); @@ -69,7 +34,7 @@ export class TempAliasTransformer extends OperationNodeTransformer { run(node: T): T { this.aliasMap.clear(); if (this.mode === 'compactLongNames') { - const hasOverlongAliases = new TempAliasLengthChecker(this.maxIdentifierLength).run(node); + const hasOverlongAliases = this.detectOverlongTempAlias(node); if (!hasOverlongAliases) { return node; } @@ -77,7 +42,27 @@ export class TempAliasTransformer extends OperationNodeTransformer { return this.transformNode(node); } + private detectOverlongTempAlias(node: OperationNode) { + this.detectOnly = true; + this.hasOverlongTempAlias = false; + this.transformNode(node); + this.detectOnly = false; + return this.hasOverlongTempAlias; + } + protected override transformIdentifier(node: IdentifierNode, queryId?: QueryId): IdentifierNode { + if (this.detectOnly) { + if (!node.name.startsWith(TEMP_ALIAS_PREFIX)) { + return node; + } + + const aliasByteLength = this.textEncoder.encode(node.name).length; + if (aliasByteLength > this.maxIdentifierLength) { + this.hasOverlongTempAlias = true; + } + return node; + } + if (node.name.startsWith(TEMP_ALIAS_PREFIX)) { let mapped = this.aliasMap.get(node.name); if (!mapped) { From a19822bb1c177e019237a3b18ca49861c8a767d4 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 5 Mar 2026 21:37:35 -0800 Subject: [PATCH 7/7] refactor: further simplify long name processing --- .../client/executor/temp-alias-transformer.ts | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/packages/orm/src/client/executor/temp-alias-transformer.ts b/packages/orm/src/client/executor/temp-alias-transformer.ts index 43e311954..fc742b23a 100644 --- a/packages/orm/src/client/executor/temp-alias-transformer.ts +++ b/packages/orm/src/client/executor/temp-alias-transformer.ts @@ -17,15 +17,17 @@ export class TempAliasTransformer extends OperationNodeTransformer { private readonly textEncoder = new TextEncoder(); private readonly mode: TempAliasTransformerMode; private readonly maxIdentifierLength: number; - private detectOnly = false; - private hasOverlongTempAlias = false; constructor(options: TempAliasTransformerOptions = {}) { super(); this.mode = options.mode ?? 'alwaysCompact'; // PostgreSQL limits identifier length to 63 bytes and silently truncates overlong aliases. const maxIdentifierLength = options.maxIdentifierLength ?? 63; - if (!Number.isFinite(maxIdentifierLength) || !Number.isInteger(maxIdentifierLength) || maxIdentifierLength <= 0) { + if ( + !Number.isFinite(maxIdentifierLength) || + !Number.isInteger(maxIdentifierLength) || + maxIdentifierLength <= 0 + ) { throw new RangeError('maxIdentifierLength must be a positive integer'); } this.maxIdentifierLength = maxIdentifierLength; @@ -33,44 +35,35 @@ export class TempAliasTransformer extends OperationNodeTransformer { run(node: T): T { this.aliasMap.clear(); - if (this.mode === 'compactLongNames') { - const hasOverlongAliases = this.detectOverlongTempAlias(node); - if (!hasOverlongAliases) { - return node; - } - } return this.transformNode(node); } - private detectOverlongTempAlias(node: OperationNode) { - this.detectOnly = true; - this.hasOverlongTempAlias = false; - this.transformNode(node); - this.detectOnly = false; - return this.hasOverlongTempAlias; - } - protected override transformIdentifier(node: IdentifierNode, queryId?: QueryId): IdentifierNode { - if (this.detectOnly) { - if (!node.name.startsWith(TEMP_ALIAS_PREFIX)) { - return node; - } + if (!node.name.startsWith(TEMP_ALIAS_PREFIX)) { + return super.transformIdentifier(node, queryId); + } + let shouldCompact = false; + if (this.mode === 'alwaysCompact') { + shouldCompact = true; + } else { + // check if the alias name exceeds the max identifier length, and + // if so, compact it const aliasByteLength = this.textEncoder.encode(node.name).length; if (aliasByteLength > this.maxIdentifierLength) { - this.hasOverlongTempAlias = true; + shouldCompact = true; } - return node; } - if (node.name.startsWith(TEMP_ALIAS_PREFIX)) { + if (shouldCompact) { let mapped = this.aliasMap.get(node.name); if (!mapped) { mapped = `$$t${this.aliasMap.size + 1}`; this.aliasMap.set(node.name, mapped); } return IdentifierNode.create(mapped); + } else { + return super.transformIdentifier(node, queryId); } - return super.transformIdentifier(node, queryId); } }