Commit 7718370f authored by William Naslund's avatar William Naslund

added recursive parent querying

parent 8acba1df
......@@ -26,6 +26,8 @@ export class Database {
/** Migrates the database to its currently registered components */
async migrate() {
// Setup Tables
for(const modelType of this.tables) {
const tableInfo = getTableInformation(modelType);
......@@ -71,6 +73,12 @@ export class Database {
}
}
}
// Setup Relationships
for(const modelType of this.tables) {
const tableInfo = getTableInformation(modelType);
// Parent lookup constraints
for(const lookup of getParentLookups(modelType)) {
const parentInfo = getTableInformation(lookup.model);
......
......@@ -24,7 +24,7 @@ export class PGModelAdapter implements DBModelAdapter {
const idInfo = modelInfo.options.id ? getFieldInformation(model, modelInfo.options.id) : null;
const insertRes = await this.db.command.query(`
INSERT INTO ${modelInfo.options.schema}.${model.name}
INSERT INTO ${modelInfo.options.schema}.${modelInfo.name}
(${fieldNames.join(', ')})
VALUES (${placeholders.join(', ')})
${idInfo ? 'RETURNING ' + idInfo.name : ''}
......
......@@ -50,6 +50,12 @@ export class PGQueryAdapter implements DBQueryAdapter {
for(const field of parent.req.fields) {
parentFields.push(`${lookupId}.${field} AS ${lookupId}___${field}`);
}
for(const parentLookupName in parent.req.parents) {
const parentParentFields = this.getParentFields(`${lookupId}__${parentLookupName}`, parent.req.parents[parentLookupName]);
for(const parentParentField of parentParentFields) {
parentFields.push(parentParentField);
}
}
return parentFields;
}
......@@ -63,6 +69,12 @@ export class PGQueryAdapter implements DBQueryAdapter {
+ `AS ${lookupId} `
+ `ON ${currentId != null && currentId.trim() != '' ? currentId + '.' : ''}${lookupField} = ${lookupId}.${idInfo.name}`
];
for(const parentLookupName in parent.req.parents) {
const parentJoins = this.getParentJoins(`${lookupId}__${parentLookupName}`, parentLookupName, lookupId, parent.req.parents[parentLookupName]);
for(const join of parentJoins) {
joins.push(join);
}
}
return joins;
}
......@@ -85,6 +97,11 @@ export class PGQueryAdapter implements DBQueryAdapter {
delete record[recordField];
}
for(const parentLookupName in parent.req.parents) {
const parentParent = this.getParentValues(record, `${lookupId}__${parentLookupName}`, parent.req.parents[parentLookupName]);
parentData.parents[parentLookupName] = parentParent;
}
return parentData;
}
......
......@@ -10,13 +10,16 @@ export class DBQuery<T extends DBModel> {
/** The base table that is being selected */
private readonly req: DBQueryRequest;
/** The property names of the fields to select */
private fieldPropertyNames: any[] = [];
constructor(
model: DBModelConstructor<T>,
private readonly db: Database
) {
this.req = {
model: model,
fields: null,
fields: [ ],
parents: { },
children: { }
};
......@@ -30,8 +33,16 @@ export class DBQuery<T extends DBModel> {
/** Returns all the rows from the query */
public async getAll(): Promise<T[]> {
if(this.req.fields == null) {
this.req.fields = [ '*' ];
if(this.fieldPropertyNames.length < 1) {
for(const fieldInfo of getTableFields(this.req.model) || []) {
this.req.fields.push(fieldInfo.name);
}
} else {
for(const fieldInfo of getTableFields(this.req.model) || []) {
if(this.fieldPropertyNames.includes(fieldInfo.propertyKey)) {
this.req.fields.push(fieldInfo.name);
}
}
}
const lookupFields = new Set<string>();
......@@ -67,7 +78,9 @@ export class DBQuery<T extends DBModel> {
/** Adds fields on the base model to be selected */
public select(...fields: (keyof T)[]): this {
this.req.fields = fields;
if(fields != null) {
this.fieldPropertyNames = fields;
}
return this;
}
......@@ -106,6 +119,9 @@ export class DBQuery<T extends DBModel> {
const parentModel = new lookup.model();
this.setFieldData(lookup.model, parentModel, lookupData);
for(const parentLookup of Reflect.getMetadata(TABLE_PARENT_LOOKUPS, lookup.model) as DBLookupInformation[] || []) {
this.setParent(parentLookup, parentModel, res.parents[lookup.name]);
}
model[lookup.propertyKey] = parentModel;
}
......@@ -130,11 +146,21 @@ export class DBQueryParent<T extends DBModel> {
public readonly lookupProperty: any;
constructor(model: DBModelConstructor<T>, lookupField: any, fields?: (keyof T)[]) {
this.lookupProperty = lookupField;
constructor(model: DBModelConstructor<T>, lookupProperty: any, fields?: (keyof T)[]) {
this.lookupProperty = lookupProperty;
const fieldsToSelect: string[] = [];
for(const fieldInfo of getTableFields(model) || []) {
if(fields == null || fields.length < 1) {
fieldsToSelect.push(fieldInfo.name);
} else if(fields.includes(fieldInfo.propertyKey as any)) {
fieldsToSelect.push(fieldInfo.name);
}
}
this.req = {
model: model,
fields: fields != null && fields.length > 0 ? fields : [ '*' ],
fields: fieldsToSelect,
parents: { },
children: { }
};
......@@ -143,7 +169,10 @@ export class DBQueryParent<T extends DBModel> {
/** Adds a related parent record to this parent record */
public parent<P extends DBModel>(model: DBModelConstructor<P>, lookupProperty: keyof T, ...fields: (keyof P)[]): DBQueryParent<P> {
const parentLookup = new DBQueryParent(model, lookupProperty, fields);
const lookupInfo = getFieldInformation(model, lookupProperty as any);
const lookupInfo = Reflect.getMetadata(LOOKUP_INFO, this.req.model, lookupProperty as any) as DBLookupInformation;
if(lookupInfo == null) {
throw new Error(`Unable to find field information for field "${lookupProperty}" on the model ${this.req.model.name}`);
}
this.req.parents[lookupInfo.name] = parentLookup;
return parentLookup;
......
import { DBTable, DBModel, DBField, DBVarChar, DBChar, DBIdSequence } from "@swirl/db";
import { DBTable, DBModel, DBField, DBVarChar, DBChar, DBIdSequence, DBLookup } from "@swirl/db";
import { Organization } from "./org";
@DBTable('account', {
id: 'id'
......@@ -11,4 +12,7 @@ export class Account extends DBModel {
@DBField('name', new DBVarChar(80))
name: string;
@DBLookup('org', Organization)
org: Organization;
}
\ No newline at end of file
import { DBModel, DBTable, DBField, DBIdSequence, DBVarChar } from "@swirl/db";
@DBTable('organizations', {
id: 'id'
})
export class Organization extends DBModel {
@DBField('id', new DBIdSequence())
id: number;
@DBField('name', new DBVarChar(60))
name: string;
}
......@@ -3,13 +3,14 @@ import { Database } from "@swirl/db";
import { getTestAdapter } from "./db/adapter";
import { Account } from "./db/account";
import { Contact } from "./db/contact";
import { Organization } from "./db/org";
describe('DBQuery', function() {
let db: Database;
beforeEach('Setup the database', async () => {
db = new Database(getTestAdapter());
await db.register(Account).register(Contact).migrate();
await db.register(Account).register(Contact).register(Organization).migrate();
});
......@@ -47,12 +48,18 @@ describe('DBQuery', function() {
describe('selectParent()', function() {
let org: Organization;
let account: Account;
let contact1: Contact;
beforeEach('Setup test record', async () => {
org = new Organization();
org.name = 'Test Org';
await org.insert();
account = new Account();
account.name = 'Test Account';
account.org = org;
await account.insert();
contact1 = new Contact();
......@@ -72,6 +79,15 @@ describe('DBQuery', function() {
assert.equal(queried.account.name, 'Test Account');
});
it('Loads parents of parents', async () => {
const query = db.query(Contact);
query.parent(Account, 'account').parent(Organization, 'org');
const queried = await query.getOne();
assert(queried.account.org != null);
assert.equal(queried.account.org.name, 'Test Org');
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment