Skip to content

Commit

Permalink
feat(orm): new selector API, still work in progress 4
Browse files Browse the repository at this point in the history
  • Loading branch information
marcj committed Jun 25, 2024
1 parent cfba6ad commit d59cbf7
Show file tree
Hide file tree
Showing 39 changed files with 1,005 additions and 914 deletions.
9 changes: 5 additions & 4 deletions packages/orm-integration/src/active-record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Tag } from './active-record/tag.js';
import { BookTag } from './active-record/book-tag.js';
import { Group } from './bookstore/group.js';
import { DatabaseFactory } from './test.js';
import { join } from '@deepkit/orm';

export const activeRecordTests = {
async basics(databaseFactory: DatabaseFactory) {
Expand Down Expand Up @@ -43,7 +44,7 @@ export const activeRecordTests = {
expect(await database.query(User).count()).toBe(2);

{
const books = await Book.query().useInnerJoinWith('author').innerJoinWith('groups').end().find();
const books = await Book.query(m => [m, join(m.author), join(m.author.groups)]).find();
expect(books.length).toBe(1); //because user1 has no group assigned
const book1Db = books[0];
expect(book1Db.author.name).toBe('peter');
Expand All @@ -54,7 +55,7 @@ export const activeRecordTests = {

{
await database.persist(new UserGroup(user2, group1));
const books = await Book.query().useInnerJoinWith('author').innerJoinWith('groups').end().find();
const books = await Book.query(m => [m, m.author, m.author.groups]).find();
expect(books.length).toBe(2); //because user1 has now a group
const book1Db = books[0];
expect(book1Db.title).toBe('My book');
Expand Down Expand Up @@ -82,7 +83,7 @@ export const activeRecordTests = {
await tagAssignment.save();

{
const books = await Book.query().joinWith('tags').find();
const books = await Book.query(m => [m, join(m.tags)]).find();
expect(books.length).toBe(1);
const book1DB = books[0];
expect(book1DB.author.id).toBe(user1.id);
Expand All @@ -95,7 +96,7 @@ export const activeRecordTests = {
await new BookTag(book1, tagHot).save();

{
const books = await Book.query().joinWith('tags').find();
const books = await Book.query(m => [m, m.tags]).find();
expect(books.length).toBe(1);
const book1DB = books[0];
expect(book1DB.author.id).toBe(user1.id);
Expand Down
20 changes: 16 additions & 4 deletions packages/orm-integration/src/bookstore.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { expect } from '@jest/globals';
import { assertType, AutoIncrement, BackReference, cast, entity, PrimaryKey, Reference, ReflectionClass, ReflectionKind, UUID, uuid } from '@deepkit/type';
import {
assertType,
AutoIncrement,
BackReference,
cast,
entity,
PrimaryKey,
Reference,
ReflectionClass,
ReflectionKind,
UUID,
uuid,
} from '@deepkit/type';
import { User, UserGroup } from './bookstore/user.js';
import { UserCredentials } from './bookstore/user-credentials.js';
import { atomicChange, DatabaseSession, getInstanceStateFromItem, Query } from '@deepkit/orm';
import { atomicChange, DatabaseSession, getInstanceStateFromItem, onPatchPost, onPatchPre } from '@deepkit/orm';
import { isArray } from '@deepkit/core';
import { Group } from './bookstore/group.js';
import { DatabaseFactory } from './test.js';
Expand Down Expand Up @@ -662,12 +674,12 @@ export const bookstoreTests = {
}
});

database.listen(Query.onPatchPre, event => {
database.listen(onPatchPre, event => {
if (event.isSchemaOf(User)) {
event.patch.increase('version', 1);
}
});
database.listen(Query.onPatchPost, event => {
database.listen(onPatchPost, event => {
if (event.isSchemaOf(User)) {
expect(isArray(event.patchResult.returning['version'])).toBe(true);
expect(event.patchResult.returning['version']![0]).toBeGreaterThan(0);
Expand Down
6 changes: 4 additions & 2 deletions packages/orm-integration/src/log-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LogPlugin, LogType, LogQuery, LogSession } from '@deepkit/orm';
import { LogPlugin, LogSession, LogType, setLogAuthor } from '@deepkit/orm';
import { AutoIncrement, deserialize, entity, PrimaryKey } from '@deepkit/type';
import { DatabaseFactory } from './test.js';
import { expect } from '@jest/globals';
Expand Down Expand Up @@ -64,7 +64,9 @@ export const logPluginTests = {
]);
}

const deleteMany = await database.query(User).lift(LogQuery).byLogAuthor('Foo').deleteMany();
const deleteMany = await database.singleQuery(User, m => {
setLogAuthor('Foo');
}).deleteMany();

{
const logEntries = await database.query(userLogEntity).find();
Expand Down
57 changes: 33 additions & 24 deletions packages/orm-integration/src/soft-delete-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { SoftDeletePlugin, SoftDeleteQuery, SoftDeleteSession } from '@deepkit/orm';
import {
includeSoftDeleted,
restoreMany,
restoreOne,
setDeletedBy,
SoftDeletePlugin,
SoftDeleteSession,
} from '@deepkit/orm';
import { AutoIncrement, cast, entity, PrimaryKey } from '@deepkit/type';
import { DatabaseFactory } from './test.js';
import { expect } from '@jest/globals';
Expand All @@ -20,42 +27,44 @@ export const softDeletePluginTests = {
await database.persist(cast<s>({ id: 2, username: 'Joe' }));
await database.persist(cast<s>({ id: 3, username: 'Lizz' }));

expect(await database.query(s).count()).toBe(3);
expect(await database.singleQuery(s).count()).toBe(3);

await database.query(s).filter({ id: 1 }).deleteOne();
expect(await database.query(s).count()).toBe(2);
await database.singleQuery(s).filter({ id: 1 }).deleteOne();
expect(await database.singleQuery(s).count()).toBe(2);

//soft delete using deletedBy
await database.query(s).lift(SoftDeleteQuery).filter({ id: 2 }).deletedBy('me').deleteOne();
expect(await database.query(s).count()).toBe(1);
await database.singleQuery(s, m => {
setDeletedBy('me');
}).filter({ id: 2 }).deleteOne();
expect(await database.singleQuery(s).count()).toBe(1);
{
const deleted2 = await database.query(s).lift(SoftDeleteQuery).withSoftDeleted().filter({ id: 2 }).findOne();
const deleted2 = await database.singleQuery(s, m => includeSoftDeleted()).filter({ id: 2 }).findOne();
expect(deleted2.id).toBe(2);
expect(deleted2.deletedAt).not.toBe(undefined);
expect(deleted2.deletedBy).toBe('me');
}

//restore first
await database.query(s).filter({ id: 1 }).lift(SoftDeleteQuery).restoreOne();
expect(await database.query(s).count()).toBe(2);
await restoreOne(database.singleQuery(s).filter({ id: 1 }));
expect(await database.singleQuery(s).count()).toBe(2);

//restore all
await database.query(s).lift(SoftDeleteQuery).restoreMany();
expect(await database.query(s).count()).toBe(3);
await restoreMany(database.singleQuery(s));
expect(await database.singleQuery(s).count()).toBe(3);
{
const deleted2 = await database.query(s).filter({ id: 2 }).findOne();
const deleted2 = await database.singleQuery(s).filter({ id: 2 }).findOne();
expect(deleted2.deletedBy).toBe(undefined);
}

//soft delete everything
await database.query(s).deleteMany();
expect(await database.query(s).count()).toBe(0);
expect(await database.query(s).lift(SoftDeleteQuery).withSoftDeleted().count()).toBe(3);
await database.singleQuery(s).deleteMany();
expect(await database.singleQuery(s).count()).toBe(0);
expect(await database.singleQuery(s, m => includeSoftDeleted()).count()).toBe(3);

//hard delete everything
await database.query(s).lift(SoftDeleteQuery).withSoftDeleted().deleteMany();
expect(await database.query(s).count()).toBe(0);
expect(await database.query(s).lift(SoftDeleteQuery).withSoftDeleted().count()).toBe(0);
await database.singleQuery(s, m => includeSoftDeleted()).deleteMany();
expect(await database.singleQuery(s).count()).toBe(0);
expect(await database.singleQuery(s, m => includeSoftDeleted()).count()).toBe(0);

database.disconnect();
},
Expand Down Expand Up @@ -83,18 +92,18 @@ export const softDeletePluginTests = {
session.add(peter, joe, lizz);
await session.commit();

expect(await database.query(User).count()).toBe(3);
expect(await database.singleQuery(User).count()).toBe(3);

{
const peter = await session.query(User).filter({ id: 1 }).findOne();
session.remove(peter);
await session.commit();
expect(await database.query(User).count()).toBe(2);
expect(await SoftDeleteQuery.from(session.query(User)).withSoftDeleted().count()).toBe(3);
expect(await database.singleQuery(User).count()).toBe(2);
expect(await database.singleQuery(User, m => includeSoftDeleted()).count()).toBe(3);

session.from(SoftDeleteSession).restore(peter);
await session.commit();
expect(await database.query(User).count()).toBe(3);
expect(await database.singleQuery(User).count()).toBe(3);
{
const deletedPeter = await session.query(User).filter(peter).findOne();
expect(deletedPeter.deletedAt).toBe(undefined);
Expand All @@ -104,8 +113,8 @@ export const softDeletePluginTests = {
session.from(SoftDeleteSession).setDeletedBy(User, 'me');
session.remove(peter);
await session.commit();
expect(await database.query(User).count()).toBe(2);
const deletedPeter = await SoftDeleteQuery.from(session.query(User)).withSoftDeleted().filter(peter).findOne();
expect(await database.singleQuery(User).count()).toBe(2);
const deletedPeter = await session.query(User, m => includeSoftDeleted()).filter(peter).findOne();
expect(deletedPeter.deletedAt).toBeInstanceOf(Date);
expect(deletedPeter.deletedBy).toBe('me');

Expand Down
87 changes: 44 additions & 43 deletions packages/orm-integration/src/various.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,49 +74,50 @@ export const variousTests = {

type Count = {count: number};

{
const result = await database.raw<Count>(sql`SELECT count(*) as count
FROM ${user}`).findOne();
expect(result.count).toBe(2);
}

{
const result = await database.createSession().raw<Count>(sql`SELECT count(*) as count
FROM ${user}`).findOne();
expect(result.count).toBe(2);
}

{
const id = 1;
const result = await database.createSession().raw<Count>(sql`SELECT count(*) as count
FROM ${user}
WHERE id > ${id}`).findOne();
expect(result.count).toBe(1);
}

{
const result = await database.raw<user>(sql`SELECT * FROM ${user}`).find();
expect(result).toEqual([
{ id: 1, username: 'peter' },
{ id: 2, username: 'marie' },
]);
}

{
const result = await database.createSession().raw<user>(sql`SELECT * FROM ${user}`).find();
expect(result).toEqual([
{ id: 1, username: 'peter' },
{ id: 2, username: 'marie' },
]);
}

await database.raw(sql`DELETE FROM ${user}`).execute();

{
const result = await database.raw<Count>(sql`SELECT count(*) as count FROM ${user}`).findOne();
expect(result.count).toBe(0);
}
database.disconnect();
throw new Error('Reimplement this test');
// {
// const result = await database.raw<Count>(sql`SELECT count(*) as count
// FROM ${user}`).findOne();
// expect(result.count).toBe(2);
// }
//
// {
// const result = await database.createSession().raw<Count>(sql`SELECT count(*) as count
// FROM ${user}`).findOne();
// expect(result.count).toBe(2);
// }
//
// {
// const id = 1;
// const result = await database.createSession().raw<Count>(sql`SELECT count(*) as count
// FROM ${user}
// WHERE id > ${id}`).findOne();
// expect(result.count).toBe(1);
// }
//
// {
// const result = await database.raw<user>(sql`SELECT * FROM ${user}`).find();
// expect(result).toEqual([
// { id: 1, username: 'peter' },
// { id: 2, username: 'marie' },
// ]);
// }
//
// {
// const result = await database.createSession().raw<user>(sql`SELECT * FROM ${user}`).find();
// expect(result).toEqual([
// { id: 1, username: 'peter' },
// { id: 2, username: 'marie' },
// ]);
// }
//
// await database.raw(sql`DELETE FROM ${user}`).execute();
//
// {
// const result = await database.raw<Count>(sql`SELECT count(*) as count FROM ${user}`).findOne();
// expect(result.count).toBe(0);
// }
// database.disconnect();
},
async testRawWhere(databaseFactory: DatabaseFactory) {
@entity.name('test_connection_user')
Expand Down
8 changes: 0 additions & 8 deletions packages/orm/src/database-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,6 @@ export class MigrateOptions {
* You can specify a more specialized adapter like MysqlDatabaseAdapter/MongoDatabaseAdapter with special API for MySQL/Mongo.
*/
export abstract class DatabaseAdapter {
// abstract queryFactory(session: DatabaseSession<this>): DatabaseAdapterQueryFactory;
//
// createQuery2Resolver?(session: DatabaseSession<this>): Query2Resolver<any>;
//
// rawFactory(session: DatabaseSession<this>): RawFactory<any> {
// return new RawFactory();
// };

abstract createSelectorResolver<T extends OrmEntity>(session: DatabaseSession<this>): SelectorResolver<T>;

abstract createPersistence(session: DatabaseSession<this>): DatabasePersistence;
Expand Down
27 changes: 7 additions & 20 deletions packages/orm/src/database-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { DatabaseLogger } from './logger.js';
import { Stopwatch } from '@deepkit/stopwatch';
import { EventDispatcher, EventDispatcherInterface, EventToken } from '@deepkit/event';
import { DatabasePluginRegistry } from './plugin/plugin.js';
import { query, Query2, SelectorInferredState, SelectorRefs, SelectorState, singleQuery } from './select.js';
import { From, query, Query2, SelectorInferredState, SelectorRefs, SelectorState, singleQuery } from './select.js';

let SESSION_IDS = 0;

Expand Down Expand Up @@ -336,33 +336,20 @@ export class DatabaseSession<ADAPTER extends DatabaseAdapter = DatabaseAdapter>
public logger: DatabaseLogger = new DatabaseLogger,
public stopwatch?: Stopwatch,
) {
// const queryFactory = this.adapter.queryFactory(this);
//
// //we cannot use arrow functions, since they can't have ReceiveType<T>
// function query<T extends OrmEntity>(type?: ReceiveType<T> | ClassType<T> | AbstractClassType<T> | ReflectionClass<T>) {
// const result = queryFactory.createQuery(type);
// result.model.adapterName = adapter.getName();
// return result;
// }
//
// this.query = query as any;
// this.query = {} as any;

// const factory = this.adapter.rawFactory(this);
// this.raw = (...args: any[]) => {
// forwardTypeArguments(this.raw, factory.create);
// return factory.create(...args);
// };
}

singleQuery<const R extends any, T extends object>(classType: ClassType<T>, cb?: (main: SelectorRefs<T>) => R | undefined): Query2<T, R> {
query(...args: any[]): any {
throw new Error('Deprecated');
}

singleQuery<const R extends any, T extends object>(classType: ClassType<T> | From<T>, cb?: (main: SelectorRefs<T>) => R | undefined): Query2<T, R> {
return this.query2(singleQuery(classType, cb));
}

query2<const R extends any, T extends object, Q extends SelectorInferredState<T, R> | SelectorState<T> | ((main: SelectorRefs<T>, ...args: SelectorRefs<unknown>[]) => R | undefined)>(cbOrQ?: Q): Query2<T, R> {
if (!cbOrQ) throw new Error('Query2 needs a callback or query object');
const state: SelectorInferredState<any, any> = isFunction(cbOrQ) ? query(cbOrQ) : 'state' in cbOrQ ? cbOrQ : { state: cbOrQ };
return new Query2(state.state, this, this.adapter.createSelectorResolver(this));
return new Query2(state.state, this, this.adapter.createSelectorResolver(this)) as Query2<T, R>;
}

/**
Expand Down
Loading

0 comments on commit d59cbf7

Please sign in to comment.