I have a field in my postgres db called authorizedUserNumber
. This field is set by default to 0, and does not auto-increment, because it is only asigned when a user has been fully onboarded.
Okay, so let’s say a new user has been fully onboarded and I want to assign a unique number to the field authorizedUserNumber
. In the event I have multiple servers running, I want to detect collisions of unique numbers in this field, so as to protect against race conditions.
I thought of defining authorizedUserNumber
as a Sequelize unique field, and trying something like this:
// get current max authorizedUserNumber let userWithMaxOnboardedauthorizedUserNumber = await connectors.usersClinical.findAll({ attributes: [ sequelize.fn('MAX', sequelize.col('authorizedUserNumber')) ], }); let newLatestauthorizedUserNumber = userWithMaxOnboardedauthorizedUserNumber[0].authorizedUserNumber; newLatestauthorizedUserNumber += 1; let numAttempts = 0 let done = false; while ((!done) && (numAttempts <= 50)){ try{ user = await updateUser(user.userId, {authorizedUserNumber: newLatestauthorizedUserNumber}); done = true; }catch(e){ // a unique field will throw an error if you try to store a duplicate value to it console.log(`Collision in assigning unique authorizedUserNumber. UserId: ${user.userId}`); newLatestauthorizedUserNumber += 1; numAttempts+= 1; } } if (!done){ console.error(`Could not assign unique authorizedUserNumber. UserId: ${user.userId}`); }
The problem with this code, is that if the field authorizedUserNumber
is defined as unique, then I can’t put a default value in it. So there’s no way to have it be empty prior to having the correct value placed in it.
What’s the best practice for dealing with this sort of situation?
UPDATE:
Thanks to @Belayer for providing the solution.
Here are some notes on how I implemented it in Sequelize/Postgres.
Sequelize, AFAICT, does not yet support sequences. So I used a raw query in Sequelize to create the sequence:
let sql = ` CREATE SEQUENCE authorizedUserNumber_seq START WITH 1 INCREMENT BY 1;` let result; try{ result = await db.query(sql); console.log(`sql code to create authorizedUserNumber_seq has been run successfully.`) } catch(e){ result = null; console.error(`Error in creating authorizedUserNumber_seq.`) }
Then when it’s time to authorize the new user and assign a unique user number, I again use a raw query, with the following sql:
let sql = `UPDATE usersClinical SET "authorizedUserNumber" = nextval('authorizedUserNumber_seq') WHERE "userId" = '${user.userId}';`
Advertisement
Answer
Rather than defaulting to 0 just let the column be null when not set. Since the default is null there can be any number of them without violating a unique constraint. Then create a sequence
for that column (do not set as column default). There is no requirement for a sequence to auto-increment, the nextval
can be assigned when needed. Make the assignment from the sequence when the new user becomes fully on-boarded.
create table users ( id integer generated always as identity , name text , assignedid integer , constraint assigned_uk unique (assignedid) ) ; create sequence user_assigned_seq;
You can even make the assignment when user is created if desired. (see demo )