Skip to content
immudb docker run -d --net host -it --name immudb codenotary/immudb:latest

Indexes

Sorted sets#

On top of the key value store immudb provides secondary indexes to help developers to handle complex queries.

The sorted set data type provides a simple secondary index that can be created with immudb. This data structure contains a set of references to other key-value entries. Elements of this set are ordered using a floating-point score specified for each element upon insertion. Entries having equal score will have the order in which they were inserted into the set.

Note: The score type is a 64-bit floating point number to support a large number of uses cases. 64-bit floating point gives a lot of flexibility and dynamic range, at the expense of having only 53-bits of integer. When a 64-bit integer is cast to a float value there could be a loss of precision, in which case the order of entries having same float64 score value will be determined by the insertion order.

The KV entry referenced in the set can be bound to a specific transaction id - such entry is called a bound reference. A bound reference will always get the value for the key at a specific transaction instead of the most recent value, including a case where one set contains multiple values for the same key but for different transactions. That way, sets allow optimal access to historical data using a single immudb read operation.

Note: If a compound operation is executed with the ExecAll call, a bound entry added to the set can reference a key created/updated in the same ExecAll call. To make such an operation, set the BoundRef value to true and the AtTx value to 0.

Inserting entries into sets can be done using the following operations: ZAdd, VerifiedZAdd, ZAddAt, VerifiedZAddAt, ExecAll. Those operations accept the following parameters:

  • Set: the name of the collection
  • Score: entry score used to order items within the set
  • Key: the key of entry to be added to the set
  • AtTx: for bound references, a transaction id at which the value will be read, if set to 0 for ExecAll operation, current transaction id will be used. Optional
  • BoundRef: if set to true, this will be a reference bound to a specific transaction. Optional
  • NoWait: if set to true, don’t wait for indexing to be finished after adding this entry

Reading data from the set can be done using the following operations: ZScan, StreamZScan. Those operations accept the following parameters:

  • Set: the name of the collection
  • SeekKey: initial key for the first entry in the iteration. Optional
  • SeekScore: the min or max score for the first entry in the iteration, depending on Desc value. Optional
  • SeekAtTx: the tx id for the first entry in the iteration. Optional
  • InclusiveSeek: the element resulting from the combination of the SeekKey SeekScore and SeekAtTx is returned with the result. Optional
  • Desc: If set to true, entries will be returned in an descending (reversed) order. Optional
  • SinceTx: immudb will wait that the transaction provided by SinceTx be processed. Optional
  • NoWait: when true scan doesn’t wait that txSinceTx is processed. Optional
  • MinScore: minimum score filter. Optional
  • MaxScore: maximum score filter. Optional
  • Limit: maximum number of returned items. Optional

Note: issuing a ZScan or StreamZScan operation will by default wait for the index to be up-to-date. To avoid waiting for the index (and thus to allow reading the data from some older state), set the SinceTx to a very high value exceeding the most recent transaction id (e.g. maximum int value) and set NoWait to true.

