query.test.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. 'use strict';
  2. const chai = require('chai');
  3. const expect = chai.expect;
  4. const Support = require('../../support');
  5. const dialect = Support.getTestDialect();
  6. const { DatabaseError, DataTypes } = require('@sequelize/core');
  7. if (dialect.startsWith('postgres')) {
  8. describe('[POSTGRES] Query', () => {
  9. const taskAlias = 'AnActualVeryLongAliasThatShouldBreakthePostgresLimitOfSixtyFourCharacters';
  10. const teamAlias = 'Toto';
  11. const sponsorAlias = 'AnotherVeryLongAliasThatShouldBreakthePostgresLimitOfSixtyFourCharacters';
  12. const executeTest = async (options, test) => {
  13. const sequelize = Support.createSingleTestSequelizeInstance(options);
  14. const User = sequelize.define('User', { name: DataTypes.STRING }, { underscored: true });
  15. const Team = sequelize.define('Team', { name: DataTypes.STRING });
  16. const Sponsor = sequelize.define('Sponsor', { name: DataTypes.STRING });
  17. const Task = sequelize.define('Task', { title: DataTypes.STRING });
  18. User.belongsTo(Task, { as: taskAlias, foreignKey: 'task_id' });
  19. User.belongsToMany(Team, {
  20. as: teamAlias,
  21. foreignKey: 'teamId',
  22. otherKey: 'userId',
  23. through: 'UserTeam',
  24. });
  25. Team.belongsToMany(Sponsor, {
  26. as: sponsorAlias,
  27. foreignKey: 'sponsorId',
  28. otherKey: 'teamId',
  29. through: 'TeamSponsor',
  30. });
  31. await sequelize.sync({ force: true });
  32. const sponsor = await Sponsor.create({ name: 'Company' });
  33. const team = await Team.create({ name: 'rocket' });
  34. const task = await Task.create({ title: 'SuperTask' });
  35. const user = await User.create({ name: 'test', task_id: task.id, updatedAt: new Date() });
  36. await user[`add${teamAlias}`](team);
  37. await team[`add${sponsorAlias}`](sponsor);
  38. const predicate = {
  39. include: [
  40. {
  41. model: Task,
  42. as: taskAlias,
  43. },
  44. {
  45. model: Team,
  46. as: teamAlias,
  47. },
  48. ],
  49. };
  50. return test({ User, Team, Sponsor, Task }, predicate);
  51. };
  52. it('should throw due to alias being truncated', async function () {
  53. const options = { ...this.sequelize.options, minifyAliases: false };
  54. await executeTest(options, async (db, predicate) => {
  55. expect((await db.User.findOne(predicate))[taskAlias]).to.not.exist;
  56. });
  57. });
  58. it('should be able to retrieve include due to alias minifying', async function () {
  59. const options = { ...this.sequelize.options, minifyAliases: true };
  60. await executeTest(options, async (db, predicate) => {
  61. expect((await db.User.findOne(predicate))[taskAlias].title).to.equal('SuperTask');
  62. });
  63. });
  64. it('should throw due to long alias on through table', async function () {
  65. const options = { ...this.sequelize.options, minifyAliases: false };
  66. await executeTest(options, async (db, predicate) => {
  67. predicate.include[1].include = [
  68. {
  69. model: db.Sponsor,
  70. as: sponsorAlias,
  71. },
  72. ];
  73. await expect(db.User.findOne(predicate)).to.eventually.be.rejected;
  74. });
  75. });
  76. it('should be able to retrieve includes with nested through joins due to alias minifying', async function () {
  77. const options = { ...this.sequelize.options, minifyAliases: true };
  78. await executeTest(options, async (db, predicate) => {
  79. predicate.include[1].include = [
  80. {
  81. model: db.Sponsor,
  82. as: sponsorAlias,
  83. },
  84. ];
  85. expect((await db.User.findOne(predicate))[teamAlias][0][sponsorAlias][0].name).to.equal(
  86. 'Company',
  87. );
  88. });
  89. });
  90. it('should throw due to table name being truncated', async () => {
  91. const sequelize = Support.createSingleTestSequelizeInstance({ minifyAliases: true });
  92. const User = sequelize.define(
  93. 'user_model_name_that_is_long_for_demo_but_also_surpasses_the_character_limit',
  94. {
  95. name: DataTypes.STRING,
  96. email: DataTypes.STRING,
  97. },
  98. {
  99. tableName: 'user',
  100. },
  101. );
  102. const Project = sequelize.define(
  103. 'project_model_name_that_is_long_for_demo_but_also_surpasses_the_character_limit',
  104. {
  105. name: DataTypes.STRING,
  106. },
  107. {
  108. tableName: 'project',
  109. },
  110. );
  111. const Company = sequelize.define(
  112. 'company_model_name_that_is_long_for_demo_but_also_surpasses_the_character_limit',
  113. {
  114. name: DataTypes.STRING,
  115. },
  116. {
  117. tableName: 'company',
  118. },
  119. );
  120. User.hasMany(Project, { foreignKey: 'userId' });
  121. Project.belongsTo(Company, { foreignKey: 'companyId' });
  122. await sequelize.sync({ force: true });
  123. const comp = await Company.create({ name: 'Sequelize' });
  124. const user = await User.create({ name: 'standard user' });
  125. await Project.create({ name: 'Manhattan', companyId: comp.id, userId: user.id });
  126. await User.findAll({
  127. include: {
  128. model: Project,
  129. include: Company,
  130. },
  131. });
  132. });
  133. it('orders by a literal when subquery and minifyAliases are enabled', async () => {
  134. const sequelizeMinifyAliases = Support.createSingleTestSequelizeInstance({
  135. logQueryParameters: true,
  136. benchmark: true,
  137. minifyAliases: true,
  138. define: {
  139. timestamps: false,
  140. },
  141. });
  142. const Foo = sequelizeMinifyAliases.define(
  143. 'Foo',
  144. {
  145. name: {
  146. field: 'my_name',
  147. type: DataTypes.TEXT,
  148. },
  149. },
  150. { timestamps: false },
  151. );
  152. await sequelizeMinifyAliases.sync({ force: true });
  153. await Foo.create({ name: 'record1' });
  154. await Foo.create({ name: 'record2' });
  155. const baseTest = (
  156. await Foo.findAll({
  157. subQuery: false,
  158. order: sequelizeMinifyAliases.literal(`"Foo".my_name`),
  159. })
  160. ).map(f => f.name);
  161. expect(baseTest[0]).to.equal('record1');
  162. const orderByAscSubquery = (
  163. await Foo.findAll({
  164. attributes: {
  165. include: [[sequelizeMinifyAliases.literal(`"Foo".my_name`), 'customAttribute']],
  166. },
  167. subQuery: true,
  168. order: [['customAttribute']],
  169. limit: 1,
  170. })
  171. ).map(f => f.name);
  172. expect(orderByAscSubquery[0]).to.equal('record1');
  173. const orderByDescSubquery = (
  174. await Foo.findAll({
  175. attributes: {
  176. include: [[sequelizeMinifyAliases.literal(`"Foo".my_name`), 'customAttribute']],
  177. },
  178. subQuery: true,
  179. order: [['customAttribute', 'DESC']],
  180. limit: 1,
  181. })
  182. ).map(f => f.name);
  183. expect(orderByDescSubquery[0]).to.equal('record2');
  184. });
  185. it('returns the minified aliased attributes', async () => {
  186. const sequelizeMinifyAliases = Support.createSingleTestSequelizeInstance({
  187. logQueryParameters: true,
  188. benchmark: true,
  189. minifyAliases: true,
  190. define: {
  191. timestamps: false,
  192. },
  193. });
  194. const Foo = sequelizeMinifyAliases.define(
  195. 'Foo',
  196. {
  197. name: {
  198. field: 'my_name',
  199. type: DataTypes.TEXT,
  200. },
  201. },
  202. { timestamps: false },
  203. );
  204. await sequelizeMinifyAliases.sync({ force: true });
  205. await Foo.findAll({
  206. subQuery: false,
  207. attributes: {
  208. include: [[sequelizeMinifyAliases.literal('"Foo".my_name'), 'order_0']],
  209. },
  210. order: [['order_0', 'DESC']],
  211. });
  212. });
  213. describe('Connection Invalidation', () => {
  214. if (process.env.DIALECT === 'postgres-native') {
  215. // native driver doesn't support statement_timeout or query_timeout
  216. return;
  217. }
  218. async function setUp(clientQueryTimeoutMs) {
  219. const sequelize = Support.createSingleTestSequelizeInstance({
  220. statement_timeout: 500, // ms
  221. query_timeout: clientQueryTimeoutMs,
  222. pool: {
  223. max: 1, // having only one helps us know whether the connection was invalidated
  224. idle: 60_000,
  225. },
  226. });
  227. return { sequelize, originalPid: await getConnectionPid(sequelize) };
  228. }
  229. async function getConnectionPid(sequelize) {
  230. const connection = await sequelize.pool.acquire();
  231. const pid = connection.processID;
  232. sequelize.pool.release(connection);
  233. return pid;
  234. }
  235. it('reuses connection after statement timeout', async () => {
  236. // client timeout > statement timeout means that the query should fail with a statement timeout
  237. const { originalPid, sequelize } = await setUp(10_000);
  238. await expect(sequelize.query('select pg_sleep(1)')).to.eventually.be.rejectedWith(
  239. DatabaseError,
  240. 'canceling statement due to statement timeout',
  241. );
  242. expect(await getConnectionPid(sequelize)).to.equal(originalPid);
  243. });
  244. it('invalidates connection after client-side query timeout', async () => {
  245. // client timeout < statement timeout means that the query should fail with a read timeout
  246. const { originalPid, sequelize } = await setUp(250);
  247. await expect(sequelize.query('select pg_sleep(1)')).to.eventually.be.rejectedWith(
  248. DatabaseError,
  249. 'Query read timeout',
  250. );
  251. expect(await getConnectionPid(sequelize)).to.not.equal(originalPid);
  252. });
  253. });
  254. });
  255. }