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

adding ability to add prefixes to indexes for Searches #245

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

slorello89
Copy link
Member

Adds overload to to RedisCollection to enable you to create your own prefixes dynamically, this is helpful for cases such as #242 where someone might want to have an index per tenant with it's own set of prefixes.

Basically now you initalize a RedisCollection with a prefix, that prefix then becomes what your Collection uses to:

  1. Serialize the index - the index name is generated as {classnametolower}-{prefix}-idx, and of course the prefixe for the index is the specified prefix.
  2. At insertion into redis, the objects are given a keyname that is: {prefix}:{id}
  3. When Querying, the collection uses the new sub-tenants index in the query.

@slorello89
Copy link
Member Author

@houstonhaynes @VagyokC4 - what are your thoughts?

@houstonhaynes
Copy link

houstonhaynes commented Oct 26, 2022

I'm confused by what you mean by "prefix".

I took the term to mean "the part of the key that the index applies to"

Where it looks like you interpret it as "the part of the name that goes before "-idx"

Did I get that right?

What I was originally talking included both the key-part that the index uses to discover/add records. I'll give an example:

Say I have two customers in a Redis instance. One customer has the ID of "A" and the other "B". The keys I generate for the records for each respectively would be "Customer:A:[some-random-GUID]" and "Customer:B:[some-random-GUID]"

Then I would create three indices, named "CustomerA-idx" "CustomerB-idx" and "CustomerAdmin-idx".

CustomerA-idx would look for all keys with the partial key match to "Customer:A:"
CustomerB-idx would look for all keys with the partial key match to "Customer:B:
"
CustomerAdmin-idx would look for all keys with the partial key match to "Customer:*"

So in effect - the CustomerA ACL would be set to access only "CustomerA-idx" and so would be tenant limited to Customer "A" records. By extrapolation the CustomerB ACL would limit those users to only see Customer "B" keyed records. And finally - admins would have a CustomerAdmin ACL would would be able to see all keys because they're using an index which matches to the "Customer:*" partial key pattern.

To be more specific - here's a sample of what I would set for the ACL for each company "user":

ACL SETUSER companya on >coapwd +@all -@dangerous +client +info +echo &company:a: ~company:a:*
ACL SETUSER companyb on >cobpwd +@all -@dangerous +client +info +echo &company:b: ~company:b:*

And here's what I'd set for the admin role:

ACL SETUSER admin on >adminpwd +@all -@dangerous +client +info +echo &company: ~company:*

I hope this clarifies what I'm trying to achieve. This means that both IndexName and Prefix need to be settable at run-time.

@slorello89
Copy link
Member Author

slorello89 commented Oct 27, 2022

@houstonhaynes - So the idea here is that the index's name would be computable given a prefix and the model, the format would now be classname-Prefix-idx, so you can easily setup your acls to block them still. What prefix most consequentially is, is a means of determing what your key-prefixes are when they are inserted into redis, the example you provide should work. Given the model

[Document(StorageType = StorageType.Json, Prefixes = new string[]{"Customer"})]
public class Customer
{
    [RedisIdField] public string Id { get; set; }
    [Indexed] public string FirstName { get; set; }
    [Indexed] public string LastName { get; set; }
}

you would then be able to do something like:

// connect
var provider = new RedisConnectionProvider("redis://localhost:6379");
var connection = provider.Connection;
connection.Execute("FLUSHDB"); // don't run this in production

var customers = provider.RedisCollection<Customer>();
var customersA = provider.RedisCollection<Customer>(false, 1000, "Customer:A");
var customersB = provider.RedisCollection<Customer>(false, 1000, "Customer:B");
customers.CreateIndex();
customersA.CreateIndex();
customersB.CreateIndex();
customersA.Insert(new Customer { FirstName = "Customer", LastName = "A" });
customersB.Insert(new Customer { FirstName = "Customer", LastName = "B" });

Console.WriteLine("Customers in customers collection");
foreach (var customer in customers)
{
    Console.WriteLine($"Customer: {customer.Id} FirstName: {customer.FirstName}, LastName: {customer.LastName}");
}

Console.WriteLine("Customers in customersA collection");

foreach (var customer in customersA)
{
    Console.WriteLine($"Customer: {customer.Id} FirstName: {customer.FirstName}, LastName: {customer.LastName}");
}

Console.WriteLine("Customers in customersB collection");
            
foreach (var customer in customersB)
{
    Console.WriteLine($"Customer: {customer.Id} FirstName: {customer.FirstName}, LastName: {customer.LastName}");
}

the customersA collection will generate this index:

"FT.CREATE" "customer-Customer:A-idx" "ON" "Json" "PREFIX" "1" "Customer:A:" "SCHEMA" "$.FirstName" "AS" "FirstName" "TAG" "SEPARATOR" "|" "$.LastName" "AS" "LastName" "TAG" "SEPARATOR" "|" "$.Email" "AS" "Email" "TAG" "SEPARATOR" "|" "$.Age" "AS" "Age" "NUMERIC" "SORTABLE" "$.Home" "AS" "Home" "GEO" "SORTABLE"

Whereas customersB will create:

"FT.CREATE" "customer-Customer:B-idx" "ON" "Json" "PREFIX" "1" "Customer:B:" "SCHEMA" "$.FirstName" "AS" "FirstName" "TAG" "SEPARATOR" "|" "$.LastName" "AS" "LastName" "TAG" "SEPARATOR" "|" "$.Email" "AS" "Email" "TAG" "SEPARATOR" "|" "$.Age" "AS" "Age" "NUMERIC" "SORTABLE" "$.Home" "AS" "Home" "GEO" "SORTABLE"

Then as you insert objects into redis via the prefixed collections, it will produces keys like:

"JSON.SET" "Customer:A:01GGCKG1RF0KC6KPT4RGQKQ08M" "." "{\"Id\":\"01GGCKG1RF0KC6KPT4RGQKQ08M\",\"FirstName\":\"Customer\",\"LastName\":\"A\",\"Age\":0,\"Home\":\"0,0\"}"

and

"JSON.SET" "Customer:B:01GGCKG1RYBSXV89RH82P5RG2F" "." "{\"Id\":\"01GGCKG1RYBSXV89RH82P5RG2F\",\"FirstName\":\"Customer\",\"LastName\":\"B\",\"Age\":0,\"Home\":\"0,0\"}"

Because the Prefix for the class Customer is Customer, the admin collection (just the redis-collection for Customer) will be able to see everything, where as the customersA and customersB collection will only be able to see what they've prefixed to.

As for ACLs, you can of course limit which index is visible to which user, you can also limit keys via prefix I think? (about 85% sure of that lol) so you can manage this by prefix. But of course as long as you are using the right collection for the right customer, the ACLs are really just a backup.

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

Successfully merging this pull request may close these issues.

Support dynamic index definition for multi-tenancy (Prefix, IndexName) Prefix Generation Strategy
2 participants