Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Outer join nullable #11

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .prettierrc.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
tabWidth = 4
printWidth = 220
printWidth = 220
2 changes: 1 addition & 1 deletion src/IsNullable.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type IsNullable<T> = null extends T ? true : never;
export type IsNullable<T> = null extends T ? true : never;
2 changes: 1 addition & 1 deletion src/NonForeignKeyObjects.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type NonForeignKeyObjects = any[] | Date;
export type NonForeignKeyObjects = any[] | Date;
2 changes: 1 addition & 1 deletion src/NonNullableRecursive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import { NonForeignKeyObjects } from './NonForeignKeyObjects';
// Removes all optional, undefined and null from type
export type NonNullableRecursive<T> = {
[P in keyof T]-?: T[P] extends object ? T[P] extends NonForeignKeyObjects ? Required<NonNullable<T[P]>> : NonNullableRecursive<T[P]> : Required<NonNullable<T[P]>>;
};
};
1 change: 1 addition & 0 deletions src/TransformPropertiesToFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type GetNullIfNullable<T> = T extends { nullable: never } ? never : null;
type AddToArray<T extends { name: string }[], A extends any> = ((a: A, ...t: T) => void) extends ((...u: infer U) => void) ? U : never;



// Take { a : string, b : { c : string }} and return { a : ()=> {a: string}, b : { c : ()=> { b: { c: string } }}}
// PropertyPath contains the path to one leaf. {a : { b: string }} will have a PropertyPath of [{name:'b'}, {name:'a'}]
// Special cases:
Expand Down
47 changes: 37 additions & 10 deletions src/typedKnex.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// tslint:disable:use-named-parameter
import { unflatten } from 'flat';
import * as Knex from 'knex';
import {
getColumnInformation,
Expand All @@ -10,7 +9,7 @@ import {
import { NonForeignKeyObjects } from './NonForeignKeyObjects';
import { NonNullableRecursive } from './NonNullableRecursive';
import { TransformPropertiesToFunction } from './TransformPropertiesToFunction';
import { FlattenOption, setToNull } from './unflatten';
import { FlattenOption, setToNull, unflatten } from './unflatten';


export class TypedKnex {
Expand Down Expand Up @@ -72,7 +71,7 @@ export interface ITypedQueryBuilder<Model, SelectableModel, Row> {

orderBy: IOrderBy<Model, SelectableModel, Row>;
innerJoinColumn: IKeyFunctionAsParametersReturnQueryBuider<Model, SelectableModel, Row>;
leftOuterJoinColumn: IKeyFunctionAsParametersReturnQueryBuider<Model, SelectableModel, Row>;
leftOuterJoinColumn: IOuterJoin<Model, SelectableModel, Row>;

whereColumn: IWhereCompareTwoColumns<Model, SelectableModel, Row>;

Expand All @@ -81,7 +80,7 @@ export interface ITypedQueryBuilder<Model, SelectableModel, Row> {
orWhereNull: IColumnParameterNoRowTransformation<Model, SelectableModel, Row>;
orWhereNotNull: IColumnParameterNoRowTransformation<Model, SelectableModel, Row>;

leftOuterJoinTableOnFunction: IJoinTableMultipleOnClauses<
leftOuterJoinTableOnFunction: IOuterJoinTableMultipleOnClauses<
Model,
SelectableModel,
Row extends Model ? {} : Row
Expand Down Expand Up @@ -317,6 +316,40 @@ interface IJoinTableMultipleOnClauses<Model, _SelectableModel, Row> {
>;
}

interface IOuterJoinTableMultipleOnClauses<Model, _SelectableModel, Row> {
<
NewPropertyType,
NewPropertyKey extends keyof any
>(
newPropertyKey: NewPropertyKey,
newPropertyClass: new () => NewPropertyType,
on: (
join: IJoinOnClause2<
AddPropertyWithType<Model, NewPropertyKey, NewPropertyType>,
NewPropertyType
>
) => void
): ITypedQueryBuilder<
Model,
Copy link

@beem812 beem812 Feb 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edit for clarity:

On this and on other joins, this return type overrides any previous joins. By that I mean if I have a table A, and I join table B and then I join table C, when I go to select the columns I want I only have access to columns from table A and table C. This means if I want columns from all three tables I have to join to table B, select columns from it then join to table C and select columns from it. if the return type was ITypedQueryBuilder<Model, AddPropertyWithType<_SelectableModel, NewPropertyKey, NewPropertyType | null>, Row> all joined tables would be selectable when you got to choose what columns you want.

AddPropertyWithType<Model, NewPropertyKey, NewPropertyType | null>,
Row
>;
}



interface IOuterJoin<Model, SelectableModel, Row> {
(
selectColumnFunction: (
c: TransformPropertiesToFunction<Model>
) => void
): ITypedQueryBuilder<Model, SelectableModel, Row>;


}



interface ISelectRaw<Model, SelectableModel, Row> {
<
TReturn extends Boolean | String | Number,
Expand Down Expand Up @@ -627,12 +660,6 @@ interface IKeyFunctionAsParametersReturnQueryBuider<Model, SelectableModel, Row>
) => void
): ITypedQueryBuilder<Model, SelectableModel, Row>;

(
selectColumnFunction: (
c: TransformPropertiesToFunction<NonNullableRecursive<Model>>
) => void,
setToNullIfNullFunction: (r: Row) => void
): ITypedQueryBuilder<Model, SelectableModel, Row>;
}

interface IWhere<Model, SelectableModel, Row> {
Expand Down
39 changes: 38 additions & 1 deletion test/compilation/compilationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ describe('compile time typed-knex', function() {

if (item !== undefined) {
console.log(item.user2.numericValue);
console.log(item.otherUser.name);
console.log(item.otherUser?.name);
}

})();
Expand All @@ -537,6 +537,43 @@ describe('compile time typed-knex', function() {
done();
});


it('should fail leftOuterJoinTableOnFunction result if it is used as not null', done => {
file = project.createSourceFile(
'test/test4.ts',
`
import * as knex from 'knex';
import { TypedKnex } from '../src/typedKnex';
import { User, UserSetting } from './testEntities';


(async () => {

const typedKnex = new TypedKnex(knex({ client: 'postgresql' }));

const item = await typedKnex
.query(UserSetting)
.leftOuterJoinTableOnFunction('otherUser', User, join => {
join.onColumns(i => i.user2Id, '=', j => j.id);
})
.select(i => [i.otherUser.name, i.user2.numericValue])
.getFirst();

if (item !== undefined) {
console.log(item.user2.numericValue);
console.log(item.otherUser.name);
}

})();
`
);

assert.equal(project.getPreEmitDiagnostics().length, 1);

file.delete();
done();
});

it('should not return type from leftOuterJoinTableOnFunction with not selected from joined table', done => {
file = project.createSourceFile(
'test/test4.ts',
Expand Down
2 changes: 2 additions & 0 deletions test/testEntities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,6 @@ export class UserSetting {
public value!: string;
@Column()
public initialValue!: string;
@Column({ name: 'user3Id' })
public user3?: User;
}
70 changes: 58 additions & 12 deletions test/unit/typedQueryBuilderTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1340,24 +1340,70 @@ describe('TypedKnexQueryBuilder', () => {
});

// it('should stay commented out', async done => {

// const typedKnex = new TypedKnex(knex({ client: 'postgresql' }));
// const query = typedKnex
// .query(UserSetting)
// .innerJoinTableOnFunction('otherUser', User, join => {
// join.onColumns(i => i.user2Id, '=', j => j.id);
// join.onNull(i => i.name);
// })
// .select(i => i.otherUser.birthDate);

// // const item = await typedKnex
// // .query(UserSetting)
// // .insertItem({ id: '1', key: });
// const a = await query.getFirst();
// console.log('a: ', a.otherUser.birthDate);
// console.log('a: ', a.otherUser?.birthDate);

// done();
// });

// it('should stay commented out', async done => {

// const typedKnex = new TypedKnex(knex({ client: 'postgresql' }));
// const query = typedKnex
// .query(UserSetting)
// .leftOuterJoinTableOnFunction('otherUser', User, join => {
// join.onColumns(i => i.user2Id, '=', j => j.id);
// join.onNull(i => i.name);
// })
// .select(i => i.otherUser.birthDate);

// const a = await query.getFirst();
// console.log('a: ', a.otherUser.birthDate);
// console.log('a: ', a.otherUser?.birthDate);

// done();
// })

// const item = await typedKnex
// .query(User)
// .select(i => i.category.name)
// .getFirst();

// console.log('item: ', item.category.name);
// it('should stay commented out', async done => {

// const typedKnex = new TypedKnex(knex({ client: 'postgresql' }));
// const query = typedKnex
// .query(UserSetting)
// .innerJoinColumn(i => i.user3)
// .select(i => i.user3.birthDate);

// const a = await query.getFirst();
// console.log('a: ', a.user3.birthDate);
// console.log('a: ', a.user3?.birthDate);

// done();
// });

// // if (item !== undefined) {
// // console.log(item.user2.numericValue);
// // console.log(item.otherUser.name);
// // }
// it('should stay commented out', async done => {

// const typedKnex = new TypedKnex(knex({ client: 'postgresql' }));
// const query = typedKnex
// .query(UserSetting)
// .leftOuterJoinColumn(i => i.user3)
// .select(i => i.user3.birthDate);

// const a = await query.getFirst();
// console.log('a: ', a.user3.birthDate);
// console.log('a: ', a.user3?.birthDate);

// done();
// });

});