Adding Set commands
gozeloglu opened this issue · comments
Hi @nalgeon.
I just discovered your project and loved it. I want to contribute to the project by adding the set commands you listed in README: https://github.com/nalgeon/redka?tab=readme-ov-file#sets. What do you think? Is it okay to implement from me? I can implement SADD
, SCARD
, SDIFF
, SMEMBER
, SPOP
, and SREM
. Or, maybe all of them 😃
Hi Gökhan, thank you for your interest in Redka! I think it's a great idea :)
I suggest we start by deciding on SQL schema changes and method signatures right here in the issue. That way we reduce the risk of further debate in the PR phase. Once we agree on schema and API, you can move on to implementation.
Hi @nalgeon,
I understand your point. You are right. Let me introduce the structure for Set commands. We can discuss and enhance them.
SADD
func (d *DB) Sadd(key string, members ...any) (int, error) {}
We return an error if len(members) == 0
. Or, as an alternative approach, we can implement as follows:
func (d *DB) Sadd(key string, member any, members ...any) (int, error) {}
In that way, we guarantee that there will be at least 1 member.
SCARD
// Scard returns number of elements of the set stored at key.
func (d *DB) Scard(key string) int {}
SDIFF
func (d *DB) Sdiff(key string, keys ...string) []any {}
SMEMBER
func (d *DB) Smember(key string) []any {}
SPOP
func (d *DB) Spop(key string, count int) ([]any, error) {}
SREM
func (d *DB) Srem(key string, member any, members ...any) (int, error) {}
SQL Schema
create table if not exists
rset (
key_id integer not null,
member text not null, -- this can be blob as well. I am not sure
foreign key (key_id) references rkey (id)
on delete cascade
);
I am open to discuss the method signatures and SQL Schama. In SQL part, there might be missing parts because I had limited time to create this draft. Also, I can define method signatures for rest of the commands. I took only the commands I mentioned above.
I think it's better to start with a limited-scope PR. I suggest implementing SADD, SREM, and SISMEMBER. And only at the Go API level (without touching the command
package, which may be the scope of the next PR).
I think the schema is fine (with a blob
). However, you also need a unique index and a view.
As for method signatures, there is no need for the S
prefix, since they are implemented in a separate package rset
. Also, it's better to keep the naming consistent, so I'd go with the following:
Add(key string, elems ...any) (int, error)
Delete(key string, elems ...any) (int, error)
Exists(key string, elem any) (bool, error)
If you agree with the above, could you please also write SQL queries for the three methods mentioned?
I agree that starting with limited commands is a good way. Function/method signatures look fine except for naming. Our command names are Add
, Rem
(Remove)
, and IsMember
. However, you suggested Delete
and Exists
instead of real names. I think choosing real command names will be more appropriate for naming convention and consistent naming scope. That's why I suggest using Remove
and IsMember
as function names. I know I need to write SQL queries for creating unique index and view. Please give me a little more time to create queries since I am a little bit busy.
Sorry, I'm not ready to go with Rem
and IsMember
. Redis naming is really inconsistent, the same things are called differently all the time (Exists/IsMember, Card/Len/Count, Del/Rem etc). I'm sure Salvatore had his reasons for doing this, and of course I will keep the command names for the RESP API. But I don't want this inconsistency in the Go API.
What do you think about the SQL queries?
create table if not exists
rset (
key_id integer not null,
member blob not null,
foreign key (key_id) references rkey (id)
on delete cascade
);
create unique index if not exists
rset_pk_idx on rset (key_id, member);
create index if not exists
rset_key_id_idx on rset (key_id);
create view if not exists
vset as
select
rkey.id as key_id, rkey.key, rset.member
datetime(etime/1000, 'unixepoch') as etime,
datetime(mtime/1000, 'unixepoch') as mtime
from rkey join rset on rkey.id = rset.key_id
where rkey.type = 3
and (rkey.etime is null or rkey.etime > unixepoch('subsec'))
I think it's mostly fine. The rset_key_id_idx
is unnecessary, and member
is better called elem
, so it'll probably be like this:
create table if not exists
rset (
key_id integer not null,
elem blob not null,
foreign key (key_id) references rkey (id)
on delete cascade
);
create unique index if not exists
rset_pk_idx on rset (key_id, elem);
create view if not exists
vset as
select
rkey.id as key_id, rkey.key, rset.elem
datetime(etime/1000, 'unixepoch') as etime,
datetime(mtime/1000, 'unixepoch') as mtime
from rkey join rset on rkey.id = rset.key_id
where rkey.type = 3
and (rkey.etime is null or rkey.etime > unixepoch('subsec'))
Okay, this didn't work out. I'm closing the issue and the associated PR.