mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
reworked WithBuilder to allow recursive with operations
This commit is contained in:
@@ -106,7 +106,6 @@ describe("Relations", async () => {
|
|||||||
expect(postAuthorRel?.other(posts).entity).toBe(users);
|
expect(postAuthorRel?.other(posts).entity).toBe(users);
|
||||||
|
|
||||||
const kysely = em.connection.kysely;
|
const kysely = em.connection.kysely;
|
||||||
const jsonFrom = (e) => e;
|
|
||||||
/**
|
/**
|
||||||
* Relation Helper
|
* Relation Helper
|
||||||
*/
|
*/
|
||||||
@@ -123,7 +122,7 @@ describe("Relations", async () => {
|
|||||||
.selectFrom(users.name)
|
.selectFrom(users.name)
|
||||||
.select((eb) => postAuthorRel.buildWith(users, "posts")(eb).as("posts"));
|
.select((eb) => postAuthorRel.buildWith(users, "posts")(eb).as("posts"));
|
||||||
expect(selectPostsFromUsers.compile().sql).toBe(
|
expect(selectPostsFromUsers.compile().sql).toBe(
|
||||||
'select (select "posts"."id" as "id", "posts"."title" as "title", "posts"."author_id" as "author_id" from "posts" as "posts" where "posts"."author_id" = "users"."id" limit ?) as "posts" from "users"'
|
'select (select from "posts" as "posts" where "posts"."author_id" = "users"."id") as "posts" from "users"'
|
||||||
);
|
);
|
||||||
expect(postAuthorRel!.getField()).toBeInstanceOf(RelationField);
|
expect(postAuthorRel!.getField()).toBeInstanceOf(RelationField);
|
||||||
const userObj = { id: 1, username: "test" };
|
const userObj = { id: 1, username: "test" };
|
||||||
@@ -143,7 +142,7 @@ describe("Relations", async () => {
|
|||||||
.select((eb) => postAuthorRel.buildWith(posts, "author")(eb).as("author"));
|
.select((eb) => postAuthorRel.buildWith(posts, "author")(eb).as("author"));
|
||||||
|
|
||||||
expect(selectUsersFromPosts.compile().sql).toBe(
|
expect(selectUsersFromPosts.compile().sql).toBe(
|
||||||
'select (select "author"."id" as "id", "author"."username" as "username" from "users" as "author" where "author"."id" = "posts"."author_id" limit ?) as "author" from "posts"'
|
'select (select from "users" as "author" where "author"."id" = "posts"."author_id" limit ?) as "author" from "posts"'
|
||||||
);
|
);
|
||||||
expect(postAuthorRel.getField()).toBeInstanceOf(RelationField);
|
expect(postAuthorRel.getField()).toBeInstanceOf(RelationField);
|
||||||
const postObj = { id: 1, title: "test" };
|
const postObj = { id: 1, title: "test" };
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { afterAll, describe, expect, test } from "bun:test";
|
import { afterAll, describe, expect, test } from "bun:test";
|
||||||
|
import { _jsonp } from "../../../src/core/utils";
|
||||||
import {
|
import {
|
||||||
Entity,
|
Entity,
|
||||||
EntityManager,
|
EntityManager,
|
||||||
@@ -9,12 +10,13 @@ import {
|
|||||||
WithBuilder
|
WithBuilder
|
||||||
} from "../../../src/data";
|
} from "../../../src/data";
|
||||||
import * as proto from "../../../src/data/prototype";
|
import * as proto from "../../../src/data/prototype";
|
||||||
|
import { compileQb, prettyPrintQb } from "../../helper";
|
||||||
import { getDummyConnection } from "../helper";
|
import { getDummyConnection } from "../helper";
|
||||||
|
|
||||||
const { dummyConnection, afterAllCleanup } = getDummyConnection();
|
const { dummyConnection } = getDummyConnection();
|
||||||
afterAll(afterAllCleanup);
|
|
||||||
|
|
||||||
function schemaToEm(s: ReturnType<(typeof proto)["em"]>): EntityManager<any> {
|
function schemaToEm(s: ReturnType<(typeof proto)["em"]>): EntityManager<any> {
|
||||||
|
const { dummyConnection } = getDummyConnection();
|
||||||
return new EntityManager(Object.values(s.entities), dummyConnection, s.relations, s.indices);
|
return new EntityManager(Object.values(s.entities), dummyConnection, s.relations, s.indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,9 +79,9 @@ describe("[data] WithBuilder", async () => {
|
|||||||
const res = qb.compile();
|
const res = qb.compile();
|
||||||
|
|
||||||
expect(res.sql).toBe(
|
expect(res.sql).toBe(
|
||||||
'select (select coalesce(json_group_array(json_object(\'id\', "agg"."id", \'content\', "agg"."content", \'author_id\', "agg"."author_id")), \'[]\') from (select "posts"."id" as "id", "posts"."content" as "content", "posts"."author_id" as "author_id" from "posts" as "posts" where "posts"."author_id" = "users"."id" limit ?) as agg) as "posts" from "users"'
|
'select (select coalesce(json_group_array(json_object(\'id\', "agg"."id", \'content\', "agg"."content", \'author_id\', "agg"."author_id")), \'[]\') from (select "posts"."id" as "id", "posts"."content" as "content", "posts"."author_id" as "author_id" from "posts" as "posts" where "posts"."author_id" = "users"."id" order by "posts"."id" asc limit ? offset ?) as agg) as "posts" from "users"'
|
||||||
);
|
);
|
||||||
expect(res.parameters).toEqual([5]);
|
expect(res.parameters).toEqual([10, 0]);
|
||||||
|
|
||||||
const qb2 = WithBuilder.addClause(
|
const qb2 = WithBuilder.addClause(
|
||||||
em,
|
em,
|
||||||
@@ -93,9 +95,9 @@ describe("[data] WithBuilder", async () => {
|
|||||||
const res2 = qb2.compile();
|
const res2 = qb2.compile();
|
||||||
|
|
||||||
expect(res2.sql).toBe(
|
expect(res2.sql).toBe(
|
||||||
'select (select json_object(\'id\', "obj"."id", \'username\', "obj"."username") from (select "author"."id" as "id", "author"."username" as "username" from "users" as "author" where "author"."id" = "posts"."author_id" limit ?) as obj) as "author" from "posts"'
|
'select (select json_object(\'id\', "obj"."id", \'username\', "obj"."username") from (select "users"."id" as "id", "users"."username" as "username" from "users" as "author" where "author"."id" = "posts"."author_id" order by "users"."id" asc limit ? offset ?) as obj) as "author" from "posts"'
|
||||||
);
|
);
|
||||||
expect(res2.parameters).toEqual([1]);
|
expect(res2.parameters).toEqual([1, 0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("test with empty join", async () => {
|
test("test with empty join", async () => {
|
||||||
@@ -165,8 +167,8 @@ describe("[data] WithBuilder", async () => {
|
|||||||
id: 2,
|
id: 2,
|
||||||
label: "beauty",
|
label: "beauty",
|
||||||
posts: [
|
posts: [
|
||||||
{ id: 2, title: "beauty post" },
|
{ id: 1, title: "fashion post" },
|
||||||
{ id: 1, title: "fashion post" }
|
{ id: 2, title: "beauty post" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -198,9 +200,9 @@ describe("[data] WithBuilder", async () => {
|
|||||||
);
|
);
|
||||||
const res = qb.compile();
|
const res = qb.compile();
|
||||||
expect(res.sql).toBe(
|
expect(res.sql).toBe(
|
||||||
'select (select json_object(\'id\', "obj"."id", \'path\', "obj"."path") from (select "media"."id" as "id", "media"."path" as "path" from "media" where "media"."reference" = ? and "categories"."id" = "media"."entity_id" limit ?) as obj) as "single" from "categories"'
|
'select (select json_object(\'id\', "obj"."id", \'path\', "obj"."path") from (select "media"."id" as "id", "media"."path" as "path" from "media" where "media"."reference" = ? and "categories"."id" = "media"."entity_id" order by "media"."id" asc limit ? offset ?) as obj) as "single" from "categories"'
|
||||||
);
|
);
|
||||||
expect(res.parameters).toEqual(["categories.single", 1]);
|
expect(res.parameters).toEqual(["categories.single", 1, 0]);
|
||||||
|
|
||||||
const qb2 = WithBuilder.addClause(
|
const qb2 = WithBuilder.addClause(
|
||||||
em,
|
em,
|
||||||
@@ -210,9 +212,9 @@ describe("[data] WithBuilder", async () => {
|
|||||||
);
|
);
|
||||||
const res2 = qb2.compile();
|
const res2 = qb2.compile();
|
||||||
expect(res2.sql).toBe(
|
expect(res2.sql).toBe(
|
||||||
'select (select coalesce(json_group_array(json_object(\'id\', "agg"."id", \'path\', "agg"."path")), \'[]\') from (select "media"."id" as "id", "media"."path" as "path" from "media" where "media"."reference" = ? and "categories"."id" = "media"."entity_id" limit ?) as agg) as "multiple" from "categories"'
|
'select (select coalesce(json_group_array(json_object(\'id\', "agg"."id", \'path\', "agg"."path")), \'[]\') from (select "media"."id" as "id", "media"."path" as "path" from "media" where "media"."reference" = ? and "categories"."id" = "media"."entity_id" order by "media"."id" asc limit ? offset ?) as agg) as "multiple" from "categories"'
|
||||||
);
|
);
|
||||||
expect(res2.parameters).toEqual(["categories.multiple", 5]);
|
expect(res2.parameters).toEqual(["categories.multiple", 10, 0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
/*test("test manytoone", async () => {
|
/*test("test manytoone", async () => {
|
||||||
@@ -236,4 +238,205 @@ describe("[data] WithBuilder", async () => {
|
|||||||
const res = await em.repository().findMany("posts", { join: ["author"] });
|
const res = await em.repository().findMany("posts", { join: ["author"] });
|
||||||
console.log(res.sql, res.parameters, res.result);
|
console.log(res.sql, res.parameters, res.result);
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
|
describe("recursive", () => {
|
||||||
|
test("compiles with singles", async () => {
|
||||||
|
const schema = proto.em(
|
||||||
|
{
|
||||||
|
posts: proto.entity("posts", {}),
|
||||||
|
users: proto.entity("users", {
|
||||||
|
username: proto.text()
|
||||||
|
}),
|
||||||
|
media: proto.entity("media", {
|
||||||
|
path: proto.text()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
({ relation }, { posts, users, media }) => {
|
||||||
|
relation(posts).manyToOne(users);
|
||||||
|
relation(users).polyToOne(media, { mappedBy: "avatar" });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const em = schemaToEm(schema);
|
||||||
|
|
||||||
|
const qb = WithBuilder.addClause(
|
||||||
|
em,
|
||||||
|
em.connection.kysely.selectFrom("posts"),
|
||||||
|
schema.entities.posts,
|
||||||
|
{
|
||||||
|
users: {
|
||||||
|
limit: 5, // ignored
|
||||||
|
select: ["id", "username"],
|
||||||
|
sort: { by: "username", dir: "asc" },
|
||||||
|
with: {
|
||||||
|
avatar: {
|
||||||
|
select: ["id", "path"],
|
||||||
|
limit: 2 // ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
//prettyPrintQb(qb);
|
||||||
|
expect(qb.compile().sql).toBe(
|
||||||
|
'select (select json_object(\'id\', "obj"."id", \'username\', "obj"."username", \'avatar\', "obj"."avatar") from (select "users"."id" as "id", "users"."username" as "username", (select json_object(\'id\', "obj"."id", \'path\', "obj"."path") from (select "media"."id" as "id", "media"."path" as "path" from "media" where "media"."reference" = ? and "users"."id" = "media"."entity_id" order by "media"."id" asc limit ? offset ?) as obj) as "avatar" from "users" as "users" where "users"."id" = "posts"."users_id" order by "users"."username" asc limit ? offset ?) as obj) as "users" from "posts"'
|
||||||
|
);
|
||||||
|
expect(qb.compile().parameters).toEqual(["users.avatar", 1, 0, 1, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("compiles with many", async () => {
|
||||||
|
const schema = proto.em(
|
||||||
|
{
|
||||||
|
posts: proto.entity("posts", {}),
|
||||||
|
comments: proto.entity("comments", {}),
|
||||||
|
users: proto.entity("users", {
|
||||||
|
username: proto.text()
|
||||||
|
}),
|
||||||
|
media: proto.entity("media", {
|
||||||
|
path: proto.text()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
({ relation }, { posts, comments, users, media }) => {
|
||||||
|
relation(posts).manyToOne(users).polyToOne(media, { mappedBy: "images" });
|
||||||
|
relation(users).polyToOne(media, { mappedBy: "avatar" });
|
||||||
|
relation(comments).manyToOne(posts).manyToOne(users);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const em = schemaToEm(schema);
|
||||||
|
|
||||||
|
const qb = WithBuilder.addClause(
|
||||||
|
em,
|
||||||
|
em.connection.kysely.selectFrom("posts"),
|
||||||
|
schema.entities.posts,
|
||||||
|
{
|
||||||
|
comments: {
|
||||||
|
limit: 12,
|
||||||
|
with: {
|
||||||
|
users: {
|
||||||
|
select: ["username"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(qb.compile().sql).toBe(
|
||||||
|
'select (select coalesce(json_group_array(json_object(\'id\', "agg"."id", \'posts_id\', "agg"."posts_id", \'users_id\', "agg"."users_id", \'users\', "agg"."users")), \'[]\') from (select "comments"."id" as "id", "comments"."posts_id" as "posts_id", "comments"."users_id" as "users_id", (select json_object(\'username\', "obj"."username") from (select "users"."username" as "username" from "users" as "users" where "users"."id" = "comments"."users_id" order by "users"."id" asc limit ? offset ?) as obj) as "users" from "comments" as "comments" where "comments"."posts_id" = "posts"."id" order by "comments"."id" asc limit ? offset ?) as agg) as "comments" from "posts"'
|
||||||
|
);
|
||||||
|
expect(qb.compile().parameters).toEqual([1, 0, 12, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns correct result", async () => {
|
||||||
|
const schema = proto.em(
|
||||||
|
{
|
||||||
|
posts: proto.entity("posts", {
|
||||||
|
title: proto.text()
|
||||||
|
}),
|
||||||
|
comments: proto.entity("comments", {
|
||||||
|
content: proto.text()
|
||||||
|
}),
|
||||||
|
users: proto.entity("users", {
|
||||||
|
username: proto.text()
|
||||||
|
}),
|
||||||
|
media: proto.entity("media", {
|
||||||
|
path: proto.text()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
({ relation }, { posts, comments, users, media }) => {
|
||||||
|
relation(posts).manyToOne(users).polyToOne(media, { mappedBy: "images" });
|
||||||
|
relation(users).polyToOne(media, { mappedBy: "avatar" });
|
||||||
|
relation(comments).manyToOne(posts).manyToOne(users);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const em = schemaToEm(schema);
|
||||||
|
await em.schema().sync({ force: true });
|
||||||
|
|
||||||
|
// add data
|
||||||
|
await em.mutator("users").insertMany([{ username: "user1" }, { username: "user2" }]);
|
||||||
|
await em.mutator("posts").insertMany([
|
||||||
|
{ title: "post1", users_id: 1 },
|
||||||
|
{ title: "post2", users_id: 1 },
|
||||||
|
{ title: "post3", users_id: 2 }
|
||||||
|
]);
|
||||||
|
await em.mutator("comments").insertMany([
|
||||||
|
{ content: "comment1", posts_id: 1, users_id: 1 },
|
||||||
|
{ content: "comment1-1", posts_id: 1, users_id: 1 },
|
||||||
|
{ content: "comment2", posts_id: 1, users_id: 2 },
|
||||||
|
{ content: "comment3", posts_id: 2, users_id: 1 },
|
||||||
|
{ content: "comment4", posts_id: 2, users_id: 2 },
|
||||||
|
{ content: "comment5", posts_id: 3, users_id: 1 },
|
||||||
|
{ content: "comment6", posts_id: 3, users_id: 2 }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const result = await em.repo("posts").findMany({
|
||||||
|
select: ["title"],
|
||||||
|
with: {
|
||||||
|
comments: {
|
||||||
|
limit: 2,
|
||||||
|
select: ["content"],
|
||||||
|
with: {
|
||||||
|
users: {
|
||||||
|
select: ["username"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.data).toEqual([
|
||||||
|
{
|
||||||
|
title: "post1",
|
||||||
|
comments: [
|
||||||
|
{
|
||||||
|
content: "comment1",
|
||||||
|
users: {
|
||||||
|
username: "user1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "comment1-1",
|
||||||
|
users: {
|
||||||
|
username: "user1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "post2",
|
||||||
|
comments: [
|
||||||
|
{
|
||||||
|
content: "comment3",
|
||||||
|
users: {
|
||||||
|
username: "user1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "comment4",
|
||||||
|
users: {
|
||||||
|
username: "user2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "post3",
|
||||||
|
comments: [
|
||||||
|
{
|
||||||
|
content: "comment5",
|
||||||
|
users: {
|
||||||
|
username: "user1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "comment6",
|
||||||
|
users: {
|
||||||
|
username: "user2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
//console.log(_jsonp(result.data));
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { unlink } from "node:fs/promises";
|
import { unlink } from "node:fs/promises";
|
||||||
import type { SqliteDatabase } from "kysely";
|
import type { SelectQueryBuilder, SqliteDatabase } from "kysely";
|
||||||
import Database from "libsql";
|
import Database from "libsql";
|
||||||
|
import { format as sqlFormat } from "sql-formatter";
|
||||||
import { SqliteLocalConnection } from "../src/data";
|
import { SqliteLocalConnection } from "../src/data";
|
||||||
|
|
||||||
export function getDummyDatabase(memory: boolean = true): {
|
export function getDummyDatabase(memory: boolean = true): {
|
||||||
@@ -51,3 +52,13 @@ export function enableConsoleLog() {
|
|||||||
console[severity as ConsoleSeverity] = fn;
|
console[severity as ConsoleSeverity] = fn;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function compileQb(qb: SelectQueryBuilder<any, any, any>) {
|
||||||
|
const { sql, parameters } = qb.compile();
|
||||||
|
return { sql, parameters };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prettyPrintQb(qb: SelectQueryBuilder<any, any, any>) {
|
||||||
|
const { sql, parameters } = qb.compile();
|
||||||
|
console.log("$", sqlFormat(sql), "\n[params]", parameters);
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@
|
|||||||
"react-hook-form": "^7.53.1",
|
"react-hook-form": "^7.53.1",
|
||||||
"react-icons": "5.2.1",
|
"react-icons": "5.2.1",
|
||||||
"react-json-view-lite": "^2.0.1",
|
"react-json-view-lite": "^2.0.1",
|
||||||
|
"sql-formatter": "^15.4.9",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.5.4",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.14",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { DatabaseIntrospector, SqliteDatabase } from "kysely";
|
import { type DatabaseIntrospector, ParseJSONResultsPlugin, type SqliteDatabase } from "kysely";
|
||||||
import { Kysely, SqliteDialect } from "kysely";
|
import { Kysely, SqliteDialect } from "kysely";
|
||||||
import { DeserializeJsonValuesPlugin } from "../plugins/DeserializeJsonValuesPlugin";
|
|
||||||
import { SqliteConnection } from "./SqliteConnection";
|
import { SqliteConnection } from "./SqliteConnection";
|
||||||
import { SqliteIntrospector } from "./SqliteIntrospector";
|
import { SqliteIntrospector } from "./SqliteIntrospector";
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ class CustomSqliteDialect extends SqliteDialect {
|
|||||||
|
|
||||||
export class SqliteLocalConnection extends SqliteConnection {
|
export class SqliteLocalConnection extends SqliteConnection {
|
||||||
constructor(private database: SqliteDatabase) {
|
constructor(private database: SqliteDatabase) {
|
||||||
const plugins = [new DeserializeJsonValuesPlugin()];
|
const plugins = [new ParseJSONResultsPlugin()];
|
||||||
const kysely = new Kysely({
|
const kysely = new Kysely({
|
||||||
dialect: new CustomSqliteDialect({ database }),
|
dialect: new CustomSqliteDialect({ database }),
|
||||||
plugins
|
plugins
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
|
|||||||
return this.em.connection.kysely;
|
return this.em.connection.kysely;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getValidOptions(options?: Partial<RepoQuery>): RepoQuery {
|
getValidOptions(options?: Partial<RepoQuery>): RepoQuery {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
// @todo: if not cloned deep, it will keep references and error if multiple requests come in
|
// @todo: if not cloned deep, it will keep references and error if multiple requests come in
|
||||||
const validated = {
|
const validated = {
|
||||||
@@ -228,43 +228,79 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
|
|||||||
return { ...response, data: data[0]! };
|
return { ...response, data: data[0]! };
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildQuery(
|
addOptionsToQueryBuilder(
|
||||||
|
_qb?: RepositoryQB,
|
||||||
_options?: Partial<RepoQuery>,
|
_options?: Partial<RepoQuery>,
|
||||||
exclude_options: (keyof RepoQuery)[] = []
|
config?: {
|
||||||
): { qb: RepositoryQB; options: RepoQuery } {
|
validate?: boolean;
|
||||||
|
ignore?: (keyof RepoQuery)[];
|
||||||
|
alias?: string;
|
||||||
|
defaults?: Pick<RepoQuery, "limit" | "offset">;
|
||||||
|
}
|
||||||
|
) {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
const options = this.getValidOptions(_options);
|
let qb = _qb ?? (this.conn.selectFrom(entity.name) as RepositoryQB);
|
||||||
|
|
||||||
const alias = entity.name;
|
const options = config?.validate !== false ? this.getValidOptions(_options) : _options;
|
||||||
|
if (!options) return qb;
|
||||||
|
|
||||||
|
const alias = config?.alias ?? entity.name;
|
||||||
const aliased = (field: string) => `${alias}.${field}`;
|
const aliased = (field: string) => `${alias}.${field}`;
|
||||||
let qb = this.conn
|
const ignore = config?.ignore ?? [];
|
||||||
.selectFrom(entity.name)
|
const defaults = {
|
||||||
.select(entity.getAliasedSelectFrom(options.select, alias));
|
limit: 10,
|
||||||
|
offset: 0,
|
||||||
|
...config?.defaults
|
||||||
|
};
|
||||||
|
|
||||||
//console.log("build query options", options);
|
/*console.log("build query options", {
|
||||||
if (!exclude_options.includes("with") && options.with) {
|
entity: entity.name,
|
||||||
|
options,
|
||||||
|
config
|
||||||
|
});*/
|
||||||
|
|
||||||
|
if (!ignore.includes("select") && options.select) {
|
||||||
|
qb = qb.select(entity.getAliasedSelectFrom(options.select, alias));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignore.includes("with") && options.with) {
|
||||||
qb = WithBuilder.addClause(this.em, qb, entity, options.with);
|
qb = WithBuilder.addClause(this.em, qb, entity, options.with);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exclude_options.includes("join") && options.join) {
|
if (!ignore.includes("join") && options.join) {
|
||||||
qb = JoinBuilder.addClause(this.em, qb, entity, options.join);
|
qb = JoinBuilder.addClause(this.em, qb, entity, options.join);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add where if present
|
// add where if present
|
||||||
if (!exclude_options.includes("where") && options.where) {
|
if (!ignore.includes("where") && options.where) {
|
||||||
qb = WhereBuilder.addClause(qb, options.where);
|
qb = WhereBuilder.addClause(qb, options.where);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exclude_options.includes("limit")) qb = qb.limit(options.limit);
|
if (!ignore.includes("limit")) qb = qb.limit(options.limit ?? defaults.limit);
|
||||||
if (!exclude_options.includes("offset")) qb = qb.offset(options.offset);
|
if (!ignore.includes("offset")) qb = qb.offset(options.offset ?? defaults.offset);
|
||||||
|
|
||||||
// sorting
|
// sorting
|
||||||
if (!exclude_options.includes("sort")) {
|
if (!ignore.includes("sort")) {
|
||||||
qb = qb.orderBy(aliased(options.sort.by), options.sort.dir);
|
qb = qb.orderBy(aliased(options.sort?.by ?? "id"), options.sort?.dir ?? "asc");
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log("options", { _options, options, exclude_options });
|
return qb as RepositoryQB;
|
||||||
return { qb, options };
|
}
|
||||||
|
|
||||||
|
private buildQuery(
|
||||||
|
_options?: Partial<RepoQuery>,
|
||||||
|
ignore: (keyof RepoQuery)[] = []
|
||||||
|
): { qb: RepositoryQB; options: RepoQuery } {
|
||||||
|
const entity = this.entity;
|
||||||
|
const options = this.getValidOptions(_options);
|
||||||
|
|
||||||
|
return {
|
||||||
|
qb: this.addOptionsToQueryBuilder(undefined, options, {
|
||||||
|
ignore,
|
||||||
|
alias: entity.name
|
||||||
|
}),
|
||||||
|
options
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async findId(
|
async findId(
|
||||||
|
|||||||
@@ -1,38 +1,9 @@
|
|||||||
import { isObject } from "core/utils";
|
import { isObject } from "core/utils";
|
||||||
import type { KyselyJsonFrom, RepoQuery } from "data";
|
import type { KyselyJsonFrom, RepoQuery } from "data";
|
||||||
import { InvalidSearchParamsException } from "data/errors";
|
import { InvalidSearchParamsException } from "data/errors";
|
||||||
import type { RepoWithSchema } from "data/server/data-query-impl";
|
|
||||||
import type { Entity, EntityManager, RepositoryQB } from "../../entities";
|
import type { Entity, EntityManager, RepositoryQB } from "../../entities";
|
||||||
|
|
||||||
export class WithBuilder {
|
export class WithBuilder {
|
||||||
/*private static buildClause(
|
|
||||||
em: EntityManager<any>,
|
|
||||||
qb: RepositoryQB,
|
|
||||||
entity: Entity,
|
|
||||||
ref: string,
|
|
||||||
config?: RepoQuery
|
|
||||||
) {
|
|
||||||
const relation = em.relationOf(entity.name, withString);
|
|
||||||
if (!relation) {
|
|
||||||
throw new Error(`Relation "${withString}" not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cardinality = relation.ref(withString).cardinality;
|
|
||||||
//console.log("with--builder", { entity: entity.name, withString, cardinality });
|
|
||||||
|
|
||||||
const jsonFrom = cardinality === 1 ? fns.jsonObjectFrom : fns.jsonArrayFrom;
|
|
||||||
|
|
||||||
if (!jsonFrom) {
|
|
||||||
throw new Error("Connection does not support jsonObjectFrom/jsonArrayFrom");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return relation.buildWith(entity, qb, jsonFrom, withString);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error(`Could not build "with" relation "${withString}": ${(e as any).message}`);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
static addClause(
|
static addClause(
|
||||||
em: EntityManager<any>,
|
em: EntityManager<any>,
|
||||||
qb: RepositoryQB,
|
qb: RepositoryQB,
|
||||||
@@ -59,11 +30,23 @@ export class WithBuilder {
|
|||||||
throw new Error("Connection does not support jsonObjectFrom/jsonArrayFrom");
|
throw new Error("Connection does not support jsonObjectFrom/jsonArrayFrom");
|
||||||
}
|
}
|
||||||
|
|
||||||
const alias = relation.other(entity).reference;
|
const other = relation.other(entity);
|
||||||
newQb = newQb.select((eb) => {
|
newQb = newQb.select((eb) => {
|
||||||
return jsonFrom(relation.buildWith(entity, ref)(eb)).as(alias);
|
let subQuery = relation.buildWith(entity, ref)(eb);
|
||||||
|
if (query) {
|
||||||
|
subQuery = em.repo(other.entity).addOptionsToQueryBuilder(subQuery, query as any, {
|
||||||
|
ignore: ["with", "join", cardinality === 1 ? "limit" : undefined].filter(
|
||||||
|
Boolean
|
||||||
|
) as any
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.with) {
|
||||||
|
subQuery = WithBuilder.addClause(em, subQuery, other.entity, query.with as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonFrom(subQuery).as(other.reference);
|
||||||
});
|
});
|
||||||
//newQb = relation.buildWith(entity, qb, jsonFrom, ref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newQb;
|
return newQb;
|
||||||
@@ -72,7 +55,7 @@ export class WithBuilder {
|
|||||||
static validateWiths(em: EntityManager<any>, entity: string, withs: RepoQuery["with"]) {
|
static validateWiths(em: EntityManager<any>, entity: string, withs: RepoQuery["with"]) {
|
||||||
let depth = 0;
|
let depth = 0;
|
||||||
if (!withs || !isObject(withs)) {
|
if (!withs || !isObject(withs)) {
|
||||||
console.warn(`'withs' undefined or invalid, given: ${JSON.stringify(withs)}`);
|
withs && console.warn(`'withs' invalid, given: ${JSON.stringify(withs)}`);
|
||||||
return depth;
|
return depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,28 +158,12 @@ export class ManyToOneRelation extends EntityRelation<typeof ManyToOneRelation.s
|
|||||||
|
|
||||||
buildWith(entity: Entity, reference: string) {
|
buildWith(entity: Entity, reference: string) {
|
||||||
const { self, entityRef, otherRef, relationRef } = this.queryInfo(entity, reference);
|
const { self, entityRef, otherRef, relationRef } = this.queryInfo(entity, reference);
|
||||||
const limit =
|
|
||||||
self.cardinality === 1
|
|
||||||
? 1
|
|
||||||
: (this.config.with_limit ?? ManyToOneRelation.DEFAULTS.with_limit);
|
|
||||||
//console.log("buildWith", entity.name, reference, { limit });
|
|
||||||
|
|
||||||
return (eb: ExpressionBuilder<any, any>) =>
|
return (eb: ExpressionBuilder<any, any>) =>
|
||||||
eb
|
eb
|
||||||
.selectFrom(`${self.entity.name} as ${relationRef}`)
|
.selectFrom(`${self.entity.name} as ${relationRef}`)
|
||||||
.select(self.entity.getSelect(relationRef))
|
|
||||||
.whereRef(entityRef, "=", otherRef)
|
.whereRef(entityRef, "=", otherRef)
|
||||||
.limit(limit);
|
.$if(self.cardinality === 1, (qb) => qb.limit(1));
|
||||||
|
|
||||||
/*return qb.select((eb) =>
|
|
||||||
jsonFrom(
|
|
||||||
eb
|
|
||||||
.selectFrom(`${self.entity.name} as ${relationRef}`)
|
|
||||||
.select(self.entity.getSelect(relationRef))
|
|
||||||
.whereRef(entityRef, "=", otherRef)
|
|
||||||
.limit(limit)
|
|
||||||
).as(relationRef)
|
|
||||||
);*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -90,26 +90,13 @@ export class PolymorphicRelation extends EntityRelation<typeof PolymorphicRelati
|
|||||||
|
|
||||||
buildWith(entity: Entity) {
|
buildWith(entity: Entity) {
|
||||||
const { other, whereLhs, reference, entityRef, otherRef } = this.queryInfo(entity);
|
const { other, whereLhs, reference, entityRef, otherRef } = this.queryInfo(entity);
|
||||||
const limit = other.cardinality === 1 ? 1 : 5;
|
|
||||||
|
|
||||||
return (eb: ExpressionBuilder<any, any>) =>
|
return (eb: ExpressionBuilder<any, any>) =>
|
||||||
eb
|
eb
|
||||||
.selectFrom(other.entity.name)
|
.selectFrom(other.entity.name)
|
||||||
.select(other.entity.getSelect(other.entity.name))
|
|
||||||
.where(whereLhs, "=", reference)
|
.where(whereLhs, "=", reference)
|
||||||
.whereRef(entityRef, "=", otherRef)
|
.whereRef(entityRef, "=", otherRef)
|
||||||
.limit(limit);
|
.$if(other.cardinality === 1, (qb) => qb.limit(1));
|
||||||
|
|
||||||
/*return qb.select((eb) =>
|
|
||||||
jsonFrom(
|
|
||||||
eb
|
|
||||||
.selectFrom(other.entity.name)
|
|
||||||
.select(other.entity.getSelect(other.entity.name))
|
|
||||||
.where(whereLhs, "=", reference)
|
|
||||||
.whereRef(entityRef, "=", otherRef)
|
|
||||||
.limit(limit)
|
|
||||||
).as(other.reference)
|
|
||||||
);*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override isListableFor(entity: Entity): boolean {
|
override isListableFor(entity: Entity): boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user