Skip to content

Commit

Permalink
feat(orm): new selector API, still work in progress 2
Browse files Browse the repository at this point in the history
  • Loading branch information
marcj committed Jun 21, 2024
1 parent 1108062 commit cb612ad
Show file tree
Hide file tree
Showing 16 changed files with 636 additions and 463 deletions.
3 changes: 1 addition & 2 deletions packages/orm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@
"@deepkit/type": "^1.0.1-alpha.13"
},
"dependencies": {
"@deepkit/topsort": "^1.0.1-alpha.121",
"sift": "^7.0.1"
"@deepkit/topsort": "^1.0.1-alpha.121"
},
"devDependencies": {
"@deepkit/core": "^1.0.1-alpha.147",
Expand Down
18 changes: 10 additions & 8 deletions packages/orm/src/database-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
} from '@deepkit/type';
import { Query } from './query.js';
import { DatabaseSession, DatabaseTransaction } from './database-session.js';
import { Query2Resolver } from './select.js';
import { SelectorResolver } from './select.js';

export abstract class DatabaseAdapterQueryFactory {
abstract createQuery<T extends OrmEntity>(type?: ReceiveType<T> | ClassType<T> | AbstractClassType<T> | ReflectionClass<T>): Query<T>;
Expand Down Expand Up @@ -110,13 +110,15 @@ 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 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
66 changes: 36 additions & 30 deletions packages/orm/src/database-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
import type { DatabaseAdapter, DatabasePersistence, DatabasePersistenceChangeSet } from './database-adapter.js';
import { DatabaseEntityRegistry } from './database-adapter.js';
import { DatabaseValidationError, OrmEntity } from './type.js';
import { AbstractClassType, ClassType, CustomError } from '@deepkit/core';
import { ClassType, CustomError, isFunction } from '@deepkit/core';
import {
getPrimaryKeyExtractor,
isReferenceInstance,
markAsHydrated,
PrimaryKeyFields,
ReceiveType,
ReflectionClass,
typeSettings,
UnpopulatedCheck,
Expand All @@ -40,6 +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 } from './select.js';

let SESSION_IDS = 0;

Expand Down Expand Up @@ -307,9 +307,9 @@ export class DatabaseSession<ADAPTER extends DatabaseAdapter = DatabaseAdapter>
/**
* Creates a new DatabaseQuery instance which can be used to query and manipulate data.
*/
public readonly query: ReturnType<this['adapter']['queryFactory']>['createQuery'];

public readonly raw!: ReturnType<this['adapter']['rawFactory']>['create'];
// public readonly query: ReturnType<this['adapter']['queryFactory']>['createQuery'];
//
// public readonly raw!: ReturnType<this['adapter']['rawFactory']>['create'];

protected rounds: DatabaseSessionRound<ADAPTER>[] = [];

Expand All @@ -336,16 +336,16 @@ 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;
// 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);
Expand All @@ -355,6 +355,12 @@ export class DatabaseSession<ADAPTER extends DatabaseAdapter = DatabaseAdapter>
// };
}

query2<const R extends any, T extends object, Q extends SelectorInferredState<T, R> | ((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) : cbOrQ;
return new Query2(state.state, this, this.adapter.createSelectorResolver(this));
}

/**
* Marks this session as transactional. On the next query or flush/commit() a transaction on the database adapter is started.
* Use flush(), commit(), and rollback() to control the transaction behavior. All created query objects from this session
Expand Down Expand Up @@ -518,21 +524,21 @@ export class DatabaseSession<ADAPTER extends DatabaseAdapter = DatabaseAdapter>
const classSchema = this.entityRegistry.getFromInstance(item);
const pk = getPrimaryKeyExtractor(classSchema)(item);

const itemDB = await this.query(classSchema).filter(pk).findOne();

for (const property of classSchema.getProperties()) {
if (property.isPrimaryKey()) continue;
if (property.isReference() || property.isBackReference()) continue;

//we set only not overwritten values
if (!item.hasOwnProperty(property.symbol)) {
Object.defineProperty(item, property.symbol, {
enumerable: false,
configurable: true,
value: itemDB[property.getNameAsString() as keyof T],
});
}
}
// const itemDB = await this.query(classSchema).filter(pk).findOne();
//
// for (const property of classSchema.getProperties()) {
// if (property.isPrimaryKey()) continue;
// if (property.isReference() || property.isBackReference()) continue;
//
// //we set only not overwritten values
// if (!item.hasOwnProperty(property.symbol)) {
// Object.defineProperty(item, property.symbol, {
// enumerable: false,
// configurable: true,
// value: itemDB[property.getNameAsString() as keyof T],
// });
// }
// }

markAsHydrated(item);
}
Expand Down
62 changes: 30 additions & 32 deletions packages/orm/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,7 @@
* You should have received a copy of the MIT License along with this program.
*/

