I need to generate an unique alphanumeric string of length of 30 characters which will be used very similar to the record id.
Is it okay to use Crypto.generateAesKey()
method to generate this 30 characters string? I'll be converting it to Hex and use String.subString(0,30)
to achieve this.
Is this enough to generate random string for every insertion? My custom objects can have up to 50 millions of records.
[SalesForce] Generate random string using Crypto.generateAesKey
Related Solutions
Instead of thinking about the system as a single number, consider the system as a set of 6 smaller numbers. This lets you get rid of most of the naivety of a brute force algorithm.
Here's the version I came up with:
public class q173224 {
public static Integer nextId(Integer value) {
value++;
Integer a = (value >> 25) & 31, b = (value >> 20) & 31, c = (value >> 15) & 31,
d = (value >> 10) & 31, e = (value >> 5) & 31, f = value & 31;
for(; a < 8; (b = 8) == 8 && (a = a + 1) >= 0) {
for(; b < 32; (c = 0) == 0 && (b = b + 1) >= 0) {
if(a != b) {
for(; c < 32; (d = 0) == 0 && (c = c + 1) >= 0) {
if(a != c && b != c) {
for(; d < 32; (e = 0) == 0 && (d = d + 1) >= 0) {
if(a != d && b != d && c != d) {
for(; e < 32; (f = 0) == 0 && (e = e + 1) >= 0) {
if(a != e && b != e && c != e && d != e) {
for(; f < 32; f++) {
if(a != f && b != f && c != f && d != f && e != f) {
return (a << 25) + (b << 20) + (c << 15) + (d << 10) + (e << 5) + f;
}
}
}
}
}
}
}
}
}
}
}
return null;
}
Static String[] keys = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'.split('');
public static String base32encode(Integer value) {
return keys[(value >> 25) & 31] + keys[(value >> 20) & 31] + keys[(value >> 15) & 31] +
keys[(value >> 10) & 31] + keys[(value >> 5) & 31] + keys[value & 31];
}
}
P.S. I apologize for the ugly hacks, but this code is optimized for performance, not legibility.
A few explanations:
(X = Y) == Z
This is a trick to simulate Java's "," operator, which would have greatly simplified the code. In Java, it would have been written as:
X = Y, W++;
Of course, << and >> are left-shift bits, and right-shift bits; it's a lot easier than having to write things like a / 33554432
and so on. Likewise, we're using & 31
to get Math.mod(value, 32)
. This entire code is optimized to run like greased lightning and for code coverage purposes (just pass in 0 and 1<<31 to get 100% coverage of nextId).
Finally, the first parameter, the initialization of the loop, isn't done in the traditional way, because we need to start the loop at a predetermined value instead of starting at 0 (or some specific value).
Converting this to Java, I found that there are actually a total of 126,282,240 unique ID values that fit your criteria, allowing 8 possible values in the first position, 24 possible values in the second position, and 32 possible values for all other positions, with no duplicate characters in any position. The code never has to go more than 10 false loops to find a unique value, instead of needing to skip hundreds of thousands of values one at a time; it can generate an ID in less than 2ms per ID.
This requires no random guesses, and just a single solitary database query to determine where to start. Keep in mind that you must use a query to get a row lock on your custom setting (FOR UPDATE) to make sure that you don't generate the same ID twice for transactions that are in-flight simultaneously.
I ended up doing it in the Javascript on the onchange for the text entry. It splits the string into a set to minimize the comparison, then back to an array where it filters that array against a const array of all gsm chars.
The gsmChars in my list are documented on a number of sites. The challenge for me was coming up with the fastest way to check each char in a string against that list so it could happen with every new character entered. The bottom part of my handler is just the functional part of what I needed to do -- if there's a non-GSM character, indicated that the message will be broken into segments of up to 67 chars, else segments of 153 chars.
const gsmChars = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'!', '#', ' ', '"', '%', '&', '\'', '(', ')', '*', ',', '.', '?',
'+', '-', '/', ';', ':', '<', '=', '>', '¡', '¿', '_', '@',
'$', '£', '¥', '¤',
'è', 'é', 'ù', 'ì', 'ò', 'Ç', 'Ø', 'ø', 'Æ', 'æ', 'ß', 'É', 'Å',
'å', 'Ä', 'Ö', 'Ñ', 'Ü', '§', 'ä', 'ö', 'ñ', 'ü', 'à',
'Δ','Φ','Ξ','Γ','Ω','Π','Ψ','Σ','Θ','Λ'];
checkBody(event){
let body = event.detail.value;
this.charCount = body.length;
let bodyCharSet = new Set(body.split(""));
let bodyCharArray = Array.from(bodyCharSet);
this.notGsm = ((bodyCharArray.filter(e => !gsmChars.includes(e)).length > 0));
if(this.notGsm){
this.segmentCount = Math.ceil(body.length / 67);
}else {
this.segmentCount = Math.ceil(body.length / 153);
}
}
Best Answer
30 hex digits yields 1.329228e+36 possible values; you're looking at having up to 50,000,000 (5.0e+7) records. This results in an extraordinarily large number of possible ID values (on the order of 2.7e29) per record.
The
Crypto
documentation does not specify exactly how an AES key is generated. If we can assume Salesforce uses a high quality source of entropy to generate true cryptographically random keys, you should be able to assume your ID values are evenly distributed through the keyspace to enough precision that it won't make any difference to your application.Collisions will not be likely, to say the least. But it's also not impossible. If you make your Id field a unique External Id field, you could build some fairly simple logic into your
before insert
trigger to run a query against your generated values to ensure they're not duplicated before you populate the field.