Commit fdd6d2ed authored by William Naslund's avatar William Naslund

Added non-aliased loading without parsing

parent 416ca3a5
# Node.js
/node_modules/
yarn-error.log
# TypeScript
/dist/
......
......@@ -12,7 +12,8 @@
"@swirl/db": "./dist/module"
},
"dependencies": {
"pg": "^7.8.0"
"pg": "^7.8.0",
"pg-cursor": "^2.0.0"
},
"devDependencies": {
"@types/mocha": "^5.2.5",
......
......@@ -71,4 +71,19 @@ export interface DatabaseAdapter {
/** Inserts a record into a table */
insert<T extends Model>(record: T): Promise<string | number>;
/** Gets a cursor for a query */
getCursor(sql: string, args: any[]): Promise<QueryCursor>;
}
/** A cursor for rows of data */
export interface QueryCursor {
available(): Promise<boolean>;
next(): Promise<any>;
close(): Promise<void>;
}
import { DatabaseAdapter } from "../adapter";
import { DatabaseAdapter, QueryCursor } from "../adapter";
import { Pool, PoolClient } from "pg";
import { TableInfo, ColumnInfo, getColumnInformation, getTableInformation, getPrimaryKey } from "../decorators";
import { ConstraintInfo } from "../decorators/constraint";
import { Model } from "../model";
import * as Cursor from "pg-cursor";
/** Connects to a PostgreSQL database */
export class PostgresAdapter implements DatabaseAdapter {
......@@ -365,6 +366,65 @@ export class PostgresAdapter implements DatabaseAdapter {
return res.rows[0][id];
}
// Cursor Interface
async getCursor(sql: string, args: any[]): Promise<QueryCursor> {
const client = await this.pool.connect();
const pgCursor = client.query(new Cursor(sql, args));
return new PostgresCursor(client, pgCursor);
}
}
/** A cursor for rows from a Postgres database */
export class PostgresCursor implements QueryCursor {
constructor(
private readonly client: PoolClient,
private readonly cursor: Cursor
) { }
private currentList: any[] = null;
public async available(): Promise<boolean> {
if(this.currentList == null || this.currentList.length < 1) {
await new Promise((resolve, reject) => {
this.cursor.read(50, (err, rows) => {
if(err) {
reject(err);
} else {
this.currentList = rows || [];
resolve();
}
});
});
}
return this.currentList.length > 0;
}
public async next(): Promise<any> {
if(await this.available() != true) {
return null;
}
return this.currentList.shift();
}
public async close() {
try {
await new Promise((resolve, reject) => {
this.cursor.close(err => {
if(err) reject(err);
else resolve();
});
});
} finally {
this.client.release();
}
}
}
......
import * as crypo from "crypto";
import { DatabaseAdapter } from "./adapter";
import { ModelConstructor } from "./model";
import { getTableInformation, getColumnInformation, getConstraintInfo, getColumnConstraintInfo } from "./decorators";
import * as crypo from "crypto";
import { QueryResult } from "./result";
/** Maps models to the database they belong to */
const MODEL_DATABASES = new Map<Function, Database>();
......@@ -116,6 +117,12 @@ export class Database {
return await this.config.adapter.runQuery<T>(sql, args);
}
/** Loads models using a SQL query */
public async load(sql: string, args?: any[]): Promise<QueryResult> {
const cursor = await this.config.adapter.getCursor(sql, args);
return new QueryResult(cursor);
}
/** Returns the database for a model */
public static getDatabase(constructor: Function): Database {
......
......@@ -23,6 +23,10 @@ export function Table<T extends ModelConstructor>(tableName: string) {
const extendedConstructor = class extends constructor {
};
Object.defineProperty(extendedConstructor, 'name', {
value: constructor.name,
writable: false
});
TABLE_REGISTRY.set(extendedConstructor, {
name: tableName
......
......@@ -6,6 +6,7 @@
export * from "./database";
export * from "./model";
export * from "./result";
export * from "./adapters/index";
export * from "./decorators/index";
......@@ -25,8 +25,8 @@ export interface Model {
/** A constructor for a model */
export interface ModelConstructor {
export interface ModelConstructor<T extends Model = any> {
new(...args: any[]): Model;
new(...args: any[]): T;
}
import { Model, ModelConstructor } from "./model";
import { getColumnInformation } from "./decorators";
import { QueryCursor } from "./adapter";
export class QueryResult {
constructor(
private readonly cursor: QueryCursor
) { }
public async available(): Promise<boolean> {
return await this.cursor.available();
}
public async next<T extends Model>(constructor: ModelConstructor<T>): Promise<T> {
const rowData = await this.cursor.next();
const record = new constructor();
for(const column of getColumnInformation(constructor) || []) {
record[String(column.propertyKey)] = rowData[column.name];
}
return record;
}
public async all<T extends Model>(constructor: ModelConstructor<T>): Promise<T[]> {
const allRows: T[] = [];
while(await this.available()) {
allRows.push(await this.next(constructor));
}
return allRows;
}
public async close() {
await this.cursor.close();
}
}
import * as assert from "assert";
import { Database } from "@swirl/db";
import { Database, QueryResult } from "@swirl/db";
import { TEST_ADAPTER } from "./db/adapter";
import { Contact } from "./db/contact";
......@@ -40,4 +40,37 @@ describe('Database', function() {
});
describe('load', function() {
beforeEach('Setup the database', async function() {
await db.setup();
});
it('Parses single table queries', async function() {
const con = new Contact();
con.firstName = 'Tommy 2';
await con.save();
let res: QueryResult;
try {
res = await db.load(`
SELECT *
FROM contacts
`);
const loaded = await res.all(Contact);
assert(loaded.length == 1);
const loadedCon = loaded[0];
assert(loadedCon instanceof Contact);
assert(loadedCon.firstName == 'Tommy 2');
assert(loadedCon.id == con.id && loadedCon.id != null);
} finally {
if(res) await res.close();
}
});
});
});
......@@ -522,6 +522,11 @@ pg-connection-string@0.1.3:
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7"
integrity sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=
pg-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pg-cursor/-/pg-cursor-2.0.0.tgz#1e11532613d2d4c61057a5705a1536b1052d1698"
integrity sha512-/gYHadqLurektHk6HXiL0hSrn+RZfowkLr+ftC0lLoLBlIm8JIdk9f9g71EEjK63XxqhFqcykHuxQLFzSeyzdQ==
pg-int8@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
......
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