@@ -34,11 +34,16 @@ export class GeneratedColumnQueryPostgres extends GeneratedColumnQueryAbstract {
3434 return depth === 0 ;
3535 }
3636
37- private isNumericLiteral ( expr : string ) : boolean {
37+ private stripOuterParentheses ( expr : string ) : string {
3838 let trimmed = expr . trim ( ) ;
3939 while ( trimmed . length > 0 && this . hasWrappingParentheses ( trimmed ) ) {
4040 trimmed = trimmed . slice ( 1 , - 1 ) . trim ( ) ;
4141 }
42+ return trimmed ;
43+ }
44+
45+ private isNumericLiteral ( expr : string ) : boolean {
46+ const trimmed = this . stripOuterParentheses ( expr ) ;
4247 // eslint-disable-next-line regexp/no-unused-capturing-group
4348 return / ^ [ - + ] ? \d + ( \. \d + ) ? $ / . test ( trimmed ) ;
4449 }
@@ -53,7 +58,8 @@ export class GeneratedColumnQueryPostgres extends GeneratedColumnQueryAbstract {
5358 }
5459
5560 private normalizeBlankComparable ( value : string ) : string {
56- return `COALESCE(NULLIF((${ value } )::text, ''), '')` ;
61+ const comparable = this . coerceToTextComparable ( value ) ;
62+ return `COALESCE(NULLIF(${ comparable } , ''), '')` ;
5763 }
5864
5965 private ensureTextCollation ( expr : string ) : string {
@@ -63,7 +69,26 @@ export class GeneratedColumnQueryPostgres extends GeneratedColumnQueryAbstract {
6369 private buildBlankAwareComparison ( operator : '=' | '<>' , left : string , right : string ) : string {
6470 const shouldNormalize = this . isEmptyStringLiteral ( left ) || this . isEmptyStringLiteral ( right ) ;
6571 if ( ! shouldNormalize ) {
66- return `(${ left } ${ operator } ${ right } )` ;
72+ const leftIsText = this . isTextLikeExpression ( left ) ;
73+ const rightIsText = this . isTextLikeExpression ( right ) ;
74+
75+ let normalizedLeft = left ;
76+ let normalizedRight = right ;
77+
78+ if ( leftIsText ) {
79+ normalizedLeft = this . ensureTextCollation ( left ) ;
80+ }
81+ if ( rightIsText ) {
82+ normalizedRight = this . ensureTextCollation ( right ) ;
83+ }
84+
85+ if ( leftIsText && ! rightIsText ) {
86+ normalizedRight = this . coerceToTextComparable ( right ) ;
87+ } else if ( ! leftIsText && rightIsText ) {
88+ normalizedLeft = this . coerceToTextComparable ( left ) ;
89+ }
90+
91+ return `(${ normalizedLeft } ${ operator } ${ normalizedRight } )` ;
6792 }
6893
6994 const normalizedLeft = this . isEmptyStringLiteral ( left )
@@ -77,13 +102,13 @@ export class GeneratedColumnQueryPostgres extends GeneratedColumnQueryAbstract {
77102 }
78103
79104 private isTextLikeExpression ( value : string ) : boolean {
80- const trimmed = value . trim ( ) ;
105+ const trimmed = this . stripOuterParentheses ( value ) ;
81106 if ( / ^ ' .* ' $ / . test ( trimmed ) ) {
82107 return true ;
83108 }
84109
85- const columnMatch = trimmed . match ( / ^ " ( [ ^ " ] + ) " $ / ) ;
86- if ( ! columnMatch ) {
110+ const columnMatch = trimmed . match ( / ^ " ( [ ^ " ] + ) " $ / ) ?? trimmed . match ( / ^ " [ ^ " ] + " \. " ( [ ^ " ] + ) " $ / ) ;
111+ if ( ! columnMatch || columnMatch . length < 2 ) {
87112 return false ;
88113 }
89114
@@ -99,6 +124,35 @@ export class GeneratedColumnQueryPostgres extends GeneratedColumnQueryAbstract {
99124 return field . dbFieldType === DbFieldType . Text ;
100125 }
101126
127+ private coerceToTextComparable ( value : string ) : string {
128+ const trimmed = this . stripOuterParentheses ( value ) ;
129+ if ( ! trimmed ) {
130+ return this . ensureTextCollation ( value ) ;
131+ }
132+ if ( / ^ ' .* ' $ / . test ( trimmed ) ) {
133+ return this . ensureTextCollation ( trimmed ) ;
134+ }
135+ if ( trimmed . toUpperCase ( ) === 'NULL' ) {
136+ return 'NULL' ;
137+ }
138+
139+ const wrapped = `(${ value } )` ;
140+ const coerced = `(CASE
141+ WHEN ${ wrapped } IS NULL THEN NULL
142+ WHEN pg_typeof(${ wrapped } ) = ANY(ARRAY['jsonb'::regtype, 'json'::regtype]) THEN (
143+ CASE jsonb_typeof((${ wrapped } )::jsonb)
144+ WHEN 'string' THEN (${ wrapped } )::jsonb #>> '{}'
145+ WHEN 'number' THEN (${ wrapped } )::jsonb #>> '{}'
146+ WHEN 'boolean' THEN (${ wrapped } )::jsonb #>> '{}'
147+ WHEN 'null' THEN NULL
148+ ELSE (${ wrapped } )::jsonb::text
149+ END
150+ )
151+ ELSE ${ wrapped } ::text
152+ END)` ;
153+ return this . ensureTextCollation ( coerced ) ;
154+ }
155+
102156 private countANonNullExpression ( value : string ) : string {
103157 if ( this . isTextLikeExpression ( value ) ) {
104158 const normalizedComparable = this . normalizeBlankComparable ( value ) ;
0 commit comments