I am trying to update multiple columns within a table using Knex.js
I have tried looking at suggestions in the Knex docs as well as online, but so far have not found any working solution.
My coding attempt:
const userUpdateHandler = (req,res,database)=>{
const { id } = req.params;
const { name, age } = req.body.formInput
database('users')
.where({ id })
.update({ name }).update({ age})
.then(resp => {
if (resp) {
res.json("success")
} else {
res.status(400).json('Not found')
}
})
.catch(err => res.status(400).json('error updating user'))
}
The above is something I've tried out of sheer desperation, but what I would like it to do is to update multiple columns at once.
Could you please advise me on the best approach?
That should work https://runkit.com/embed/6ulv4hy93fcj
knex('table').update({foo: 1}).update({bar: 2}).toSQL()
// generates SQL: update "table" set "foo" = ?, "bar" = ?
and so should:
database('users')
.where({ id })
.update({ name, age })
Check out if your generated queries are what you are expecting...
Related
Good day everyone, I am working with watermelondb and I have the code below, but I don't know how to actually use it. I am new in watermelondb and I don't know how to pass data as props to the pullChanges and pushChanges objects. How do I pass necessary data like changes and lastPulledAt from the database into the sync function when I call it. And I need more explanation on the migrationsEnabledAtVersion: 1 too. Thanks in advance for your gracious answers.
import { synchronize } from '#nozbe/watermelondb/sync'
async function mySync() {
await synchronize({
database,
pullChanges: async ({ lastPulledAt, schemaVersion, migration }) => {
const urlParams = `last_pulled_at=${lastPulledAt}&schema_version=${schemaVersion}&migration=${encodeURIComponent(JSON.stringify(migration))}`
const response = await fetch(`https://my.backend/sync?${urlParams}`)
if (!response.ok) {
throw new Error(await response.text())
}
const { changes, timestamp } = await response.json()
return { changes, timestamp }
},
pushChanges: async ({ changes, lastPulledAt }) => {
const response = await fetch(`https://my.backend/sync?last_pulled_at=${lastPulledAt}`, {
method: 'POST',
body: JSON.stringify(changes)
})
if (!response.ok) {
throw new Error(await response.text())
}
},
migrationsEnabledAtVersion: 1,
})
}
Watermelondb's documentation is terrible and its link to typescript even worse.
I spent almost a week to get 100% synchronization with a simple table, now I'm having the same problems to solve the synchronization with associations.
Well, the object you need to return in pullChanges is of the following form:
return {
changes: {
//person is the name of the table in the models
person: {
created: [
{
// in created you need to send null in the id, if you don't send the id it doesn't work
id: null,
// other fields of your schema, not model
}
],
updated: [
{
// the fields of your schema, not model
}
],
deleted: [
// is a string[] consisting of the watermelondb id of the records that were deleted in the remote database
],
}
},
timestamp: new Date().getTime() / 1000
}
In my case, the remote database is not a watermelondb, it's a mySQL, and I don't have an endpoint in my API that returns everything in the watermelon format. For each table I do a search with deletedAt, updatedAt or createdAt > lastPulledAt and do the necessary filtering and preparations so that the data from the remote database is in the schema format of the local database.
In pushChanges I do the reverse data preparation process by calling the appropriate creation, update or deletion endpoints for each of the tables.
It's costly and annoying to do, but in the end it works fine, the biggest problem is watermelon's documentation which is terrible.
I am trying to return all the records that match my searchtext.So far I have only seen examples where we need to specify field name but I want return records if any of the field contains the searchtext, without specifying any field name. And I got to see $text , but unfortunatly it's not supported in cosmosdb API mongodb.
Can someone please help me to resolve this issue ?
Here is what I tried but failed
let querySpec = {
entity: "project",
$text: { $search: "\"test\"" } ,
$or: [{
accessType: "Private",
userName: userName
}, {
accessType: "Public"
}]
}
dbHandler.findandOrder(querySpec, sortfilter, "project").then(function (response) {
res.status(200).json({
status: 'success',
data: utils.unescapeList(response),
totalRecords:response.length
});
exports.findandOrder = function (filterObject, sortfilter, collectionname) {
return new Promise((resolve, reject) => {
return getConnection().then((db) => {
if (db == null) {
console.log("db in findandOrder() is undefined");
} else {
db.db(config.mongodb.dbname).collection(collectionname).find(filterObject).sort(sortfilter).toArray((err, res) => {
if (db) {
//db.close();
}
if (err) {
reject(err);
} else {
resolve(res);
}
});
}
});
});
};
Error:
{"message":{"ok":0,"code":115,"codeName":"CommandNotSupported","name":"MongoError"}}
I am using $regex as temperory solution as $text is not supported.
Please suggest ...
From the MongoDB manual, Create a Wildcard Index on All Fields:
db.collection.createIndex( { "$**" : 1 } )
but this is far from an ideal way to index your data. On the same page is this admonition:
Wildcard indexes are not designed to replace workload-based index planning.
In other words, know your data/schema and tailor indices accordingly. There are many caveats to wildcard indices so read the manual page linked above.
Alternately you can concatenate all the text you want to search into a single field (while also maintaining the individual fields) and create a text index on that field.
I have a mongodb collections named "articles"
I have configured below rules for validating "title" field of article while Updating the record.
validator.body('title').custom( (value, {req}) => {
console.log(value, req.params.id)
return Article.find({ title:value, _id:{ $ne: req.params.id } })
.then( article => {
if (article) {
return Promise.reject('Title already in use');
}
})
})
So basically it should check if "title" should not exists in the collection and it should not be the same ID as the one I am updating.
The line console.log(value, req.params.id) is printing proper Title and ID but the validation is always saying "Title already in use". Even though I use entirely different title that is not used at all.
Any idea what's wrong?
You should use findOne query for better performance and check data is null like as bellow.
validator.body('title').custom( (value, {req}) => {
console.log(value, req.params.id)
return Article.findOne({ title:value, _id:{ $ne: req.params.id } })
.then( article => {
if (article !== null) {
return Promise.reject('Title already in use');
}
})
})
Stupid mistake,
the
if (article) {
is supposed to be
if (article.length) {
And it worked fine.
I have two tables as seen below
The first table is for users and is populated via a registration form on the client side. When a new user is created, I need the second 'quotas' table to be populated with date, amount, and linked with the user id. The 'user_id' is used to pull the quotas information in a GET and display client side. I am having issues using the 'id' to populate the second table at the time of creation. I am using knex to make all queries. Would I be using join to link them in knex?
server
hydrateRouter // get all users
.route('/api/user')
.get((req, res) => {
knexInstance
.select('*')
.from('hydrate_users')
.then(results => {
res.json(results)
})
})
.post(jsonParser, (req, res, next) => { // register new users
const { username, glasses } = req.body;
const password = bcrypt.hashSync(req.body.password, 8);
const newUser = { username, password, glasses };
knexInstance
.insert(newUser)
.into('hydrate_users')
.then(user => {
res.status(201).json(user);
})
.catch(next);
})
client
export default class Register extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
glasses: 0
}
}
handleSubmit(event) {
event.preventDefault();
fetch('http://localhost:8000/api/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.state)
})
.then(response => response.json())
.then(responseJSON => {
this.props.history.push('/login');
})
}
server side route for displaying the water amount
hydrateRouter
.route('/api/user/waterconsumed/:user_id') // display water consumed/day
.all(requireAuth)
.get((req, res, next) => {
const {user_id} = req.params;
knexInstance
.from('hydrate_quotas')
.select('amount')
.where('user_id', user_id)
.first()
.then(water => {
res.json(water)
})
.catch(next)
})
Thank you!
Getting the id of an inserted row
So this is a common pattern in relational databases, where you can't create the egg until you have the unique id of the chicken that lays it! Clearly, the database needs to tell you how it wants to refer to the chicken.
In Postgres, you can simply use Knex's .returning function to make it explicit that you want the new row's id column returned to you after a successful insert. That'll make the first part of your query look like this:
knexInstance
.insert(newUser)
.into('users')
.returning('id')
Note: not all databases support this in the same way. In particular, if you happen to be developing locally using SQLite, it will return the number of rows affected by the query, not the id, since SQLite doesn't support SQL's RETURNING. Best is just to develop locally using Postgres to avoid nasty surprises.
Ok, so we know which chicken we're after. Now we need to make sure we've waited for the right id, then go ahead and use it:
.then(([ userId ]) => knexInstance
.insert({ user_id: userId,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT })
.into('quotas')
)
Or however you choose to populate that second table.
Note: DATE is a SQL keyword. For that reason, it doesn't make a great column name. How about created or updated instead?
Responding with sensible data
So that's basic "I have the ID, let's insert to another table" strategy. However, you actually want to be able to respond with the user that was created... this seems like sensible API behaviour for a 201 response.
What you don't want to do is respond with the entire user record from the database, which will expose the password hash (as you're doing in your first code block from your question). Ideally, you'd probably like to respond with some UI-friendly combination of both tables.
Luckily, .returning also accepts an array argument. This allows us to pass a list of columns we'd like to respond with, reducing the risk of accidentally exposing something to the API surface that we'd rather not transmit.
const userColumns = [ 'id', 'username', 'glasses' ]
const quotaColumns = [ 'amount' ]
knexInstance
.insert(newUser)
.into('users')
.returning(userColumns)
.then(([ user]) => knexInstance
.insert({
user_id: user.id,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT
})
.into('quotas')
.returning(quotaColumns)
.then(([ quota ]) => res.status(201)
.json({
...user,
...quota
})
)
)
Async/await for readability
These days, I'd probably avoid a promise chain like that in favour of the syntactic sugar that await provides us.
try {
const [ user ] = await knexInstance
.insert(newUser)
.into('users')
.returning(userColumns)
const [ quota ] = await knexInstance
.insert({
user_id: userId,
date: knex.fn.now(),
amount: userConstants.INITIAL_QUOTA_AMOUNT
})
.into('quotas')
.returning(quotaColumns)
res
.status(201)
.json({
...user,
...quota
})
} catch (e) {
next(Error("Something went wrong while inserting a user!"))
}
A note on transactions
There are a few assumptions here, but one big one: we assume that both inserts will be successful. Sure, we provide some error handling, but there's still the possibility that the first insert will succeed, and the second fail or time out for some reason.
Typically, we'd do multiple insertions in a transaction block. Here's how Knex handles this:
try {
const userResponse = await knexInstance.transaction(async tx => {
const [ user ] = await tx.insert(...)
const [ quota ] = await tx.insert(...)
return {
...user,
...quota
}
})
res
.status(201)
.json(userResponse)
} catch (e) {
next(Error('...'))
}
This is good general practice for multiple inserts that depend on each other, since it sets up an "all or nothing" approach: if something fails, the database will go back to its previous state.
I have a list of mongo '_id' which I want to delete. Currently I am doing this
# inactive_users --> list of inactive users
for item in inactive_users:
db.users.remove({'_id' : item})
but my problem is the list is too huge... (it might go 100,000 +). So querying for every item in list will only increase the load on server. Is their a way to pass the entire list in mongo query so that I dont have to fire query again and again.
Thank you
db.users.deleteMany({'_id':{'$in':inactive_users}})
List them all and use $in operator:
db.users.remove({_id:{$in:[id1, id2, id3, ... ]}})
You need to pass the ids in a specific format using ObjectId():
db.users.remove({_id: {$in: [ObjectId('Item1'), ObjectId('Item2'), ObjectId('Item2')]}});
Remove doesn't accept integer - you have to use ObjectId instance with _id format as a string.
var collection = db.users;
var usersDelete = [];
var ObjectID = req.mongo.ObjectID; //req is request from express
req.body.forEach(function(item){ //req.body => [{'_id' : ".." , "name" : "john"}]
usersDelete.push(new ObjectID(item._id));
});
collection.remove({'_id':{'$in': usersDelete}},function(){
//res.json(contatos);
});
I had the same question and ran across these answers but it seems the MongoDB manual is recommending deleteMany instead of remove. deleteMany returns the delete count as well as an acknowledgement of the write concern (if the operation succeeded).
const ids = [id1, id2, id3...];
const query = { _id: { $in: ids} };
dbo.collection("users").deleteMany(query, function (err, obj) {
if (err) throw err;
});
Or with an arrow function:
const ids = [id1, id2, id3...];
const query = { _id: { $in: ids} };
dbo.collection("users").deleteMany(query, (err, obj) => {
if (err) throw err;
});
Or better yet, with a promise:
const ids = [id1, id2, id3...];
const query = { _id: { $in: ids} };
dbo.collection("users").deleteMany(query)
.then(result => {
console.log("Records Deleted");
console.log(JSON.stringify(result));
//for number removed...
console.log("Removed: " + result["n"]);
})
.catch(err => {
console.log("Error");
console.log(err);
});