import {
AbstractClassType,
ClassType,
forwardTypeArguments,
getClassName,
getClassTypeFromInstance,
} from '@deepkit/core';
import { AbstractClassType, ClassType, getClassName, getClassTypeFromInstance } from '@deepkit/core';
import {
entityAnnotation,
EntityOptions,
Expand All @@ -38,7 +32,7 @@ import { Stopwatch } from '@deepkit/stopwatch';
import { getClassState, getInstanceState, getNormalizedPrimaryKey } from './identity-map.js';
import { EventDispatcher, EventDispatcherUnsubscribe, EventListenerCallback, EventToken } from '@deepkit/event';
import { DatabasePlugin, DatabasePluginRegistry } from './plugin/plugin.js';
import { Query2 } from './select.js';
import { Query2, SelectorInferredState, SelectorRefs } from './select.js';

/**
* Hydrates not completely populated item and makes it completely accessible.
Expand Down Expand Up @@ -135,9 +129,9 @@ export class Database<ADAPTER extends DatabaseAdapter = DatabaseAdapter> {
* await session.commit(); //only necessary when you changed items received by this session
* ```
*/
public readonly query: ReturnType<this['adapter']['queryFactory']>['createQuery'];

public readonly raw: ReturnType<this['adapter']['rawFactory']>['create'];
// public readonly query: ReturnType<this['adapter']['queryFactory']>['createQuery'];
//
// public readonly raw: ReturnType<this['adapter']['rawFactory']>['create'];

protected virtualForeignKeyConstraint: VirtualForeignKeyConstraint = new VirtualForeignKeyConstraint(this);

Expand All @@ -154,24 +148,24 @@ export class Database<ADAPTER extends DatabaseAdapter = DatabaseAdapter> {
this.entityRegistry.add(...schemas);
if (Database.registry) Database.registry.push(this);

const self = 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 session = self.createSession();
session.withIdentityMap = false;
return session.query(type);
}

this.query = query;

this.raw = (...args: any[]) => {
const session = this.createSession();
session.withIdentityMap = false;
if (!session.raw) throw new Error('Adapter has no raw mode');
forwardTypeArguments(this.raw, session.raw);
return session.raw(...args);
};
// const self = 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 session = self.createSession();
// session.withIdentityMap = false;
// return session.query(type);
// }

// this.query = query;
//
// this.raw = (...args: any[]) => {
// const session = this.createSession();
// session.withIdentityMap = false;
// if (!session.raw) throw new Error('Adapter has no raw mode');
// forwardTypeArguments(this.raw, session.raw);
// return session.raw(...args);
// };

this.registerEntity(...schemas);

Expand All @@ -180,10 +174,14 @@ export class Database<ADAPTER extends DatabaseAdapter = DatabaseAdapter> {
}
}

from<T extends OrmEntity>(type?: ReceiveType<T>) {
query<T extends any>(...args: any[]): any {
throw new Error('Deprecated');
}

query2<const R extends any, T extends object, Q extends SelectorInferredState<T, R> | ((main: SelectorRefs<T>, ...args: SelectorRefs<unknown>[]) => R | undefined)>(cbOrQ?: Q): Query2<T, R> {
const session = this.createSession();
if (!this.adapter.createQuery2Resolver) throw new Error('Adapter has no createQuery2Resolver method');
return new Query2(ReflectionClass.fromType(resolveReceiveType(type)), session, this.adapter.createQuery2Resolver(session));
session.withIdentityMap = false;
return session.query2(cbOrQ as any);
}

registerPlugin(...plugins: DatabasePlugin[]): void {
Expand Down
Loading

0 comments on commit cb612ad

Please sign in to comment.