Go

  1package main
  2
  3import (
  4	"context"
  5	"encoding/json"
  6	"fmt"
  7	"log"
  8	"math"
  9
 10	"github.com/codenotary/immudb/pkg/api/schema"
 11	immudb "github.com/codenotary/immudb/pkg/client"
 12)
 13
 14func main() {
 15	opts := immudb.DefaultOptions().
 16		WithAddress("localhost").
 17		WithPort(3322)
 18
 19	client := immudb.NewClient().WithOptions(opts)
 20	err := client.OpenSession(
 21		context.TODO(),
 22		[]byte(`immudb`),
 23		[]byte(`immudb`),
 24		"defaultdb",
 25	)
 26	if err != nil {
 27		log.Fatal(err)
 28	}
 29
 30	defer client.CloseSession(context.TODO())
 31
 32	i1, err := client.Set(
 33
 34		context.TODO(),
 35		[]byte(`user1`),
 36		[]byte(`user1@mail.com`),
 37	)
 38	if err != nil {
 39		log.Fatal(err)
 40	}
 41	i2, err := client.Set(
 42		context.TODO(),
 43		[]byte(`user2`),
 44		[]byte(`user2@mail.com`),
 45	)
 46	if err != nil {
 47		log.Fatal(err)
 48	}
 49	i3, err := client.Set(
 50		context.TODO(),
 51		[]byte(`user3`),
 52		[]byte(`user3@mail.com`),
 53	)
 54	if err != nil {
 55		log.Fatal(err)
 56	}
 57	i4, err := client.Set(
 58		context.TODO(),
 59		[]byte(`user3`),
 60		[]byte(`another-user3@mail.com`),
 61	)
 62	if err != nil {
 63		log.Fatal(err)
 64	}
 65
 66	if _, err = client.ZAddAt(
 67		context.TODO(),
 68		[]byte(`age`), 25, []byte(`user1`), i1.Id,
 69	); err != nil {
 70		log.Fatal(err)
 71	}
 72	if _, err = client.ZAddAt(
 73		context.TODO(),
 74		[]byte(`age`), 36, []byte(`user2`), i2.Id,
 75	); err != nil {
 76		log.Fatal(err)
 77	}
 78	if _, err = client.ZAddAt(
 79		context.TODO(),
 80		[]byte(`age`), 36, []byte(`user3`), i3.Id,
 81	); err != nil {
 82		log.Fatal(err)
 83	}
 84	if _, err = client.ZAddAt(
 85		context.TODO(),
 86		[]byte(`age`), 54, []byte(`user3`), i4.Id,
 87	); err != nil {
 88		log.Fatal(err)
 89	}
 90
 91	zscanOpts1 := &schema.ZScanRequest{
 92		Set:      []byte(`age`),
 93		SinceTx:  math.MaxUint64,
 94		NoWait:   true,
 95		MinScore: &schema.Score{Score: 36},
 96	}
 97
 98	the36YearsOldList, err := client.ZScan(context.TODO(), zscanOpts1)
 99	if err != nil {
100		log.Fatal(err)
101	}
102	s, _ := json.MarshalIndent(the36YearsOldList, "", "\t")
103	fmt.Print(string(s))
104
105	oldestReq := &schema.ZScanRequest{
106		Set:       []byte(`age`),
107		SeekKey:   []byte{0xFF},
108		SeekScore: math.MaxFloat64,
109		SeekAtTx:  math.MaxUint64,
110		Limit:     1,
111		Desc:      true,
112		SinceTx:   math.MaxUint64,
113		NoWait:    true,
114	}
115
116	oldest, err := client.ZScan(context.TODO(), oldestReq)
117	if err != nil {
118		log.Fatal(err)
119	}
120	s, _ = json.MarshalIndent(oldest, "", "\t")
121	fmt.Print(string(s))
122}

Java

 1package io.codenotary.immudb.helloworld;
 2
 3import java.util.Iterator;
 4
 5import io.codenotary.immudb4j.FileImmuStateHolder;
 6import io.codenotary.immudb4j.ImmuClient;
 7import io.codenotary.immudb4j.ZEntry;
 8
 9public class App {
10
11    public static void main(String[] args) {
12
13        ImmuClient client = null;
14
15        try {
16
17            FileImmuStateHolder stateHolder = FileImmuStateHolder.newBuilder()
18                    .withStatesFolder("./immudb_states")
19                    .build();
20
21            client = ImmuClient.newBuilder()
22                    .withServerUrl("127.0.0.1")
23                    .withServerPort(3322)
24                    .withStateHolder(stateHolder)
25                    .build();
26
27            client.openSession("defaultdb", "immudb", "immudb");
28
29            byte[] value1 = { 0, 1, 2, 3 };
30            byte[] value2 = { 4, 5, 6, 7 };
31
32            client.set("key1", value1);
33            client.set("key2", value2);
34
35            client.zAdd("set1", "key1", 1);
36
37            client.zAdd("set1", "key2", 2);
38
39            Iterator<ZEntry> it = client.zScan("set1");
40           
41            while (it.hasNext()) {
42                ZEntry zentry = it.next();
43                System.out.format("('%s', '%f')\n", new String(zentry.getKey()), zentry.getScore());
44            }
45
46            client.closeSession();
47
48        } catch (Exception e) {
49            e.printStackTrace();
50        } finally {
51            if (client != null) {
52                try {
53                    client.shutdown();
54                } catch (InterruptedException e) {
55                    e.printStackTrace();
56                }
57            }
58        }
59
60    }
61
62}

