Skip to content

Commit

Permalink
fix(postgres): cast VARCHAR[] columns (#16481)
Browse files Browse the repository at this point in the history
  • Loading branch information
papandreou committed Sep 11, 2023
1 parent ef95e6c commit df30331
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 23 deletions.
3 changes: 1 addition & 2 deletions packages/core/src/dialects/postgres/data-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,7 @@ export class ARRAY<T extends BaseTypes.AbstractDataType<any>> extends BaseTypes.
const mappedValues = isString(type) ? values : values.map(value => type.escape(value));

// Types that don't need to specify their cast
const unambiguousType = type instanceof BaseTypes.STRING
|| type instanceof BaseTypes.TEXT
const unambiguousType = type instanceof BaseTypes.TEXT
|| type instanceof BaseTypes.INTEGER;

const cast = mappedValues.length === 0 || !unambiguousType ? `::${attributeTypeToSql(type)}[]` : '';
Expand Down
19 changes: 19 additions & 0 deletions packages/core/test/unit/data-types/arrays.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,25 @@ describe('DataTypes.ARRAY', () => {
return;
}

it('does not not add cast to array of TEXT', () => {
expectsql(queryGenerator.escape([
'foo',
'bar',
], { type: DataTypes.ARRAY(DataTypes.TEXT) }), {
postgres: `ARRAY['foo','bar']`,
});
});

// Regression test for https://github.com/sequelize/sequelize/issues/16391
it('adds cast to array of VARCHAR', () => {
expectsql(queryGenerator.escape([
'foo',
'bar',
], { type: DataTypes.ARRAY(DataTypes.STRING(64)) }), {
postgres: `ARRAY['foo','bar']::VARCHAR(64)[]`,
});
});

it('escapes array of JSON', () => {
expectsql(queryGenerator.escape([
{ some: 'nested', more: { nested: true }, answer: 42 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ if (dialect.startsWith('postgres')) {
}, {
title: 'string in array should escape \' as \'\'',
arguments: ['myTable', { where: { aliases: { [Op.contains]: ['Queen\'s'] } } }],
expectation: 'SELECT * FROM "myTable" WHERE "myTable"."aliases" @> ARRAY[\'Queen\'\'s\'];',
expectation: 'SELECT * FROM "myTable" WHERE "myTable"."aliases" @> ARRAY[\'Queen\'\'s\']::VARCHAR(255)[];',
},

// Variants when quoteIdentifiers is false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('QueryGenerator#jsonPathExtractionQuery', () => {
default: notSupportedError,
mariadb: `json_compact(json_extract(\`profile\`,'$.id.username[0]."0".name'))`,
'mysql sqlite': `json_extract(\`profile\`,'$.id.username[0]."0".name')`,
postgres: `"profile"#>ARRAY['id','username','0','0','name']`,
postgres: `"profile"#>ARRAY['id','username','0','0','name']::VARCHAR(255)[]`,
});
});

Expand All @@ -44,7 +44,7 @@ describe('QueryGenerator#jsonPathExtractionQuery', () => {
mysql: `json_extract(\`profile\`,'$."\\\\""."\\'"."$"')`,
mariadb: `json_compact(json_extract(\`profile\`,'$."\\\\""."\\'"."$"'))`,
sqlite: `json_extract(\`profile\`,'$."\\""."''"."$"')`,
postgres: `"profile"#>ARRAY['"','''','$']`,
postgres: `"profile"#>ARRAY['"','''','$']::VARCHAR(255)[]`,
});
});
}
Expand Down Expand Up @@ -75,7 +75,7 @@ describe('QueryGenerator#jsonPathExtractionQuery', () => {
default: notSupportedError,
mssql: `JSON_VALUE([profile], N'$.id.username[0]."0".name')`,
'mysql mariadb sqlite': `json_unquote(json_extract(\`profile\`,'$.id.username[0]."0".name'))`,
postgres: `"profile"#>>ARRAY['id','username','0','0','name']`,
postgres: `"profile"#>>ARRAY['id','username','0','0','name']::VARCHAR(255)[]`,
});
});
}
Expand Down
9 changes: 5 additions & 4 deletions packages/core/test/unit/sql/literal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('json', () => {
};

expectsql(() => queryGenerator.escape(json(conditions)), {
postgres: `("metadata"->'language' = '"icelandic"' AND "metadata"#>ARRAY['pg_rating','dk'] = '"G"') AND "another_json_field"->'x' = '1'`,
postgres: `("metadata"->'language' = '"icelandic"' AND "metadata"#>ARRAY['pg_rating','dk']::VARCHAR(255)[] = '"G"') AND "another_json_field"->'x' = '1'`,
sqlite: `(json_extract(\`metadata\`,'$.language') = '"icelandic"' AND json_extract(\`metadata\`,'$.pg_rating.dk') = '"G"') AND json_extract(\`another_json_field\`,'$.x') = '1'`,
mariadb: `(json_compact(json_extract(\`metadata\`,'$.language')) = '"icelandic"' AND json_compact(json_extract(\`metadata\`,'$.pg_rating.dk')) = '"G"') AND json_compact(json_extract(\`another_json_field\`,'$.x')) = '1'`,
mysql: `(json_extract(\`metadata\`,'$.language') = CAST('"icelandic"' AS JSON) AND json_extract(\`metadata\`,'$.pg_rating.dk') = CAST('"G"' AS JSON)) AND json_extract(\`another_json_field\`,'$.x') = CAST('1' AS JSON)`,
Expand All @@ -30,15 +30,15 @@ describe('json', () => {
const path = 'metadata.pg_rating.dk';

expectsql(() => queryGenerator.escape(json(path)), {
postgres: `"metadata"#>ARRAY['pg_rating','dk']`,
postgres: `"metadata"#>ARRAY['pg_rating','dk']::VARCHAR(255)[]`,
mariadb: `json_compact(json_extract(\`metadata\`,'$.pg_rating.dk'))`,
'sqlite mysql': `json_extract(\`metadata\`,'$.pg_rating.dk')`,
});
});

it('supports numbers in the dot notation', () => {
expectsql(() => queryGenerator.escape(json('profile.id.0.1')), {
postgres: `"profile"#>ARRAY['id','0','1']`,
postgres: `"profile"#>ARRAY['id','0','1']::VARCHAR(255)[]`,
mariadb: `json_compact(json_extract(\`profile\`,'$.id."0"."1"'))`,
'sqlite mysql': `json_extract(\`profile\`,'$.id."0"."1"')`,
});
Expand All @@ -49,7 +49,7 @@ describe('json', () => {
const value = 'U';

expectsql(() => queryGenerator.escape(json(path, value)), {
postgres: `"metadata"#>ARRAY['pg_rating','is'] = '"U"'`,
postgres: `"metadata"#>ARRAY['pg_rating','is']::VARCHAR(255)[] = '"U"'`,
sqlite: `json_extract(\`metadata\`,'$.pg_rating.is') = '"U"'`,
mariadb: `json_compact(json_extract(\`metadata\`,'$.pg_rating.is')) = '"U"'`,
mysql: `json_extract(\`metadata\`,'$.pg_rating.is') = CAST('"U"' AS JSON)`,
Expand Down Expand Up @@ -147,6 +147,7 @@ describe('fn', () => {

expectsql(out, {
default: `concat(ARRAY['abc'])`,
postgres: `concat(ARRAY['abc']::VARCHAR(255)[])`,
});
});
});
27 changes: 14 additions & 13 deletions packages/core/test/unit/sql/where.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ describe(getTestDialectTeaser('SQL'), () => {
for (const [arrayOperator, arraySqlOperator] of arrayOperators) {
testSql({ [attributeName]: { [operator]: { [arrayOperator]: testWithValues } } }, {
default: `[${attributeName}] ${sqlOperator} ${arraySqlOperator} (ARRAY[${testWithValues.map(v => util.inspect(v)).join(',')}])`,
postgres: `"${attributeName}" ${sqlOperator} ${arraySqlOperator} (ARRAY[${testWithValues.map(v => util.inspect(v)).join(',')}]${attributeName === 'stringAttr' ? '::VARCHAR(255)[]' : ''})`,
});

testSql({ [attributeName]: { [operator]: { [arrayOperator]: literal('literal') } } }, {
Expand Down Expand Up @@ -2039,7 +2040,7 @@ Caused by: "undefined" cannot be escaped`),
});

testSql({ 'jsonAttr.nested.twice': 'value' }, {
postgres: `"jsonAttr"#>ARRAY['nested','twice'] = '"value"'`,
postgres: `"jsonAttr"#>ARRAY['nested','twice']::VARCHAR(255)[] = '"value"'`,
sqlite: `json_extract(\`jsonAttr\`,'$.nested.twice') = '"value"'`,
mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested.twice')) = '"value"'`,
mysql: `json_extract(\`jsonAttr\`,'$.nested.twice') = CAST('"value"' AS JSON)`,
Expand All @@ -2057,7 +2058,7 @@ Caused by: "undefined" cannot be escaped`),
testSql({
'jsonAttr.nested': { twice: 'value' },
}, {
postgres: `"jsonAttr"#>ARRAY['nested','twice'] = '"value"'`,
postgres: `"jsonAttr"#>ARRAY['nested','twice']::VARCHAR(255)[] = '"value"'`,
sqlite: `json_extract(\`jsonAttr\`,'$.nested.twice') = '"value"'`,
mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested.twice')) = '"value"'`,
mysql: `json_extract(\`jsonAttr\`,'$.nested.twice') = CAST('"value"' AS JSON)`,
Expand Down Expand Up @@ -2117,7 +2118,7 @@ Caused by: "undefined" cannot be escaped`),
testSql({
'$association.jsonAttr$.nested.deep::STRING': 'value',
}, {
postgres: `CAST("association"."jsonAttr"#>ARRAY['nested','deep'] AS STRING) = 'value'`,
postgres: `CAST("association"."jsonAttr"#>ARRAY['nested','deep']::VARCHAR(255)[] AS STRING) = 'value'`,
mariadb: `CAST(json_compact(json_extract(\`association\`.\`jsonAttr\`,'$.nested.deep')) AS STRING) = 'value'`,
'sqlite mysql': `CAST(json_extract(\`association\`.\`jsonAttr\`,'$.nested.deep') AS STRING) = 'value'`,
});
Expand All @@ -2131,7 +2132,7 @@ Caused by: "undefined" cannot be escaped`),
});

testSql({ 'jsonAttr.nested.attribute': 4 }, {
postgres: `"jsonAttr"#>ARRAY['nested','attribute'] = '4'`,
postgres: `"jsonAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] = '4'`,
sqlite: `json_extract(\`jsonAttr\`,'$.nested.attribute') = '4'`,
mariadb: `json_compact(json_extract(\`jsonAttr\`,'$.nested.attribute')) = '4'`,
mysql: `json_extract(\`jsonAttr\`,'$.nested.attribute') = CAST('4' AS JSON)`,
Expand All @@ -2156,7 +2157,7 @@ Caused by: "undefined" cannot be escaped`),
});

testSql({ 'jsonAttr.0.attribute': 4 }, {
postgres: `"jsonAttr"#>ARRAY['0','attribute'] = '4'`,
postgres: `"jsonAttr"#>ARRAY['0','attribute']::VARCHAR(255)[] = '4'`,
sqlite: `json_extract(\`jsonAttr\`,'$."0".attribute') = '4'`,
mariadb: `json_compact(json_extract(\`jsonAttr\`,'$."0".attribute')) = '4'`,
mysql: `json_extract(\`jsonAttr\`,'$."0".attribute') = CAST('4' AS JSON)`,
Expand All @@ -2179,7 +2180,7 @@ Caused by: "undefined" cannot be escaped`),
});

testSql({ 'jsonAttr[0].nested.attribute': 4 }, {
postgres: `"jsonAttr"#>ARRAY['0','nested','attribute'] = '4'`,
postgres: `"jsonAttr"#>ARRAY['0','nested','attribute']::VARCHAR(255)[] = '4'`,

// these tests cannot be deduplicated because [0] will be replaced by `0` by expectsql
sqlite: `json_extract(\`jsonAttr\`,'$[0].nested.attribute') = '4'`,
Expand All @@ -2189,7 +2190,7 @@ Caused by: "undefined" cannot be escaped`),

// aliases attribute -> column correctly
testSql({ 'aliasedJsonAttr.nested.attribute': 4 }, {
postgres: `"aliased_json"#>ARRAY['nested','attribute'] = '4'`,
postgres: `"aliased_json"#>ARRAY['nested','attribute']::VARCHAR(255)[] = '4'`,
sqlite: `json_extract(\`aliased_json\`,'$.nested.attribute') = '4'`,
mariadb: `json_compact(json_extract(\`aliased_json\`,'$.nested.attribute')) = '4'`,
mysql: `json_extract(\`aliased_json\`,'$.nested.attribute') = CAST('4' AS JSON)`,
Expand All @@ -2210,7 +2211,7 @@ Caused by: "undefined" cannot be escaped`),
});

testSql({ 'jsonAttr.nested.key:unquote': 0 }, {
postgres: `"jsonAttr"#>>ARRAY['nested','key'] = 0`,
postgres: `"jsonAttr"#>>ARRAY['nested','key']::VARCHAR(255)[] = 0`,
mssql: `JSON_VALUE([jsonAttr], N'$.nested.key') = 0`,
'sqlite mysql mariadb': `json_unquote(json_extract([jsonAttr],'$.nested.key')) = 0`,
});
Expand Down Expand Up @@ -2351,7 +2352,7 @@ Caused by: "undefined" cannot be escaped`),
},
},
}, {
postgres: `"User"."jsonbAttr"#>ARRAY['nested','attribute'] = '"value"'`,
postgres: `"User"."jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] = '"value"'`,
}, {
mainAlias: 'User',
});
Expand All @@ -2371,7 +2372,7 @@ Caused by: "undefined" cannot be escaped`),
[Op.in]: [3, 7],
},
}, {
postgres: `"jsonbAttr"#>ARRAY['nested','attribute'] IN ('3', '7')`,
postgres: `"jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] IN ('3', '7')`,
});

testSql({
Expand Down Expand Up @@ -2403,7 +2404,7 @@ Caused by: "undefined" cannot be escaped`),
},
},
}, {
postgres: `"User"."jsonbAttr"#>ARRAY['name','last'] = '"Simpson"' AND "User"."jsonbAttr"->'employment' != '"None"'`,
postgres: `"User"."jsonbAttr"#>ARRAY['name','last']::VARCHAR(255)[] = '"Simpson"' AND "User"."jsonbAttr"->'employment' != '"None"'`,
}, {
mainAlias: 'User',
});
Expand All @@ -2419,7 +2420,7 @@ Caused by: "undefined" cannot be escaped`),
},
},
}, {
postgres: `"jsonbAttr"#>ARRAY['nested','attribute'] > ${queryGen.escape(jsonDt)}`,
postgres: `"jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] > ${queryGen.escape(jsonDt)}`,
});

testSql({
Expand All @@ -2429,7 +2430,7 @@ Caused by: "undefined" cannot be escaped`),
},
},
}, {
postgres: `"jsonbAttr"#>ARRAY['nested','attribute'] = 'true'`,
postgres: `"jsonbAttr"#>ARRAY['nested','attribute']::VARCHAR(255)[] = 'true'`,
});

testSql({
Expand Down

0 comments on commit df30331

Please sign in to comment.