Skip to content

Commit

Permalink
Merge pull request #22 from jnssnmrcs/fix-column-indexes
Browse files Browse the repository at this point in the history
Fix column indexes. Fixes #4 and partly #16 (create function checks need to be fixed as well)
  • Loading branch information
skoshx authored Jul 4, 2023
2 parents ce61a15 + b73caae commit b5f59e1
Show file tree
Hide file tree
Showing 10 changed files with 341 additions and 80 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ import { createPentagon } from "https://deno.land/x/pentagon/mod.ts";
const kv = await Deno.openKv();

export const User = z.object({
id: z.string().uuid().describe("primary, unique"),
id: z.string().uuid().describe("primary"),
createdAt: z.date(),
name: z.string(),
});

export const Order = z.object({
id: z.string().uuid().describe("primary, unique"),
id: z.string().uuid().describe("primary"),
createdAt: z.date(),
name: z.string(),
userId: z.string().uuid(),
Expand Down
2 changes: 2 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 8 additions & 6 deletions src/crud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export async function create<T extends TableDefinition>(
createArgs: CreateArgs<T>,
): Promise<WithVersionstamp<z.output<T["schema"]>>> {
let res = kv.atomic();
const keys = schemaToKeys(tableDefinition.schema, createArgs.data);
const keys = schemaToKeys(tableName, tableDefinition.schema, createArgs.data);
const indexKeys = keysToIndexes(tableName, keys);
const item: z.output<T["schema"]> = tableDefinition.schema.parse(
createArgs.data,
Expand Down Expand Up @@ -132,7 +132,7 @@ export async function createMany<T extends TableDefinition>(
const items: z.output<T["schema"]>[] = [];

for (const data of createManyArgs.data) {
const keys = schemaToKeys(tableDefinition.schema, data);
const keys = schemaToKeys(tableName, tableDefinition.schema, data);
const indexKeys = keysToIndexes(tableName, keys);
const item: z.output<T["schema"]> = tableDefinition.schema.parse(data);

Expand Down Expand Up @@ -161,7 +161,11 @@ export async function findMany<T extends TableDefinition>(
tableDefinition: T,
queryArgs: QueryArgs<T>,
) {
const keys = schemaToKeys(tableDefinition.schema, queryArgs.where ?? []);
const keys = schemaToKeys(
tableName,
tableDefinition.schema,
queryArgs.where ?? [],
);
const indexKeys = keysToIndexes(tableName, keys);
const foundItems = await whereToKeys(
kv,
Expand All @@ -172,9 +176,7 @@ export async function findMany<T extends TableDefinition>(

if (queryArgs.include) {
for (
const [relationName, relationValue] of Object.entries(
queryArgs.include,
)
const [relationName, relationValue] of Object.entries(queryArgs.include)
) {
// Relation name
const relationDefinition = tableDefinition.relations?.[relationName];
Expand Down
114 changes: 78 additions & 36 deletions src/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import { isKeyOf } from "./util.ts";

export const KeyPropertySchema = z.enum(["primary", "unique", "index"]);

export function parseKeyProperties(keyPropertyString: string): KeyProperty[] {
return keyPropertyString
export function parseKeyProperties(
tableName: string,
property: string,
keyPropertyString: string,
): KeyProperty | undefined {
const parsedProperties = keyPropertyString
.split(",")
.map((key) => key.trim())
.map((key) => {
Expand All @@ -25,39 +29,63 @@ export function parseKeyProperties(keyPropertyString: string): KeyProperty[] {
);
}
});

if (parsedProperties.length > 1) {
throw new Error(
`Table '${tableName}' can't have more than one type of index for property ${property}`,
);
}

return parsedProperties[0];
}

export function schemaToKeys<T extends ReturnType<typeof z.object>>(
tableName: string,
schema: T,
values: Partial<z.input<T>>,
): AccessKey[] {
const keys: AccessKey[] = [];

for (const [key, value] of Object.entries(schema.shape)) {
if (value.description) {
const parsedProperties = parseKeyProperties(value.description);
const accessKeys = Object.entries(schema.shape).reduce(
(current, [key, value]) => {
const inputValue = values[key];
if (!inputValue) continue;

const newKey: AccessKey = { value: inputValue };

for (let i = 0; i < parsedProperties.length; i++) {
if (parsedProperties[i] === "primary") {
newKey.primary = true;
}
if (parsedProperties[i] === "unique") {
newKey.unique = true;
}
if (parsedProperties[i] === "index") {
newKey.suffix = `_by_${key}`;
}

if (!value.description || !inputValue) {
return current;
}

keys.push(newKey);
}
const keyType = parseKeyProperties(tableName, key, value.description);

switch (keyType) {
case "primary":
current.push({ value: inputValue, type: "primary" });
break;
case "unique":
current.push({
value: inputValue,
type: "unique",
suffix: `_by_unique_${key}`,
});
break;
case "index":
current.push({
value: inputValue,
type: "index",
suffix: `_by_${key}`,
});
break;
}

return current;
},
[] as AccessKey[],
);

const primaryKeys = accessKeys.filter(({ type }) => type === "primary");

if (primaryKeys.length > 1) {
throw new Error(`Table ${tableName} Can't have more than one primary key`);
}

return keys;
return accessKeys;
}

/**
Expand All @@ -69,22 +97,36 @@ export function keysToIndexes(
tableName: string,
accessKeys: AccessKey[],
): Deno.KvKey[] {
const keys: Deno.KvKey[] = [];
const primaryKey = accessKeys.find(({ type }) => type === "primary");

for (let i = 0; i < accessKeys.length; i++) {
// Primary key values
if (accessKeys[i].primary) {
keys.push([tableName, accessKeys[i].value]);
continue;
return accessKeys.map((accessKey) => {
// Primary key
if (accessKey.type === "primary") {
return [tableName, accessKey.value];
}
// Indexed values
if (accessKeys[i].suffix) {
keys.push([`${tableName}${accessKeys[i].suffix}`, accessKeys[i].value]);
continue;

// Unique indexed key
if (accessKey.type === "unique") {
return [`${tableName}${accessKey.suffix}`, accessKey.value];
}
}

return keys;
// Non-unique indexed key
if (accessKey.type === "index") {
if (!primaryKey) {
throw new Error(
`Table '${tableName}' can't use a non-unique index without a primary index`,
);
}

return [
`${tableName}${accessKey.suffix}`,
accessKey.value,
primaryKey.value,
];
}

throw new Error("Invalid access key");
});
}

export async function whereToKeys<
Expand Down
18 changes: 15 additions & 3 deletions src/pentagon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ async function deleteImpl<T extends TableDefinition>(
tableDefinition: T,
queryArgs: Parameters<PentagonMethods<T>["delete"]>[0],
) {
const keys = schemaToKeys(tableDefinition.schema, queryArgs.where ?? []);
const keys = schemaToKeys(
tableName,
tableDefinition.schema,
queryArgs.where ?? [],
);
const indexKeys = keysToIndexes(tableName, keys);
const foundItems = await whereToKeys(
kv,
Expand All @@ -105,7 +109,11 @@ async function deleteManyImpl<T extends TableDefinition>(
tableDefinition: T,
queryArgs: Parameters<PentagonMethods<T>["deleteMany"]>[0],
) {
const keys = schemaToKeys(tableDefinition.schema, queryArgs.where ?? []);
const keys = schemaToKeys(
tableName,
tableDefinition.schema,
queryArgs.where ?? [],
);
const indexKeys = keysToIndexes(tableName, keys);
const foundItems = await whereToKeys(
kv,
Expand All @@ -123,7 +131,11 @@ async function updateManyImpl<T extends TableDefinition>(
tableDefinition: T,
updateArgs: Parameters<PentagonMethods<T>["updateMany"]>[0],
): ReturnType<PentagonMethods<T>["updateMany"]> {
const keys = schemaToKeys(tableDefinition.schema, updateArgs.where ?? []);
const keys = schemaToKeys(
tableName,
tableDefinition.schema,
updateArgs.where ?? [],
);
const indexKeys = keysToIndexes(tableName, keys);
const foundItems = await whereToKeys(
kv,
Expand Down
15 changes: 9 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,15 @@ export type QueryArgs<T extends TableDefinition> = {
kvOptions?: QueryKvOptions;
};

export type AccessKey = {
primary?: true;
suffix?: string; // eg. "_by_email"
unique?: true;
value: Deno.KvKeyPart;
};
export type AccessKey =
& {
value: Deno.KvKeyPart;
}
& (
| { type: "primary" }
| { type: "index"; suffix: string }
| { type: "unique"; suffix: string }
);

export type KeyProperty = z.infer<typeof KeyPropertySchema>;

Expand Down
Loading

0 comments on commit b5f59e1

Please sign in to comment.