.NET

 1var client = new ImmuClient();
 2await client.Open("immudb", "immudb", "defaultdb");
 3
 4byte[] value1 = { 0, 1, 2, 3 };
 5byte[] value2 = { 4, 5, 6, 7 };
 6
 7try
 8{
 9    await client.Set("zadd1", value1);
10    await client.Set("zadd2", value2);
11}
12catch (CorruptedDataException e)
13{
14   Console.WriteLine("A CorruptedDataException occurred.");
15}
16
17try
18{
19    await client.ZAdd("set1", "zadd1", 1);
20    await client.ZAdd("set1", "zadd2", 2);
21
22    await client.ZAdd("set2", "zadd1", 2);
23    await client.ZAdd("set2", "zadd2", 1);
24}
25catch (CorruptedDataException e)
26{
27    Console.WriteLine("A CorruptedDataException occurred");
28}
29
30List<ZEntry> zScan1 = await client.ZScan("set1", 5, false);
31
32Console.WriteLine(zScan1.Count);
33
34await client.Close();

Python

 1from immudb import ImmudbClient
 2
 3URL = "localhost:3322"  # immudb running on your machine
 4LOGIN = "immudb"        # Default username
 5PASSWORD = "immudb"     # Default password
 6DB = b"defaultdb"       # Default database name (must be in bytes)
 7
 8def main():
 9    client = ImmudbClient(URL)
10    client.login(LOGIN, PASSWORD, database = DB)
11    client.set(b"user1", b"user1@mail.com")
12    client.set(b"user2", b"user2@mail.com")
13    client.set(b"user3", b"user3@mail.com")
14    client.set(b"user4", b"user3@mail.com")
15
16    client.zAdd(b"age", 100, b"user1")
17    client.zAdd(b"age", 101, b"user2")
18    client.zAdd(b"age", 99, b"user3")
19    client.zAdd(b"age", 100, b"user4")
20
21    scanResult = client.zScan(b"age", b"", 0, 0, True, 50, False, 100, 101)
22    print(scanResult)   # Shows records with 'age' 100 <= score < 101
23                        # with descending order and limit = 50
24
25if __name__ == "__main__":
26    main()

Node.js

 1import ImmudbClient from 'immudb-node'
 2import Parameters from 'immudb-node/types/parameters'
 3
 4const IMMUDB_HOST = '127.0.0.1'
 5const IMMUDB_PORT = '3322'
 6const IMMUDB_USER = 'immudb'
 7const IMMUDB_PWD = 'immudb'
 8
 9const cl = new ImmudbClient({ host: IMMUDB_HOST, port: IMMUDB_PORT });
10
11(async () => {
12    await cl.login({ user: IMMUDB_USER, password: IMMUDB_PWD })
13
14    const { id: id1 } = await cl.set({ key: 'user1', value: 'user1@mail.com' })
15    const { id: id2 } = await cl.set({ key: 'user2', value: 'user2@mail.com' })
16    const { id: id3 } = await cl.set({ key: 'user3', value: 'user3@mail.com' })
17    const { id: id4 } = await cl.set({ key: 'user3', value: 'another-user3@mail.com' })
18
19    const zAddAtReq1: Parameters.ZAddAt = {
20        set: 'age',
21        score: 25,
22        key: 'user1',
23        attx: id1
24    }
25    const zAddAtRes1 = await cl.zAddAt(zAddAtReq1)
26    const zAddAtReq2: Parameters.ZAddAt = {
27        set: 'age',
28        score: 36,
29        key: 'user2',
30        attx: id2
31    }
32    const zAddAtRes2 = await cl.zAddAt(zAddAtReq2)
33    const zAddAtReq3: Parameters.ZAddAt = {
34        set: 'age',
35        score: 36,
36        key: 'user3',
37        attx: id3
38    }
39    const zAddAtRes3 = await cl.zAddAt(zAddAtReq3)
40    const zAddAtReq4: Parameters.ZAddAt = {
41        set: 'age',
42        score: 54,
43        key: 'user4',
44        attx: id4
45    }
46    const zAddAtRes4 = await cl.zAddAt(zAddAtReq4)
47
48    const zScanReq: Parameters.ZScan = {
49        set: 'age',
50        sincetx: 0,
51        nowait: true,
52        minscore: {
53            score: 36
54        }
55    }
56    const zScanRes = await cl.zScan(zScanReq)
57    console.log('success: zScan all 36-years-old users', zScanRes)
58})()

Others

If you’re using another development language, please refer to the immugw option.

Edit this page on GitHub Last updated