ID photo of Ciro Santilli taken in 2013 right eyeCiro Santilli OurBigBook logoOurBigBook.com  Sponsor 中国独裁统治 China Dictatorship 新疆改造中心、六四事件、法轮功、郝海东、709大抓捕、2015巴拿马文件 邓家贵、低端人口、西藏骚乱
nodejs/sequelize/index.js
#!/usr/bin/env node
// https://cirosantilli.com/sequelize-example
const assert = require('assert')
const { DataTypes, Op } = require('sequelize')
const common = require('./common')
const sequelize = common.sequelize(__filename, process.argv[2])

// OMG fuck this asynchronous bullshit:
// https://stackoverflow.com/questions/39928452/execute-sequelize-queries-synchronously/43250120
;(async () => {

// Connection sanity check.
// https://stackoverflow.com/questions/19429152/check-mysql-connection-in-sequelize/31115934
await sequelize.authenticate();

const IntegerNames = sequelize.define('IntegerNames', {
  value: {
    type: DataTypes.INTEGER,
    allowNull: false,
    unique: true,
  },
  name: {
    type: DataTypes.STRING,
  },
});

// Create the database defined by `sequelize.define`.
await IntegerNames.sync({force: true})
// After this:
//
// psql lkmc-nodejs -c '\dt'
//
// gives:
//
//            List of relations
//  Schema |     Name     | Type  | Owner
// --------+--------------+-------+-------
//  public | IntegerNames | table | ciro
// (2 rows)
//
// and:
//
// psql lkmc-nodejs -c '\d+ "IntegerNames"'
//
// gives:
//
//   Column   |           Type           | Collation | Nullable |                  Default                   | Storage  | Stats target | Description
// -----------+--------------------------+-----------+----------+--------------------------------------------+----------+--------------+-------------
//  id        | integer                  |           | not null | nextval('"IntegerNames_id_seq"'::regclass) | plain    |              |
//  value     | integer                  |           | not null |                                            | plain    |              |
//  name      | character varying(255)   |           |          |                                            | extended |              |
//  createdAt | timestamp with time zone |           | not null |                                            | plain    |              |
//  updatedAt | timestamp with time zone |           | not null |                                            | plain    |              |
// Indexes:
//     "IntegerNames_pkey" PRIMARY KEY, btree (id)

async function reset() {
  // First clean the database to properly reset data after we modify data in our tests.
  await sequelize.truncate({ cascade: true })

  return [
    await IntegerNames.create({value: 2, name: 'two'}),
    await IntegerNames.create({value: 3, name: 'three'}),
    await IntegerNames.create({value: 5, name: 'five'}),
  ]
  // psql lkmc-nodejs -c 'SELECT * FROM "IntegerNames";'
  //
  // gives:
  //
  //  id | value | name  |         createdAt          |         updatedAt
  // ----+-------+-------+----------------------------+----------------------------
  //   1 |     2 | two   | 2021-03-19 19:12:08.436+00 | 2021-03-19 19:12:08.436+00
  //   2 |     3 | three | 2021-03-19 19:12:08.436+00 | 2021-03-19 19:12:08.436+00
  //   3 |     5 | five  | 2021-03-19 19:12:08.437+00 | 2021-03-19 19:12:08.437+00
  // (3 rows)
}
let i2, i3, i5
;[i2, i3, i5] = await reset()

let row, rows;

// SELECT all
rows = await IntegerNames.findAll({ order: [['value', 'ASC']] });
common.assertEqual(rows, [
  { id: 1, value: 2, name: 'two',   },
  { id: 2, value: 3, name: 'three', },
  { id: 3, value: 5, name: 'five',  },
])

// SELECT OFFSET without LIMIT. Non-SQL standard, but Sequelize implements it for us.
rows = await IntegerNames.findAll({ offset: 1, order: [['value', 'ASC']] });
common.assertEqual(rows, [
  { value: 3 },
  { value: 5 },
])

// ORDER as expression.
rows = await IntegerNames.findAll({ order: [[sequelize.fn('-', sequelize.col('value')), 'ASC']] });
common.assertEqual(rows, [
  { value: 5 },
  { value: 3 },
  { value: 2 },
])

// SELECT WHERE
rows = await IntegerNames.findAll({
  where: {
    value: 2
  },
  order: [['value', 'ASC']],
});
common.assertEqual(rows, [
  { id: 1, value: 2, name: 'two',   },
])

// SELECT WHERE IN
rows = await IntegerNames.findAll({
  where: {
    id: [1, 2],
  },
  order: [['value', 'ASC']],
});
common.assertEqual(rows, [
  { value: 2 },
  { value: 3 },
])

// It is a bit annoying though that it makes IN NULL queries when the list is empty.
rows = await IntegerNames.findAll({
  where: { id: [], },
});
common.assertEqual(rows, [])

// findOne: SELECT WHERE IN LIMIT 1
row = await IntegerNames.findOne({
  where: { value: { [Op.gt]: 2 }, },
  order: [['value', 'ASC']]
});
assert.strictEqual(row.value, 3);

// findByPk: SELECT WHERE IN id =
row = await IntegerNames.findByPk(i3.id)
assert.strictEqual(row.value, 3);

// findByPk: returns NULL if does not exist.
row = await IntegerNames.findByPk(666)
assert.strictEqual(row, null);

// attributes: SELECT only specified columns instead of the default of selecting all of them.
//
// Also rename value to myvalue with an AS.
//
// Also add a new demo calculated column.
//
// TODO why is get('myvalue') needed to get the value, only for the AS alias? Does not work with direct .myvalue access...
// https://stackoverflow.com/questions/45681000/sequelize-cannot-access-alias-alias-is-undefined
// Docs say they are the same however...
// https://web.archive.org/web/20210506230920/https://sequelize.org/master/class/lib/model.js~Model.html#static-method-getTableName
// ah, some of the docs say that you need .get(): https://github.com/sequelize/sequelize/issues/10592 terrible interface and docs as usual.
// https://stackoverflow.com/questions/32649218/how-do-i-select-a-column-using-an-alias
rows = await IntegerNames.findAll({
  attributes: [
    'name',
    // value AS myvalue
    ['value', 'myvalue'],
    // -value AS "myvalueMinus"
    [sequelize.fn('-', sequelize.col('value')), 'myvalueMinus'],
  ],
  where: {
    value: 2
  },
  order: [['value', 'ASC']],
});
common.assertEqual(rows, [
  { id: undefined, value: undefined, myvalue: 2, myvalueMinus: -2 },
])

// Add extra attribute with attributes.include. Also show how to use custom attribute on sort.
rows = await IntegerNames.findAll({
  attributes: {
    include: [[sequelize.fn('-', sequelize.col('value')), 'valueMinus']],
  },
  order: [
    // Literal needed otherwise it searches for Integer.valueMinus which does not exist and blows up.
    [sequelize.literal('valueMinus'), 'ASC']
  ],
});
common.assertEqual(rows, [
  { value: 5, valueMinus: -5 },
  { value: 3, valueMinus: -3 },
  { value: 2, valueMinus: -2 },
])

// DELETE WHERE: https://stackoverflow.com/questions/8402597/sequelize-js-delete-query
await IntegerNames.destroy({
  where: {
    value: { [Op.gt]: 2 },
  },
  // The LIMIT requires a subquery in many DBMS e.g. SQLite and PostgreSQL,
  // as it is not SQL standard. But sequelize does implement for us.
  // It does not implement offset however, although that would be trivial:
  // https://stackoverflow.com/questions/56377593/complex-destroy-query
  limit: 1,
});
rows = await IntegerNames.findAll({order: [['value', 'ASC']]})
common.assertEqual(rows, [
  { name: 'two'   },
  { name: 'five'  },
])
;[i2, i3, i5] = await reset()

// Truncate all tables.
// https://stackoverflow.com/questions/47816162/wipe-all-tables-in-a-schema-sequelize-nodejs/66985334#66985334
await sequelize.truncate();
assert.strictEqual(await IntegerNames.count(), 0);
;[i2, i3, i5] = await reset()

// bulkCreate
await sequelize.truncate();
const date = new Date(2000, 0, 1, 2, 3, 4, 5)
await IntegerNames.bulkCreate([
  { value: 2, name: 'two'  , createdAt: date, updatedAt: date },
  { value: 3, name: 'three', createdAt: date, updatedAt: date },
  { value: 5, name: 'five' , createdAt: date, updatedAt: date },
])
rows = await IntegerNames.findAll({order: [['value', 'ASC']]})
// Check that they get updated.
// https://stackoverflow.com/questions/42519583/sequelize-updating-updatedat-manually
// https://github.com/sequelize/sequelize/issues/3759
// https://stackoverflow.com/questions/69053635/in-sequelize-bulkcreate-timestamps-are-not-updating
// https://github.com/sequelize/sequelize/issues/6992
assert.strictEqual(rows[0].createdAt.getTime(), date.getTime())
assert.strictEqual(rows[0].updatedAt.getTime(), date.getTime())
;[i2, i3, i5] = await reset()

await IntegerNames.bulkCreate([
  { value: 7, },
  { value: 11 },
])
// Produces IS NOT NULL
rows = await IntegerNames.findAll({
  where: { name: { [Op.ne]: null } },
  order: [['value', 'ASC']],
})
common.assertEqual(rows, [
  { value: 2, name: 'two',   },
  { value: 3, name: 'three', },
  { value: 5, name: 'five',  },
])
// Produces IS NULL
rows = await IntegerNames.findAll({
  where: { name: null },
  order: [['value', 'ASC']],
})
common.assertEqual(rows, [
  { value: 7,  name: null,  },
  { value: 11, name: null,  },
])
;[i2, i3, i5] = await reset()

// .close Otherwise it hangs for 10 seconds, it seems that it keeps the connection alive.
// https://stackoverflow.com/questions/28253831/recreating-database-sequelizejs-is-slow
// https://github.com/sequelize/sequelize/issues/8468
})().finally(() => { return sequelize.close() });