-
Notifications
You must be signed in to change notification settings - Fork 5
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
Partial Key Queries #33
base: master
Are you sure you want to change the base?
Conversation
} | ||
|
||
partialKey( | ||
params: AtLeastOne<{ [X in U]: string }> & AtLeastOne<{ [Y in V]: 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.
This is where I think I could possibly use a generated type of only valid combinations of keys.
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.
Hmm... I think the types we'd want to express here would be a union like:
{ country: string } | { country: string, state: string } | { country: string, state: string, city: string }
This is tricky to do as is since:
- We have a generic model class
- All it knows inside here is that partitionKeys / sortKeys are a union type.
- Afaik there's not a way to extract keys of a union type in-order and re-combine them to what we'd want (maybe there is, I just don't know about it).
This would be easier to express if we explicitly codegenned each model vs having a generic model class as we do today. Since then, we could just codegen the right types directly
Nice @cjoelrun ! Going to try to get to reviewing this ~soon. |
I think there's still some work to be done, just wanted your opinion before I continue in this or another direction. |
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.
Nice @cjoelrun !
Sorry it took so long to get to this PR. But really exciting to see!
I think the overall approach here is a good short-term solution, in that we can check-in an initial version of complex / partial key support that doesn't enforce needing correct combinations of partial keys. As a "v1 / mvp" of this feature we could just allow a Partial<T>
where T
is the "full key".
Then, the long-term approach I'd want to take is to refactor our Model
class / codegen to make expressing the types needed to enforce correct partial key combinations easier. I think this will basically result in axe-ing our generic Model
class and generating specific classes for each model instead.
So, for now:
-
I'd like to DRY-up a lot of the isArray / isNotArray checks that are a result of threading the
string | string[]
type throughout the code. I think we could do this pretty easily by just normalizing non-array types to arrays near the "top" of where they appear. -
There's a few small things related to style/naming that I've left comments on.
@@ -1,4 +1,5 @@ | |||
import { Fields, Table } from "./types" | |||
import {buildKey, replaceKeyDollar} from "./util" |
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.
These imports seem not be used?
encryptionBlacklistSet.add(pk.replace("$", "")) | ||
encryptionBlacklistSet.add(sk.replace("$", "")) | ||
|
||
const pks = buildKeyArray(model.keys.partitionKey[1]) |
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.
Looks like you can just use the pk
and sk
variables above as they're already bound to model.keys.partitionKey[1]
and model.keys.sortKey[1]
}) | ||
|
||
table.gsis.forEach(({ name, partitionKey, sortKey }) => { | ||
encryptionBlacklistSet.add(partitionKey.replace("$", "")) | ||
encryptionBlacklistSet.add(sortKey.replace("$", "")) | ||
const pk = buildKeyArray(partitionKey) |
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.
Nit: would just inline to: buildKeyArray(partitionKey).forEach(key => encryptionBlacklist.add(key))
@@ -11,3 +11,27 @@ export function groupBy<T extends { [key: string]: any }, U extends keyof T>( | |||
|
|||
return Object.entries(groupedItems) | |||
} | |||
|
|||
export function replaceKeyDollar(key: string | 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.
Hmm...we're threading this string | string[]
type through multiple places in the code. I'd prefer we only do this check once at the "top level" and just transform string
types into string[]
. That way, everything that comes after can just assume the type is string[]
@@ -11,3 +11,27 @@ export function groupBy<T extends { [key: string]: any }, U extends keyof T>( | |||
|
|||
return Object.entries(groupedItems) | |||
} | |||
|
|||
export function replaceKeyDollar(key: string | 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.
Style: all function
types outside of tests should explicitly specify their return types. This avoids accidentally getting the wrong type inferred.
For inline lambdas, e.g. const f = () => { ... }
, it's fine to leave the return type off since they're usually very simple .
private buildKey(prefix: string, key: string): string { | ||
/* TODO: Had issue mapping unioned fields of T & V */ | ||
private _buildPartitionKey(model: AtLeastOne<{ [X in U]: string }>, fields: U | U[]): string | string[] { | ||
if (Array.isArray(fields)) { |
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 think this is another place we're we'd benefit from normalizing non-array keys into arrays. For example, we could transform keys to arrays internally in the model's constructor.
} | ||
|
||
partialKey( | ||
params: AtLeastOne<{ [X in U]: string }> & AtLeastOne<{ [Y in V]: 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.
Hmm... I think the types we'd want to express here would be a union like:
{ country: string } | { country: string, state: string } | { country: string, state: string, city: string }
This is tricky to do as is since:
- We have a generic model class
- All it knows inside here is that partitionKeys / sortKeys are a union type.
- Afaik there's not a way to extract keys of a union type in-order and re-combine them to what we'd want (maybe there is, I just don't know about it).
This would be easier to express if we explicitly codegenned each model vs having a generic model class as we do today. Since then, we could just codegen the right types directly
@@ -322,3 +322,49 @@ Tables: | |||
|
|||
expect(lines).toContainEqual(`name: BestNameEvah`) | |||
}) | |||
|
|||
it("should generate a complex key model", () => { |
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.
Nice test, thanks!
Partitions: | ||
ComplexAuthors: | ||
ComplexAuthor: | ||
partitionKey: [Author, $id] |
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.
Would like to add a test for partition keys as well, since it looks like this PR supports complex keys for both.
@@ -11,3 +11,5 @@ export type ExtractKeyType<T> = T extends PartitionAndSortKey<infer U> | |||
export type GroupedModels<T extends TaggedModel> = { | |||
[K in T["model"]]: T extends { model: K } ? T[] : never | |||
} | |||
|
|||
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U] |
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.
Hmm... This ends up generating a pretty crazy type that's hard to read. And it appears to be basically equivalent to Partial<{ [X in U]: string }>
-- i.e. you can specify one or more of the key parts.
Looking to get some feedback on approach and thoughts about how to make partialKey queries safer.