Skip to content

Commit

Permalink
use json object (#95)
Browse files Browse the repository at this point in the history
* use json object

* finalize json test

* 2.14.0

* simplify abstract method error

* update return type
  • Loading branch information
kbarbounakis authored Aug 5, 2024
1 parent ce86c33 commit 9f75dd9
Show file tree
Hide file tree
Showing 10 changed files with 592 additions and 17 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@themost/query",
"version": "2.13.1",
"version": "2.14.0",
"description": "MOST Web Framework Codename ZeroGravity - Query Module",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
Expand Down
276 changes: 276 additions & 0 deletions spec/QueryExpression.selectJson.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
// noinspection SpellCheckingInspection

import {MemberExpression, MethodCallExpression} from '../src/index';
import { QueryEntity, QueryExpression } from '../src/index';
import { SqliteFormatter } from '@themost/sqlite';
import { MemoryAdapter } from './test/TestMemoryAdapter';
import { MemoryFormatter } from './test/TestMemoryFormatter';
import SimpleOrderSchema from './test/config/models/SimpleOrder.json';

if (typeof SqliteFormatter.prototype.$jsonGet !== 'function') {
SqliteFormatter.prototype.$jsonGet = function(expr) {
if (typeof expr.$name !== 'string') {
throw new Error('Invalid json expression. Expected a string');
}
const parts = expr.$name.split('.');
const extract = this.escapeName(parts.splice(0, 2).join('.'));
return `json_extract(${extract}, '$.${parts.join('.')}')`;
};
SqliteFormatter.prototype.$jsonArray = function(expr) {
return `json_each(${this.escapeName(expr)})`;
}
// const superEscape = SqlUtils.escape;
// SqlUtils.escape = function(value) {
// if (isObjectDeep(value)) {
// return `'${JSON.stringify(value)}'`;
// } else {
// const args = Array.from(arguments)
// return superEscape.apply(null, args);
// }
// }
}

/**
* @param { MemoryAdapter } db
* @returns {Promise<void>}
*/
async function createSimpleOrders(db) {
const { source } = SimpleOrderSchema;
const exists = await db.table(source).existsAsync();
if (!exists) {
await db.table(source).createAsync(SimpleOrderSchema.fields);
}
// get some orders
const orders = await db.executeAsync(
new QueryExpression().from('OrderBase').select(
({orderDate, discount, discountCode, orderNumber, paymentDue,
dateCreated, dateModified, createdBy, modifiedBy,
orderStatus, orderedItem, paymentMethod, customer}) => {
return { orderDate, discount, discountCode, orderNumber, paymentDue,
dateCreated, dateModified, createdBy, modifiedBy,
orderStatus, orderedItem, paymentMethod, customer};
})
.orderByDescending((x) => x.orderDate).take(10), []
);
const paymentMethods = await db.executeAsync(
new QueryExpression().from('PaymentMethodBase').select(
({id, name, alternateName, description}) => {
return { id, name, alternateName, description };
}), []
);
const orderStatusTypes = await db.executeAsync(
new QueryExpression().from('OrderStatusTypeBase').select(
({id, name, alternateName, description}) => {
return { id, name, alternateName, description };
}), []
);
const orderedItems = await db.executeAsync(
new QueryExpression().from('ProductData').select(
({id, name, category, model, releaseDate, price}) => {
return { id, name, category, model, releaseDate, price };
}), []
);
const customers = await db.executeAsync(
new QueryExpression().from('PersonData').select(
({id, familyName, givenName, jobTitle, email, description, address}) => {
return { id, familyName, givenName, jobTitle, email, description, address };
}), []
);
const postalAddresses = await db.executeAsync(
new QueryExpression().from('PostalAddressData').select(
({id, streetAddress, postalCode, addressLocality, addressCountry, telephone}) => {
return {id, streetAddress, postalCode, addressLocality, addressCountry, telephone };
}), []
);
// get
const items = orders.map((order) => {
const { orderDate, discount, discountCode, orderNumber, paymentDue,
dateCreated, dateModified, createdBy, modifiedBy } = order;
const orderStatus = orderStatusTypes.find((x) => x.id === order.orderStatus);
const orderedItem = orderedItems.find((x) => x.id === order.orderedItem);
const paymentMethod = paymentMethods.find((x) => x.id === order.paymentMethod);
const customer = customers.find((x) => x.id === order.customer);
if (customer) {
customer.address = postalAddresses.find((x) => x.id === customer.address);
delete customer.address?.id;
}
return {
orderDate,
discount,
discountCode,
orderNumber,
paymentDue,
orderStatus,
orderedItem,
paymentMethod,
customer,
dateCreated,
dateModified,
createdBy,
modifiedBy
}
});
for (const item of items) {
await db.executeAsync(new QueryExpression().insert(item).into(source), []);
}
}

function onResolvingJsonMember(event) {
let member = event.fullyQualifiedMember.split('.');
const field = SimpleOrderSchema.fields.find((x) => x.name === member[0]);
if (field == null) {
return;
}
if (field.type !== 'Json') {
return;
}
event.object = event.target.$collection;
event.member = new MethodCallExpression('jsonGet', [
new MemberExpression(event.target.$collection + '.' + event.fullyQualifiedMember)
]);
}

describe('SqlFormatter', () => {

/**
* @type {MemoryAdapter}
*/
let db;
beforeAll((done) => {
MemoryAdapter.create({
name: 'local',
database: './spec/db/local.db'
}).then((adapter) => {
db = adapter;
return done();
}).catch((err) => {
return done(err);
});
});
afterAll((done) => {
if (db) {
db.close(() => {
MemoryAdapter.drop(db).then(() => {
return done();
});
});
}
});

it('should select json field', async () => {
await createSimpleOrders(db);
const Orders = new QueryEntity('SimpleOrders');
const query = new QueryExpression();
query.resolvingJoinMember.subscribe(onResolvingJsonMember);
query.select((x) => {
// noinspection JSUnresolvedReference
return {
id: x.id,
customer: x.customer.description
}
})
.from(Orders);
const formatter = new MemoryFormatter();
const sql = formatter.format(query);
expect(sql).toEqual('SELECT `SimpleOrders`.`id` AS `id`, json_extract(`SimpleOrders`.`customer`, \'$.description\') AS `customer` FROM `SimpleOrders`');
/**
* @type {Array<{id: number, customer: string}>}
*/
const results = await db.executeAsync(sql, []);
expect(results).toBeTruthy();
for (const result of results) {
expect(result).toBeTruthy();
expect(result.id).toBeTruthy();
expect(result.customer).toBeTruthy();
}
});

it('should select nested json field', async () => {
await createSimpleOrders(db);
const Orders = new QueryEntity('SimpleOrders');
const query = new QueryExpression();
query.resolvingJoinMember.subscribe(onResolvingJsonMember);
query.select((x) => {
// noinspection JSUnresolvedReference
return {
id: x.id,
customer: x.customer.description,
address: x.customer.address.streetAddress
}
})
.from(Orders);
const formatter = new MemoryFormatter();
const sql = formatter.format(query);
expect(sql).toEqual('SELECT `SimpleOrders`.`id` AS `id`, ' +
'json_extract(`SimpleOrders`.`customer`, \'$.description\') AS `customer`, ' +
'json_extract(`SimpleOrders`.`customer`, \'$.address.streetAddress\') AS `address` ' +
'FROM `SimpleOrders`');
/**
* @type {Array<{id: number, customer: string}>}
*/
const results = await db.executeAsync(sql, []);
expect(results).toBeTruthy();
for (const result of results) {
expect(result).toBeTruthy();
expect(result.id).toBeTruthy();
expect(result.customer).toBeTruthy();
}
});

it('should select nested json field with method', async () => {
await createSimpleOrders(db);
const Orders = new QueryEntity('SimpleOrders');
const query = new QueryExpression();
query.resolvingJoinMember.subscribe(onResolvingJsonMember);
query.select((x) => {
// noinspection JSUnresolvedReference
return {
id: x.id,
customer: x.customer.description,
releaseYear: x.orderedItem.releaseDate.getFullYear()
}
})
.from(Orders);
const formatter = new MemoryFormatter();
const sql = formatter.format(query);
/**
* @type {Array<{id: number, customer: string, releaseYear: number}>}
*/
const results = await db.executeAsync(sql, []);
expect(results).toBeTruthy();
for (const result of results) {
expect(result).toBeTruthy();
expect(result.releaseYear).toBeTruthy();
}
});

it('should select json object', async () => {
await createSimpleOrders(db);
const Orders = new QueryEntity('SimpleOrders');
const query = new QueryExpression();
query.resolvingJoinMember.subscribe(onResolvingJsonMember);
query.select((x) => {
// noinspection JSUnresolvedReference
return {
id: x.id,
customer: x.customer,
orderedItem: x.orderedItem
}
})
.from(Orders);
const formatter = new MemoryFormatter();
const sql = formatter.format(query);
/**
* @type {Array<{id: number, customer: string, releaseYear: number}>}
*/
const results = await db.executeAsync(sql, []);
expect(results).toBeTruthy();
for (const result of results) {
if (typeof result.customer === 'string') {
const customer = JSON.parse(result.customer);
expect(customer).toBeTruthy();
}
}
});

});
4 changes: 2 additions & 2 deletions spec/QueryExpression.string.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ describe('QueryExpression.where', () => {
}
})
.from(Products)
.where(({name: productName}) => { // use object destructuting with name
.where(({name: productName}) => { // use object destructuring with name
return productName.indexOf('Intel') >= 0;
});
results = await db.executeAsync(query);
Expand Down Expand Up @@ -252,4 +252,4 @@ describe('QueryExpression.where', () => {
expect(item.givenName.concat(' ', item.familyName)).toBe(item.name);
});
});
});
});
40 changes: 40 additions & 0 deletions spec/is-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved
const {isPlainObject, isObjectLike, isNative} = require('lodash');

const objectToString = Function.prototype.toString.call(Object);

function isObjectDeep(any) {
// check if it is a plain object
let result = isPlainObject(any);
if (result) {
return result;
}
// check if it's object
if (isObjectLike(any) === false) {
return false;
}
// get prototype
let proto = Object.getPrototypeOf(any);
// if prototype exists, try to validate prototype recursively
while(proto != null) {
// get constructor
const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor')
&& proto.constructor;
// check if constructor is native object constructor
result = (typeof Ctor == 'function') && (Ctor instanceof Ctor)
&& Function.prototype.toString.call(Ctor) === objectToString;
// if constructor is not object constructor and belongs to a native class
if (result === false && isNative(Ctor) === true) {
// return false
return false;
}
// otherwise. get parent prototype and continue
proto = Object.getPrototypeOf(proto);
}
// finally, return result
return result;
}

module.exports = {
isObjectDeep
}
Loading

0 comments on commit 9f75dd9

Please sign in to comment.