-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
module commands to be marked as unstable and require clients to opt in #2766
Conversation
packages/client/lib/client/index.ts
Outdated
return new ((this as any).Multi as Multi)( | ||
this._executeMulti.bind(this), | ||
this._executePipeline.bind(this) | ||
this._executePipeline.bind(this), | ||
this._self.#options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
multi should throw an error on attempted executoin of an unstableResp3 command. this enables that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, with what we discussed yesterday, putting the handler on object via attachCommand, this is no longer neccessary, so reverted.
packages/client/lib/client/index.ts
Outdated
@@ -163,6 +167,9 @@ export default class RedisClient< | |||
static #createModuleCommand(command: Command, resp: RespVersions) { | |||
const transformReply = getTransformReply(command, resp); | |||
return async function (this: NamespaceProxyClient, ...args: Array<unknown>) { | |||
if (command.unstableResp3Module && resp == 3 && !this._self._self.#options?.unstableResp3Modules) { | |||
throw new Error("unstable resp3 module, client not configured with proper flag"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to update the message + include a link
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, but unsure what it should be at the moment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added a FIXME to it, to know we have to work on that string
packages/client/lib/client/index.ts
Outdated
@@ -163,6 +167,9 @@ export default class RedisClient< | |||
static #createModuleCommand(command: Command, resp: RespVersions) { | |||
const transformReply = getTransformReply(command, resp); | |||
return async function (this: NamespaceProxyClient, ...args: Array<unknown>) { | |||
if (command.unstableResp3Module && resp == 3 && !this._self._self.#options?.unstableResp3Modules) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move the if + throw to a function and reuse it everywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. makes sense. would be a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
something like
export function resp3UnstableCheck(resp: RespVersions, unstableResp3Command?: boolean, unstableResp3Client?: boolean) {
if (resp == 3 && unstableResp3Command && !unstableResp3Client) {
throw new Error("unstable resp3 module, client not configured with proper flag");
}
}```
and usage ala
resp3UnstableCheck(resp, command.unstableResp3Module, this._self._self.#options?.unstableResp3Modules);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as discussed on zoom, moved this to how the command is actually attached to object in attachCommand
@@ -2,6 +2,7 @@ import COMMANDS from '../commands'; | |||
import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType, RedisMultiQueuedCommand } from '../multi-command'; | |||
import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping } from '../RESP/types'; | |||
import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; | |||
import { RedisClientOptions } from '.'; | |||
|
|||
type CommandSignature< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be "never" when using RESP3 without enabling unstable commands..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont know how to do that into the type (I wasn't eager to extend the typing of the client even further)
36ad751
to
2b6e99a
Compare
…o opt in to that.
update unstableResp3Module flag to only commands that have unstable api add ability to tag commands to ignore user type mapping (as transformReply will convert it to a non typed map type) update bloom info commands to work with new non typed map ability fill in search/time-series commands and tag with unstableResp3Modules flag as appropriate
4fa8734
to
f13f47a
Compare
adjust all usages of it to SimpleStringReply
packages/json/lib/commands/TYPE.ts
Outdated
// TODO: ?!??! | ||
3: undefined as unknown as () => any | ||
} | ||
3: undefined as unknown as () => ArrayReply<NullReply | BlobStringReply | ArrayReply<BlobStringReply>> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not too sure if this is right..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in resp3, the same reply that is possible in resp2 is returned in resp3, just wrapped in an array.
pub fn json_type<M: Manager>(manager: M, ctx: &Context, args: Vec<RedisString>) -> RedisResult {
let mut args = args.into_iter().skip(1);
let key = args.next_arg()?;
let path = Path::new(args.next_str().unwrap_or(default_path()));
let key = manager.open_key_read(ctx, &key)?;
let value = if path.is_legacy() {
json_type_legacy::<M>(&key, path.get_path())?
} else {
json_type_impl::<M>(&key, path.get_path())?
};
// Check context flags to see if RESP3 is enabled and return the appropriate result
if is_resp3(ctx) {
Ok(vec![value].into())
} else {
Ok(value)
}
}
so if one is ok with the resp2, response, the resp3 response looks right to me.
the problem is really in me messing up the conversion in v4, we have
export declare function transformReply(): string | null | Array<string | null>;
so what we need is the ArrayReply<BlobStringReply | NullReply>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
made the change I mentioned now
@@ -3,8 +3,8 @@ import { SimpleStringReply, Command, RedisArgument } from '@redis/client/dist/li | |||
export default { | |||
FIRST_KEY_INDEX: undefined, | |||
IS_READ_ONLY: true, | |||
transformArguments(index: RedisArgument, cursorId: RedisArgument) { | |||
return ['FT.CURSOR', 'DEL', index, cursorId]; | |||
transformArguments(index: RedisArgument, cursorId: number) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cursorId can go outside the float64 range.. string has to be supported, number is nice to have
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, was a copy form v4 which used number. trying to remember why I changed it from what you did originally, I suspect number might be used elsewhere and needs to be fixed, will look into that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so I'm also unconvinced that "number" is nice to have, its a "number", but in practice, cursors should be opaque, and we shouldn't encourage users to think of it as a number, so thinking that RedisArgument as is, should be enough (as you had originally)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so the issue here is really due to AGGREGATE_WITHCURSOR (where cursor id is returned to user). Changed it to be a NumberReply there. I think before I ran into some problems with tests, will have to redo and see what the problem was.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e. the problem is that AGGREGATE_WITHCURSOR when cursor is a NumberReply will default to returning as a number.
instead returning NumberReply (from WITHCURSOR) for cursor and taking an UnwrapReply for cursor in cursor read/del and its up to the user to therefore have a proper number conversion setup.
Honestly, not happy with it. I'd want to force the NumberReply to be treated as a string and not as a number in WITHCURSOR (and then can force a string in cursor functions)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"I'd want to force the NumberReply to be treated as a string and not as a number in WITHCURSOR (and then can force a string in cursor functions)" - I'd want the server to reply with a string and not a number..
The server not gonna change, we should probably support both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so we have the NumberReply that can be both. With that said, I'd think we should document this (i.e. we strongly reccomend one overrides the type mapping for NumberReplies when using the WITHCURSOR function).
A better solution (which I dont know how to implement) would be to be able to tag commands with a default type mapping that can only override default (blank) client type mapping, but not type mapping that the user specifies.
i.e. by default we might say a NumberReply maps to a a js number, but for some commands we can say it maps to string, and the client would then just do the right thing by default always without user intervention required.
@@ -8,14 +8,15 @@ export interface FtCursorReadOptions { | |||
export default { | |||
FIRST_KEY_INDEX: undefined, | |||
IS_READ_ONLY: true, | |||
transformArguments(index: RedisArgument, cursor: RedisArgument, options?: FtCursorReadOptions) { | |||
const args = ['FT.CURSOR', 'READ', index, cursor]; | |||
transformArguments(index: RedisArgument, cursor: number, options?: FtCursorReadOptions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same cursor comment
@@ -1,28 +1,28 @@ | |||
// import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types'; | |||
// import { Params, pushParamsArgs } from "."; | |||
import { RedisArgument, SimpleStringReply, Command } from '@redis/client/dist/lib/RESP/types'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused. I see a spec.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unit tests only..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am trying to add an execution test, but not sure why I'm seeing output that I am via a decoder
i.e. via telnet
FT.EXPLAIN index '*'
$11
<WILDCARD>
via cli
127.0.0.1:6379> FT.EXPLAIN index '*'
"<WILDCARD>\n"
127.0.0.1:6379>
but via node-redis have to do this to make it pass
assert.equal('<WILDCARD>}\n', reply);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leibale thoughts?
export interface SampleRawReply2 { | ||
timestamp: NumberReply; | ||
value: BlobStringReply; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an object/map in resp2 from the server? no way.. TuplesReply<[timestamp: NumberReply, value: BlobStringReply]>;
is right i think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copied from v4 badly I think. will do what you said.
@@ -18,14 +19,47 @@ export function pushFilterArgument(args: CommandArguments, filter: RedisVariadic | |||
return pushVariadicArguments(args, filter); | |||
} | |||
|
|||
export type MGetRawReply2 = Array<[ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use ArrayReply
instead of Array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done and BlobStringReply instead of string and UnwrapReply in transformReply
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
though had some issues with typing in the transformReply, needs to be validated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and same in RANGE/MRANGE/_WITHLABELS/
@@ -18,14 +19,47 @@ export function pushFilterArgument(args: CommandArguments, filter: RedisVariadic | |||
return pushVariadicArguments(args, filter); | |||
} | |||
|
|||
export type MGetRawReply2 = Array<[ | |||
key: string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BlobStringReply
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it should be BlobStringReply, this was just a port over from v2, but it's a RedisModule_ReplyWithStringBuffer() so BlobString. Will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed above
}; | ||
}, | ||
3(reply: SampleRawReply[3]) { | ||
const [timestamp, value] = reply as unknown as UnwrapReply<typeof reply>; | ||
3(reply: SampleRawReply3): SampleReply3 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In RESP3 the reply raw reply type should be
type SampleRawReply3 = ArrayReply<TuplesReply<[timestamp: NumberReply, value: DoubleReply]>>;
(I checked that in TS.RANGE
, I guess its true for the rest of them?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so I'm confused about how tuplereply and array reply work. do they have to be used together?
i.e. in looking at it now i see for range, we basically just have a single element but the function is a an arrayreply wrapping the tuple reply. I'm confused overall.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in looking at it more, I dont see why you would make SampleRawReply3 an ArrayReply, I'd think both resp2/resp3 should just be TupleReply and then RANGE et al would have it wrapped in an ArrayReply as get an array of the tuples. In practice then, resp2/resp3 would be the exact same then (at least for range) and therefore the code could be simplified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type in my comments is for samples, but named sample, my bad..
This should work
type SampleRawReply = TuplesReply<[timestamp: NumberReply, value: DoubleReply]>;
const transformSampleReply = {
2(reply: Resp2Reply<SampleRawReply>) {
const [ timestamp, value ] = reply as unknown as UnwrapReply<typeof reply>;
return {
timestamp,
value: Number(value)
};
},
3(reply: SampleRawReply) {
const [ timestamp, value ] = reply as unknown as UnwrapReply<typeof reply>;
return {
timestamp,
value
};
}
};
type SamplesRawReply = ArrayReply<SampleRawReply>;
const transformSamplesReply = {
2(reply: Resp2Reply<SamplesRawReply>) {
return (reply as unknown as UnwrapReply<typeof reply>)
.map(sample => transformSampleReply[2](sample));
},
3(reply: SamplesRawReply) {
return (reply as unknown as UnwrapReply<typeof reply>)
.map(sample => transformSampleReply[3](sample)); }
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tried implementing this, but cant do it right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe got it now
didn't update tests, and got types wrong
didn't like the null prototype for deep equal
96dcdba
to
ea51f39
Compare
Change testing
1 - use redis-stack only 2 - protect hscan-novalues for older versions 3 - disable graph tests
…BELS|_SELECTED_LABELS]`
6ef31a1
to
3f65936
Compare
infrastrucutre code to enable search module commands to be marked unstable in RESP3 client usage.
needs, finish conversion of commands to v5, where I'm stuck at the moment