Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exclude reserved IDs - how? #35

Open
hrstoyanov opened this issue Nov 30, 2024 · 4 comments
Open

Exclude reserved IDs - how? #35

hrstoyanov opened this issue Nov 30, 2024 · 4 comments

Comments

@hrstoyanov
Copy link

hrstoyanov commented Nov 30, 2024

Thanks for this well-design library!

There are use cases where the database system need to be bootstrapped with a few "well known" ids, while the rest of the data entities can be generated tsid as usual. (Example: you are creating an user database where the "admin" user must be with id=0, all other users can be get their IDs with Tsid::fast, or TsidCreator::getTsid, but should never receive 0)

Is there an easy way to generating tsids by excluding certain reserved values? I can see a way how to do that with some sort of skipping factory, but was hoping for something built-in, a "creator" that takes a list of "avoid-these-values" argument..

@fabiolimace
Copy link
Member

fabiolimace commented Nov 30, 2024

Thank you for the suggestion!

The scenario you described is indeed valid, and I understand the need for certain IDs to be reserved. However, implementing built-in support for "avoid-these-values" in the TSID generation process would introduce additional complexity to the library.

For your use case, a good solution would be to implement a custom wrapper around the TSID generator. This approach keeps the core library lean and allows you to tailor the behavior to your specific needs. For example:

package com.example;

import java.util.Arrays;
import java.util.List;

// import com.github.f4b6a3.tsid.Tsid;
import com.github.f4b6a3.tsid.TsidCreator;

public class CustomTsidCreator {

	// Add other reserved values if needed
	private static final Tsid NIL = Tsid.from(0x0000000000000000L);
	private static final List<Tsid> RESERVED_VALUES = Arrays.asList(NIL);

	public static Tsid getTsidExcludingReserved() {
		Tsid tsid;
		do {
			// tsid = Tsid.fast();
			tsid = TsidCreator.getTsid();
		} while (RESERVED_VALUES.contains(tsid));
		return tsid;
	}
	
	public static void main(String[] args) { 
		System.out.println(CustomTsidCreator.getTsidExcludingReserved());
	}
}

@hrstoyanov
Copy link
Author

hrstoyanov commented Dec 1, 2024

Thanks, I already came up with something similar - so ... maybe just add something in the FAQ?

Feel free to close this issue otherwise.

public record TsidGenerator(long excludeStart, long excludeEnd, Supplier<Tsid> generator) {

    public TsidGenerator {
        Objects.requireNonNull(generator);
        if(excludeStart > excludeEnd)
            throw new IllegalArgumentException("start:"+excludeStart+" should be less than end:"+excludeEnd);
    }

    static public TsidGenerator fast(long excludeStart, long excludeEnd) {
        return new TsidGenerator(excludeStart, excludeEnd, Tsid::fast);
    }

    public Tsid generate() {
        Tsid tsid;
        long tsidLong;
        do {
            tsid = generator.get();
            tsidLong = tsid.toLong();
        } while(tsidLong >= excludeStart && tsidLong <= excludeEnd);
        return tsid;
    }
}

@fabiolimace
Copy link
Member

fabiolimace commented Dec 1, 2024

Very good example!

I also came up with a solution using a factory builder, but it's very limited to timestamp.

TsidFactory factory = TsidFactory.builder().withTimeFunction(() -> {
    final long startTime = Tsid.TSID_EPOCH + 1;
    long time = System.currentTimeMillis();
    if (time < startTime) time = startTime;
    return time;
}).build();

The function above would work similarly to this PostgreSQL's sequence creation command:

CREATE SEQUENCE my_sequence START WITH 1;

Your TsidGenerator is more configurable.

Another solution I came up with is to add a builder method to include custom behavior. But this adds more complexity to the library. Here is the diff:

diff --git a/src/main/java/com/github/f4b6a3/tsid/TsidFactory.java b/src/main/java/com/github/f4b6a3/tsid/TsidFactory.java
index f8bd2af..0166b0a 100644
--- a/src/main/java/com/github/f4b6a3/tsid/TsidFactory.java
+++ b/src/main/java/com/github/f4b6a3/tsid/TsidFactory.java
@@ -34,6 +34,7 @@ import java.util.concurrent.locks.ReentrantLock;
 import java.util.function.IntFunction;
 import java.util.function.IntSupplier;
 import java.util.function.LongSupplier;
+import java.util.function.LongUnaryOperator;
 
 /**
  * A factory that actually generates Time-Sorted Unique Identifiers (TSID).
@@ -81,6 +82,7 @@ public final class TsidFactory {
 
 	private final long customEpoch;
 	private final LongSupplier timeFunction;
+	private final LongUnaryOperator afterFunction;
 
 	private final IRandom random;
 	private final int randomBytes;
@@ -139,6 +141,7 @@ public final class TsidFactory {
 		this.nodeBits = builder.getNodeBits();
 		this.random = builder.getRandom();
 		this.timeFunction = builder.getTimeFunction();
+		this.afterFunction = builder.getAfterFunction();
 
 		// setup constants that depend on node bits
 		this.counterBits = RANDOM_BITS - nodeBits;
@@ -232,8 +235,8 @@ public final class TsidFactory {
 			final long _time = getTime() << RANDOM_BITS;
 			final long _node = (long) this.node << this.counterBits;
 			final long _counter = (long) this.counter & this.counterMask;
-
-			return new Tsid(_time | _node | _counter);
+			
+			return new Tsid(this.afterFunction.applyAsLong(_time | _node | _counter));
 		} finally {
 			lock.unlock();
 		}
@@ -329,6 +332,19 @@ public final class TsidFactory {
 		private Long customEpoch;
 		private IRandom random;
 		private LongSupplier timeFunction;
+		private LongUnaryOperator afterFunction;
+		
+		public Builder withAfterFunction(LongUnaryOperator afterFunction) {
+			this.afterFunction = afterFunction;
+			return this;
+		}
+		
+		protected LongUnaryOperator getAfterFunction() {
+			if (this.afterFunction == null) {
+				this.afterFunction = (tsid) -> tsid;
+			}
+			return this.afterFunction;
+		}
 
 		/**
 		 * Set the node identifier.

I'll let for a while until I put something in the FAQ or README about it.

@hrstoyanov
Copy link
Author

hrstoyanov commented Dec 1, 2024

Thanks @fabiolimace - I think you have enough material to put something in the README or FAQ, or change this - otherwise very awesome library!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants