Skip to content
Merged
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
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@themost/pg",
"version": "2.12.0",
"version": "2.13.0",
"description": "MOST Web Framework PostgreSQL Adapter",
"main": "dist/index.js",
"module": "dist/index.esm.js",
Expand Down Expand Up @@ -31,6 +31,7 @@
"homepage": "https://github.com/themost-framework/pg",
"dependencies": {
"@themost/events": "^1.5.0",
"lodash": "^4.17.21",
"pg": "^8.7.3",
"sprintf-js": "^1.1.2"
},
Expand Down
137 changes: 117 additions & 20 deletions spec/QueryExpression.selectJson.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { PostgreSQLFormatter } from '@themost/pg';
import SimpleOrderSchema from './config/models/SimpleOrder.json';
import {TestApplication} from './TestApplication';
import { TraceUtils } from '@themost/common';
import { DataPermissionEventListener } from '@themost/data';
import { DataPermissionEventListener, executeInUnattendedMode, executeInUnattendedModeAsync } from '@themost/data';
import { promisify } from 'util';
const beforeExecuteAsync = promisify(DataPermissionEventListener.prototype.beforeExecute);

Expand All @@ -17,7 +17,9 @@ async function createSimpleOrders(db) {
const { source } = SimpleOrderSchema;
const exists = await db.table(source).existsAsync();
if (!exists) {
await db.table(source).createAsync(SimpleOrderSchema.fields);
await db.table(source).createAsync(SimpleOrderSchema.fields);
} else {
return;
}
// get some orders
const orders = await db.executeAsync(
Expand Down Expand Up @@ -61,7 +63,19 @@ async function createSimpleOrders(db) {
return {id, streetAddress, postalCode, addressLocality, addressCountry, telephone };
}), []
);
// get

const shuffleArray = (array) => {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
};

const getRandomItems = (array, numItems) => {
const shuffledArray = shuffleArray([...array]);
return shuffledArray.slice(0, numItems);
};
const items = orders.map((order) => {
const { orderDate, discount, discountCode, orderNumber, paymentDue,
dateCreated, dateModified, createdBy, modifiedBy } = order;
Expand All @@ -73,6 +87,8 @@ async function createSimpleOrders(db) {
customer.address = postalAddresses.find((x) => x.id === customer.address);
delete customer.address?.id;
}
// get 2 random payment methods
const additionalPaymentMethods = getRandomItems(paymentMethods, 2);
return {
orderDate,
discount,
Expand All @@ -82,6 +98,7 @@ async function createSimpleOrders(db) {
orderStatus,
orderedItem,
paymentMethod,
additionalPaymentMethods,
customer,
dateCreated,
dateModified,
Expand Down Expand Up @@ -599,26 +616,106 @@ describe('SqlFormatter', () => {
expect(item.customer).toBeInstanceOf(Object);
expect(item.orderedItem).toBeInstanceOf(Object);
}
});


it('should return json arrays', async () => {
// set context user
context.user = {
name: 'alexis.rees@example.com'
};
await executeInUnattendedModeAsync(context, async () => {
const user = await context.model('User').where('name').equal(context.interactiveUser.name).getItem();
user.groups = [
{ name: 'Administrators' },
{ name: 'Users' }
];
await context.model('User').save(user);
});
const queryPeople = context.model('Person').asQueryable().select(
'id', 'familyName', 'givenName', 'jobTitle', 'email'
).flatten();
await beforeExecuteAsync({
model: queryPeople.model,
emitter: queryPeople,
query: queryPeople.query,
});
const { viewAdapter: People } = queryPeople.model;
const queryOrders = context.model('Order').asQueryable().select(
'id', 'orderDate', 'orderStatus', 'orderedItem', 'customer'
).flatten();
const { viewAdapter: Orders } = queryOrders.model;
// prepare query for each customer
queryOrders.query.where(
new QueryField('customer').from(Orders)
).equal(
new QueryField('id').from(People)
);
const selectPeople = queryPeople.query.$select[People];
// add orders as json array
selectPeople.push({
orders: {
$jsonArray: [
queryOrders.query
]
}
});
const start= new Date().getTime();
const items = await queryPeople.take(50).getItems();
const end = new Date().getTime();
TraceUtils.log('Elapsed time: ' + (end-start) + 'ms');
expect(items.length).toBeTruthy();
for (const item of items) {
expect(Array.isArray(item.orders)).toBeTruthy();
for (const order of item.orders) {
expect(order.customer).toEqual(item.id);
}

}
});

/**
* todo: test sql with json_build_object
*
SELECT "OrderData"."id", "OrderData"."orderDate",
"OrderData"."orderStatus", "customer"."json" as "customer"
FROM "OrderData"
INNER JOIN (
SELECT "PersonData"."id" as "id", json_build_object('id',"PersonData"."id",
'givenName',"PersonData"."givenName",
'familyName',"PersonData"."familyName") as "json"
FROM "PersonData"
) as "customer" ON "OrderData"."customer" = "customer"."id"
LIMIT 10
*/

it('should parse string as json array', async () => {
// set context user
context.user = {
name: 'alexis.rees@example.com'
};
const { viewAdapter: People } = context.model('Person');
const query = new QueryExpression().select(
'id', 'familyName', 'givenName', 'jobTitle', 'email',
new QueryField({
tags: {
$jsonArray: [
new QueryField({
$value: '[ "user", "customer", "admin" ]'
})
]
}
})
).from(People).where('email').equal(context.user.name);
const [item] = await context.db.executeAsync(query);
expect(item).toBeTruthy();
});

it('should parse array as json array', async () => {
// set context user
context.user = {
name: 'alexis.rees@example.com'
};
const { viewAdapter: People } = context.model('Person');
const query = new QueryExpression().select(
'id', 'familyName', 'givenName', 'jobTitle', 'email',
new QueryField({
tags: {
$jsonArray: [
{
$value: [ 'user', 'customer', 'admin' ]
}
]
}
})
).from(People).where('email').equal(context.user.name);
const [item] = await context.db.executeAsync(query);
expect(item).toBeTruthy();
expect(Array.isArray(item.tags)).toBeTruthy();
expect(item.tags).toEqual([ 'user', 'customer', 'admin' ]);
});
});
9 changes: 8 additions & 1 deletion spec/config/models/SimpleOrder.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@
"type": "Integer",
"calculation": "javascript:return this.user();",
"readonly": true
},
{
"name": "additionalPaymentMethods",
"type": "Json",
"additionalType": "PaymentMethod",
"expandable": true,
"many": true
}
],
"views": [
Expand Down Expand Up @@ -204,4 +211,4 @@
"filter": "customer/user eq me()"
}
]
}
}
Binary file modified spec/db/local.db
Binary file not shown.
2 changes: 2 additions & 0 deletions src/PostgreSQLAdapter.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DataAdapterBase, DataAdapterBaseHelper, DataAdapterDatabase, DataAdapterIndexes, DataAdapterMigration, DataAdapterTable, DataAdapterView } from '@themost/common';
import { SqlFormatter } from '@themost/query';

export declare interface DataAdapterTables {
list(callback: (err: Error, result: { name: string, owner?: string, schema?: string }[]) => void): void;
Expand Down Expand Up @@ -37,4 +38,5 @@ export declare class PostgreSQLAdapter implements DataAdapterBase, DataAdapterBa
createView(name: string, query: any, callback: (err: Error) => void): void;
tables(): DataAdapterTables;
views(): DataAdapterViews;
getFormatter(): SqlFormatter;
}
26 changes: 15 additions & 11 deletions src/PostgreSQLAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class PostgreSQLAdapter {
}
else {
//format query expression or any object that may act as query expression
const formatter = new PostgreSQLFormatter();
const formatter = this.getFormatter();
sql = formatter.format(query);
}
//validate sql statement
Expand Down Expand Up @@ -553,7 +553,7 @@ class PostgreSQLAdapter {
}

refreshView(name, query, callback) {
const formatter = new PostgreSQLFormatter();
const formatter = this.getFormatter();
this.execute('REFRESH MATERIALIZED VIEW ' + formatter.escapeName(name), null, function (err) {
callback(err);
});
Expand Down Expand Up @@ -694,7 +694,7 @@ class PostgreSQLAdapter {
if (fields.length === 0) {
return callback(new Error('Invalid argument. Fields collection cannot be empty.'));
}
const formatter = new PostgreSQLFormatter();
const formatter = self.getFormatter();
let strFields = fields.filter((x) => {
return !x.oneToMany;
}).map((field) => {
Expand All @@ -712,7 +712,7 @@ class PostgreSQLAdapter {
strFields += ', ';
strFields += sprintf('PRIMARY KEY(%s)', strPKFields);
}
const escapedTable = new PostgreSQLFormatter().escapeName(name);
const escapedTable = self.getFormatter().escapeName(name);
const sql = sprintf('CREATE TABLE %s (%s)', escapedTable, strFields);
self.execute(sql, null, function (err) {
callback(err);
Expand Down Expand Up @@ -745,8 +745,8 @@ class PostgreSQLAdapter {
return callback();
}
// generate SQL statement
const formatter = new PostgreSQLFormatter();
const escapedTable = new PostgreSQLFormatter().escapeName(name);
const formatter = self.getFormatter();
const escapedTable = formatter.escapeName(name);
const sql = fields.map((field) => {
const escapedField = formatter.escapeName(field.name);
return sprintf('ALTER TABLE %s ADD COLUMN %s %s', escapedTable, escapedField, self.formatType(field));
Expand Down Expand Up @@ -782,7 +782,7 @@ class PostgreSQLAdapter {
return callback();
}
//generate SQL statement
const formatter = new PostgreSQLFormatter();
const formatter = self.getFormatter();
const escapedTable = formatter.escapeName(name);
let sql = fields.map((field) => {
const escapedType = self.formatType(field, '%t');
Expand Down Expand Up @@ -872,7 +872,7 @@ class PostgreSQLAdapter {
}
const exists = (result[0].count > 0);
if (exists) {
const formatter = new PostgreSQLFormatter();
const formatter = self.getFormatter();
const sql = sprintf('DROP VIEW %s', formatter.escapeName(name));
return self.execute(sql, [], function (err) {
if (err) {
Expand Down Expand Up @@ -907,7 +907,7 @@ class PostgreSQLAdapter {
return transcactionCallback(err);
}
try {
const formatter = new PostgreSQLFormatter();
const formatter = self.getFormatter();
const sql = sprintf('CREATE VIEW %s AS ', formatter.escapeName(name)) + formatter.format(q);
return self.execute(sql, [], (err) => {
return transcactionCallback(err);
Expand Down Expand Up @@ -1072,7 +1072,7 @@ class PostgreSQLAdapter {
//get table name
table = matches[2];
}
const formatter = new PostgreSQLFormatter();
const formatter = self.getFormatter();
return {
list: function (callback) {
const this1 = this;
Expand Down Expand Up @@ -1291,7 +1291,7 @@ class PostgreSQLAdapter {
});
},
create: function (callback) {
const formatter = new PostgreSQLFormatter();
const formatter = self.getFormatter();
return self.execute(`CREATE DATABASE ${formatter.escapeName(name)};`, [], (err, results) => {
if (err) {
return callback();
Expand Down Expand Up @@ -1365,6 +1365,10 @@ FROM "pg_catalog"."pg_views" WHERE "schemaname" <> 'pg_catalog' AND "schemaname"
}
}

getFormatter() {
return new PostgreSQLFormatter();
}

}

export {
Expand Down
Loading