Skip to content

Commit

Permalink
ucast-prisma: support translation table and col names
Browse files Browse the repository at this point in the history
  • Loading branch information
srenatus committed Nov 7, 2024
1 parent 77a399e commit 7be2211
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 8 deletions.
27 changes: 27 additions & 0 deletions .changeset/hip-files-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
"@styra/ucast-prisma": patch
---

support translating table and column names via extra options

An extra options object can be passed to `ucastToPrisma` to translate table and column names.
This is useful when the Prisma schema uses different names than the OPA policy used to generate
the conditions.

```typescript
const p = ucastToPrisma(
{ or: [{ "tickets.resolved": false }, { "users.name": "ceasar" }] },
"tickets0",
{
translations: {
tickets: { $self: "tickets0", resolved: "resolved0" },
users: { $self: "users0", name: "name0" },
},
}
);
```

In this example, the conditions `{ or: [{ "tickets.resolved": false }, { "users.name": "ceasar" }] }`
will be rewritten to `{ OR: [{ tickets0: { resolved0: false } }, { users0: { name0: "ceasar" } }] }`,
assuming that the Prisma schema uses `tickets0` and `users0` as table names and `resolved0` and `name0`
as column names respectively.
16 changes: 14 additions & 2 deletions packages/ucast-prisma/src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@ import { createPrismaInterpreter } from "./interpreter.js";
import * as instructions from "./instructions.js";
import * as interpreters from "./interpreters.js";

export type Options = {
translations?: Record<string, Record<string, string>>;
};

export function ucastToPrisma(
ucast: Record<string, any>,
primary: string
primary: string,
{ translations }: Options = {}
): Record<string, any> {
const parsed = new ObjectQueryParser(instructions).parse(ucast);
return createPrismaInterpreter(primary, interpreters)(parsed);
return createPrismaInterpreter(primary, {
interpreters,
translate: (tbl: string, col: string): [string, string] => {
const tbl0 = translations?.[tbl]?.$self || tbl;
const col0 = translations?.[tbl]?.[col] || col;
return [tbl0, col0];
},
})(parsed);
}
17 changes: 15 additions & 2 deletions packages/ucast-prisma/src/interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,24 @@ export type PrismaOperator<C extends Condition> = (
context: InterpretationContext<PrismaOperator<C>>
) => Query;

export type interpreterOpts = {
interpreters: Record<string, PrismaOperator<any>>; // TODO(sr): this <any> doesn't feel right.
} & rawOpts;

type rawOpts = {
translate?: (tbl: string, col: string) => [string, string];
};

export function createPrismaInterpreter(
primary: string,
operators: Record<string, PrismaOperator<any>> // TODO(sr): this <any> doesn't feel right.
{ interpreters, translate }: interpreterOpts
) {
const interpret = createInterpreter<PrismaOperator<any>>(operators);
const interpret = createInterpreter<PrismaOperator<any>, rawOpts>(
interpreters,
{
translate,
}
);
return (condition: Condition) =>
interpret(condition, new Query(primary)).toJSON();
}
7 changes: 5 additions & 2 deletions packages/ucast-prisma/src/interpreters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
FieldCondition,
Condition,
Comparable,
InterpretationContext,
} from "@ucast/core";
import { PrismaOperator } from "./interpreter.js";

Expand Down Expand Up @@ -53,8 +54,10 @@ export const or: PrismaOperator<CompoundCondition> = (
};

function op<T>(name: string): PrismaOperator<FieldCondition<T>> {
return (condition, query) => {
const [tbl, field] = condition.field.split(".");
return (condition, query, options) => {
const translate =
(options as any)?.translate || ((...x: string[]) => [x[0], x[1]]);
const [tbl, field] = translate(...condition.field.split("."));
return query.addCondition(tbl, { [field]: { [name]: condition.value } });
};
}
55 changes: 55 additions & 0 deletions packages/ucast-prisma/tests/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,59 @@ describe("ucastToPrisma", () => {
});
});
});
describe("translations", () => {
describe("field operators", () => {
it("converts column names", () => {
const p = ucastToPrisma({ "table.name": "test" }, "table", {
translations: { table: { name: "name_col" } },
});
expect(p).toStrictEqual({ name_col: { equals: "test" } });
});

it("converts table names", () => {
const p = ucastToPrisma({ "table.name": "test" }, "tbl", {
translations: { table: { $self: "tbl" } },
});
expect(p).toStrictEqual({ name: { equals: "test" } });
});

it("converts multiple table+col names", () => {
const p = ucastToPrisma(
{ "table.name": "test", "user.name": "alice" },
"tbl",
{
translations: {
table: { $self: "tbl", name: "name_col" },
user: { $self: "usr", name: "name_col_0" },
},
}
);
expect(p).toStrictEqual({
name_col: { equals: "test" },
usr: { name_col_0: { equals: "alice" } },
});
});
});

describe("compound operators", () => {
it("supports translations for 'or'", () => {
const p = ucastToPrisma(
{ or: [{ "tickets.resolved": false }, { "users.name": "ceasar" }] },
"tickets0",
{
translations: {
tickets: { $self: "tickets0", resolved: "resolved0" },
users: { $self: "users0", name: "name0" },
},
}
);
expect(p).toStrictEqual({
OR: [
{ resolved0: { equals: false } },
{ users0: { name0: { equals: "ceasar" } } },
],
});
});
});
});
});
4 changes: 2 additions & 2 deletions packages/ucast-prisma/tests/interpreters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { describe, it, expect } from "vitest";

describe("Condition interpreter", () => {
describe("field operators", () => {
const interpret = createPrismaInterpreter("table", interpreters);
const interpret = createPrismaInterpreter("table", { interpreters });

it('generates query with `equals operator for "eq"', () => {
const condition = new FieldCondition("eq", "table.name", "test");
Expand Down Expand Up @@ -39,7 +39,7 @@ describe("Condition interpreter", () => {
});

describe("compound operators", () => {
const interpret = createPrismaInterpreter("user", interpreters);
const interpret = createPrismaInterpreter("user", { interpreters });

it('generates query without extra fluff for "AND"', () => {
const condition = new CompoundCondition("and", [
Expand Down

0 comments on commit 7be2211

Please sign in to comment.