diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many.js b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many.js index 0e8de5d..c336534 100755 --- a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many.js +++ b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many.js @@ -49,6 +49,8 @@ const post2 = await Post.create({body: 'post2'}); // Make user0 like post0 await user0.addPost(post0) +// Also works. +//await user0.addPost(post0.id) // Make user0 and user2 like post1 await post1.addUsers([user0, user2]) @@ -68,6 +70,20 @@ const user2Likes = await user2.getPosts({order: [['body', 'ASC']]}) assert(user2Likes[0].body === 'post1'); assert(user2Likes.length === 1); +// Same as get* but with the user ID instead of the model object. +{ + const user0Likes = await Post.findAll({ + include: [{ + model: User, + where: {id: user0.id}, + }], + order: [['body', 'ASC']], + }) + assert(user0Likes[0].body === 'post0'); + assert(user0Likes[1].body === 'post1'); + assert(user0Likes.length === 2); +} + // Get users that liked a given likes. const post0Likers = await post0.getUsers({order: [['name', 'ASC']]}) @@ -88,6 +104,7 @@ assert(post1Likers.length === 2); // Check if user likes post. assert( await user0.hasPost(post0)) +assert( await user0.hasPost(post0.id)) // same assert( await user0.hasPost(post1)) assert(!await user0.hasPost(post2)) @@ -104,7 +121,7 @@ assert(!await user0.hasPosts([post0, post1, post2])) assert(await user0.countPosts() === 2) assert(await post0.countUsers() === 1) -// Autogenerated remove* method +// Autogenerated remove* methods // user0 doesn't like post0 anymore. await user0.removePost(post0) diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_custom_table.js b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_custom_table.js index 8c1fd08..18197d3 100755 --- a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_custom_table.js +++ b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_custom_table.js @@ -67,14 +67,40 @@ const post2 = await Post.create({body: 'post2'}); // Autogenerated add* methods -// Make user0 like post0 -await user0.addPost(post0, {through: { score: 1 }}) +// Make some useres like some posts. +await user0.addPost(post0, {through: {score: 1}}) +await user1.addPost(post1, {through: {score: 2}}) +await user1.addPost(post2, {through: {score: 3}}) +// Find what user0 likes. const user0Likes = await user0.getPosts({order: [['body', 'ASC']]}) assert(user0Likes[0].body === 'post0'); assert(user0Likes[0].UserLikesPost.score === 1); assert(user0Likes.length === 1); +// Find what user1 likes. +const user1Likes = await user1.getPosts({order: [['body', 'ASC']]}) +assert(user1Likes[0].body === 'post1'); +assert(user1Likes[0].UserLikesPost.score === 2); +assert(user1Likes[1].body === 'post2'); +assert(user1Likes[1].UserLikesPost.score === 3); +assert(user1Likes.length === 2); + +// Where on the custom through table column. +// https://stackoverflow.com/questions/38857156/how-to-query-many-to-many-relationship-sequelize +{ + const user1LikesWithScore3 = await Post.findAll({ + include: [{ + model: User, + where: {id: user1.id}, + through: {where: {score: 3}}, + }], + }) + assert(user1LikesWithScore3[0].body === 'post2'); + assert(user1LikesWithScore3[0].UserLikesPost.score === 3); + assert(user1LikesWithScore3.length === 1); +} + // TODO: this doesn't work. Possible at all in a single addUsers call? // Make user0 and user2 like post1 // This method automatically generated. diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_same_model.js b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_same_model.js index 1b3c36d..2417563 100755 --- a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_same_model.js +++ b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_same_model.js @@ -18,7 +18,7 @@ const sequelize = new Sequelize({ const User = sequelize.define('User', { name: { type: DataTypes.STRING }, }, {}); -User.belongsToMany(User, {through: 'UserFollowsUser', as: 'Follow'}); +User.belongsToMany(User, {through: 'UserFollowUser', as: 'Follows'}); await sequelize.sync({force: true}); // Create some users. @@ -29,12 +29,48 @@ const user2 = await User.create({name: 'user2'}) const user3 = await User.create({name: 'user3'}) // Make user0 follow user1 and user2 -await user0.addFollow(user1) -await user0.addFollow(user2) -const user0Follows = await user0.getFollow({order: [['name', 'ASC']]}) +await user0.addFollows([user1, user2]) +// Make user2 and user3 follow user0 +await user2.addFollow(user0) +await user3.addFollow(user0) + +// Check that the follows worked. +const user0Follows = await user0.getFollows({order: [['name', 'ASC']]}) assert(user0Follows[0].name === 'user1'); assert(user0Follows[1].name === 'user2'); assert(user0Follows.length === 2); +const user1Follows = await user1.getFollows({order: [['name', 'ASC']]}) +assert(user1Follows.length === 0); + +const user2Follows = await user2.getFollows({order: [['name', 'ASC']]}) +assert(user2Follows[0].name === 'user0'); +assert(user2Follows.length === 1); + +const user3Follows = await user3.getFollows({order: [['name', 'ASC']]}) +assert(user3Follows[0].name === 'user0'); +assert(user3Follows.length === 1); + +// Same but with ID instead of object. +{ + const user0Follows = (await User.findOne({ + where: {id: user0.id}, + include: [{model: User, as: 'Follows'}], + })).Follows + assert(user0Follows[0].name === 'user1'); + assert(user0Follows[1].name === 'user2'); + assert(user0Follows.length === 2); +} + +// has methods +assert(!await user0.hasFollow(user0)) +assert(!await user0.hasFollow(user0.id)) +assert( await user0.hasFollow(user1)) +assert( await user0.hasFollow(user2)) +assert(!await user0.hasFollow(user3)) + +// Count method +assert(await user0.countFollows() === 2) + await sequelize.close(); })(); diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/association_nested_include.js b/rootfs_overlay/lkmc/nodejs/sequelize/association_nested_include.js new file mode 100755 index 0000000..8fdc970 --- /dev/null +++ b/rootfs_overlay/lkmc/nodejs/sequelize/association_nested_include.js @@ -0,0 +1,235 @@ +#!/usr/bin/env node + +// Find all posts by users that a given user follows. +// https://stackoverflow.com/questions/42632943/sequelize-multiple-where-clause + +const assert = require('assert'); +const path = require('path'); + +const { Sequelize, DataTypes } = require('sequelize'); + +const sequelize = new Sequelize({ + dialect: 'sqlite', + storage: 'tmp.' + path.basename(__filename) + '.sqlite', +}); + +(async () => { + +// Create the tables. +const User = sequelize.define('User', { + name: { type: DataTypes.STRING }, +}, {}); +const Post = sequelize.define('Post', { + body: { type: DataTypes.STRING }, +}, {}); +User.belongsToMany(User, {through: 'UserFollowUser', as: 'Follows'}); +User.hasMany(Post); +Post.belongsTo(User); +await sequelize.sync({force: true}); + +// Create data. +const users = await User.bulkCreate([ + {name: 'user0'}, + {name: 'user1'}, + {name: 'user2'}, + {name: 'user3'}, +]) + +const posts = await Post.bulkCreate([ + {body: 'body00', UserId: users[0].id}, + {body: 'body11', UserId: users[0].id}, + {body: 'body10', UserId: users[1].id}, + {body: 'body11', UserId: users[1].id}, + {body: 'body20', UserId: users[2].id}, + {body: 'body21', UserId: users[2].id}, + {body: 'body30', UserId: users[3].id}, + {body: 'body31', UserId: users[3].id}, +]) + +await users[0].addFollows([users[1], users[2]]) + +// Get all posts by authors that user0 follows. +// The posts are placed inside their respetive authors under .Posts +// so we loop to gather all of them. +{ + const user0Follows = (await User.findByPk(users[0].id, { + include: [ + { + model: User, + as: 'Follows', + include: [ + { + model: Post, + } + ], + }, + ], + })).Follows + const postsFound = [] + for (const followedUser of user0Follows) { + postsFound.push(...followedUser.Posts) + } + postsFound.sort((x, y) => { return x.body < y.body ? -1 : x.body > y.body ? 1 : 0 }) + assert(postsFound[0].body === 'body10') + assert(postsFound[1].body === 'body11') + assert(postsFound[2].body === 'body20') + assert(postsFound[3].body === 'body21') + assert(postsFound.length === 4) +} + +// With ordering, offset and limit. +// The posts are placed inside their respetive authors under .Posts +// The only difference is that posts that we didn't select got removed. + +{ + const user0Follows = (await User.findByPk(users[0].id, { + offset: 1, + limit: 2, + // TODO why is this needed? It does try to make a subquery otherwise, and then it doesn't work. + // https://selleo.com/til/posts/ddesmudzmi-offset-pagination-with-subquery-in-sequelize- + subQuery: false, + include: [ + { + model: User, + as: 'Follows', + include: [ + { + model: Post, + } + ], + }, + ], + })).Follows + assert(user0Follows[0].name === 'user1') + assert(user0Follows[1].name === 'user2') + assert(user0Follows.length === 2) + const postsFound = [] + for (const followedUser of user0Follows) { + postsFound.push(...followedUser.Posts) + } + postsFound.sort((x, y) => { return x.body < y.body ? -1 : x.body > y.body ? 1 : 0 }) + // Note that what happens is that some of the + assert(postsFound[0].body === 'body11') + assert(postsFound[1].body === 'body20') + assert(postsFound.length === 2) + + // Same as above, but now with DESC ordering. + { + const user0Follows = (await User.findByPk(users[0].id, { + order: [[ + {model: User, as: 'Follows'}, + Post, + 'body', + 'DESC' + ]], + offset: 1, + limit: 2, + subQuery: false, + include: [ + { + model: User, + as: 'Follows', + include: [ + { + model: Post, + } + ], + }, + ], + })).Follows + // Note how user ordering is also reversed from an ASC. + // it likely takes the use that has the first post. + assert(user0Follows[0].name === 'user2') + assert(user0Follows[1].name === 'user1') + assert(user0Follows.length === 2) + const postsFound = [] + for (const followedUser of user0Follows) { + postsFound.push(...followedUser.Posts) + } + // In this very specific data case, this would not be needed. + // because user2 has the second post body and user1 has the first + // alphabetically. + postsFound.sort((x, y) => { return x.body < y.body ? 1 : x.body > y.body ? -1 : 0 }) + // Note that what happens is that some of the + assert(postsFound[0].body === 'body20') + assert(postsFound[1].body === 'body11') + assert(postsFound.length === 2) + } + + // Here user2 would have no post hits due to the limit, + // so it is entirely pruned from the user list as desired. + // Otherwise we would fetch a lot of unwanted user data + // in a large database. + const user0FollowsLimit2 = (await User.findByPk(users[0].id, { + limit: 2, + subQuery: false, + include: [ + { + model: User, + as: 'Follows', + include: [ { model: Post } ], + }, + ], + })).Follows + assert(user0FollowsLimit2[0].name === 'user1') + assert(user0FollowsLimit2.length === 1) + + // Case in which our post-sorting is needed. + // TODO: possible to get sequelize to do this for us by returning + // a flat array directly? + // It's not big deal since the LIMITed result should be small, + // but feels wasteful. + // https://stackoverflow.com/questions/41502699/return-flat-object-from-sequelize-with-association + // https://github.com/sequelize/sequelize/issues/4419 + { + await Post.truncate({restartIdentity: true}) + const posts = await Post.bulkCreate([ + {body: 'body0', UserId: users[0].id}, + {body: 'body1', UserId: users[1].id}, + {body: 'body2', UserId: users[2].id}, + {body: 'body3', UserId: users[3].id}, + {body: 'body4', UserId: users[0].id}, + {body: 'body5', UserId: users[1].id}, + {body: 'body6', UserId: users[2].id}, + {body: 'body7', UserId: users[3].id}, + ]) + const user0Follows = (await User.findByPk(users[0].id, { + order: [[ + {model: User, as: 'Follows'}, + Post, + 'body', + 'DESC' + ]], + subQuery: false, + include: [ + { + model: User, + as: 'Follows', + include: [ + { + model: Post, + } + ], + }, + ], + })).Follows + assert(user0Follows[0].name === 'user2') + assert(user0Follows[1].name === 'user1') + assert(user0Follows.length === 2) + const postsFound = [] + for (const followedUser of user0Follows) { + postsFound.push(...followedUser.Posts) + } + // We need this here, otherwise we would get all user2 posts first: + // body6, body2, body5, body1 + postsFound.sort((x, y) => { return x.body < y.body ? 1 : x.body > y.body ? -1 : 0 }) + assert(postsFound[0].body === 'body6') + assert(postsFound[1].body === 'body5') + assert(postsFound[2].body === 'body2') + assert(postsFound[3].body === 'body1') + assert(postsFound.length === 4) + } +} + +await sequelize.close(); +})(); diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/index.js b/rootfs_overlay/lkmc/nodejs/sequelize/index.js index fb098ff..ad35ce4 100755 --- a/rootfs_overlay/lkmc/nodejs/sequelize/index.js +++ b/rootfs_overlay/lkmc/nodejs/sequelize/index.js @@ -96,7 +96,7 @@ await IntegerNames.create({value: 5, name: 'five'}); // 3 | 5 | five | 2021-03-19 19:12:08.437+00 | 2021-03-19 19:12:08.437+00 // (3 rows) -let integerNames = await IntegerNames.findAll({ +const integerNames = await IntegerNames.findAll({ where: { value: 2 } diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/truncate_key.js b/rootfs_overlay/lkmc/nodejs/sequelize/truncate_key.js new file mode 100755 index 0000000..3c2c695 --- /dev/null +++ b/rootfs_overlay/lkmc/nodejs/sequelize/truncate_key.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +// https://stackoverflow.com/questions/39765582/in-sequelize-model-destroy-truncate-true-does-not-reset-primary-key + +const assert = require('assert') +const path = require('path') + +const { Sequelize, DataTypes } = require('sequelize') + +const sequelize = new Sequelize({ + dialect: 'sqlite', + storage: 'tmp.' + path.basename(__filename) + '.sqlite', +}) + +;(async () => { +const IntegerNames = sequelize.define('IntegerNames', { + value: { + type: DataTypes.INTEGER, + allowNull: false, + unique: true, + }, + name: { + type: DataTypes.STRING, + }, +}, {}) +await IntegerNames.sync({force: true}) +await IntegerNames.create({value: 2, name: 'two'}) +await IntegerNames.create({value: 3, name: 'three'}) +console.error((await IntegerNames.findOne({where: {value: 2}})).id) +await IntegerNames.truncate({cascade: true}) +await IntegerNames.create({value: 5, name: 'five'}) +await IntegerNames.create({value: 7, name: 'seven'}) +console.error((await IntegerNames.findOne({where: {value: 5}})).id) + +await sequelize.close() +})();