Skip to content

Commit

Permalink
0.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
kimung committed Mar 15, 2019
1 parent 6c9d114 commit c436821
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 30 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## 0.6.0 - 2019-03-15
- add toIterator method to EntityCollection
- add orderBy and orderByDesc to queryable

## 0.5.4 - 2019-03-15
### Changed
- remove alias on DELETE
Expand Down
9 changes: 6 additions & 3 deletions lib/queryable.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ class Queryable {
leftJoin (queryable, predicate) {
return this._method('join', queryable.expression, predicate, 'LEFT')
}
orderBy (selector) {
return this._method('orderBy', selector)
}
groupBy (selector) {
return this._method('groupBy', selector)
}
Expand All @@ -59,6 +56,12 @@ class Queryable {
distinct (count) {
return this._method('distinct', true)
}
orderBy (selector) {
return this._method('orderBy', selector, 'ASC')
}
orderByDesc (selector) {
return this._method('orderBy', selector, 'DESC')
}
_method (type, ...args) {
let expression = new MethodExpression(
this.expression,
Expand Down
1 change: 1 addition & 0 deletions lib/sql/expression/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class SelectExpression extends Expression {
this.selectors = []
this.where = []
this.groups = []
this.orders = []
this.distinct = false
}
}
Expand Down
17 changes: 17 additions & 0 deletions lib/sql/schema/entity/iterable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class EntityCollection {
constructor (Type, coll) {
this.Type = Type
this.coll = coll || []
}
* [Symbol.iterator] () {
for (let i = 0; i < this.coll.length; i++) {
yield new this.Type(this.coll[i])
}
}
}

module.exports = {
ofType: (Type) => ({
create: coll => new EntityCollection(Type, coll)
})
}
50 changes: 50 additions & 0 deletions lib/sql/schema/entity/queryable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* MIT License
*
* Copyright (c) 2016 - 2018 RDUK <tech@rduk.fr>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

'use strict'

const Queryable = require('../../../queryable')
const EntityCollection = require('./iterable')

class EntityQueryable extends Queryable {
constructor (expression, Entity, mapping) {
super(expression)
this.Entity = Entity
this.mapping = mapping
}
_method (type, ...args) {
let q = super._method(type, ...args)
return new EntityQueryable(q.expression, this.Entity, this.mapping)
}
toArray (context) {
return super.toArray(context)
.then(items => items.map(item => new this.Entity(item)))
}
toIterator (context) {
return super.toArray(context)
.then(items => EntityCollection.ofType(this.Entity).create(items))
}
}

module.exports = EntityQueryable
23 changes: 19 additions & 4 deletions lib/sql/schema/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@

const mix = require('mixwith').mix

const Queryable = require('../../queryable')
const EntityQueryable = require('./entity/queryable')

const FieldExpression = require('@rduk/expression/lib/parser/expression/field')
const LambdaExpression = require('@rduk/expression/lib/parser/expression/lambda')
const NameExpression = require('@rduk/expression/lib/parser/expression/name')
const ObjectLiteralExpression = require('@rduk/expression/lib/parser/expression/object')
const PropertyExpression = require('@rduk/expression/lib/parser/expression/property')
const SelectExpression = require('../expression/select')
const SourceExpression = require('../../expression/source')

const data = require('../../')
Expand Down Expand Up @@ -63,7 +70,15 @@ class EntityFactory {
})
}
static query () {
return new Queryable(new SourceExpression(info.table))
let expression = new SelectExpression(new SourceExpression(info.table))

expression.selectors = [
new LambdaExpression(new ObjectLiteralExpression(Object.keys(mapping).map(k => (
new FieldExpression(k, new PropertyExpression(new NameExpression(name), mapping[k]))
))), [new NameExpression(name)])
]

return new EntityQueryable(expression, Entity, mapping)
}
}

Expand All @@ -76,8 +91,8 @@ class EntityFactory {
}

class Schema {
constructor (definition, InsertExpression, UpdateExpression) {
let factory = new EntityFactory(InsertExpression, UpdateExpression)
constructor (definition) {
let factory = new EntityFactory()
Object.keys(definition.entities).forEach(entityName => {
let entity = factory.create(entityName, definition.entities[entityName])
Object.defineProperty(this, entityName, {
Expand Down
14 changes: 13 additions & 1 deletion lib/sql/translator/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ class SelectTranslator extends BaseTranslator {
let joins = this.translateJoins(context)
let where = this.translateWhere(context)
let group = this.translateGroups(context)
let order = this.translateOrders(context)
let limit = this.translateKey('limit', context)
let offset = this.translateKey('offset', context)
return `SELECT${distinct} ${columns} FROM ${from} AS t0 ${joins} WHERE ${where}${group}${limit}${offset}`
return `SELECT${distinct} ${columns} FROM ${from} AS t0 ${joins} WHERE ${where}${group}${order}${limit}${offset}`
}
translateDistinct () {
return this.expression.distinct ? ' DISTINCT' : ''
Expand Down Expand Up @@ -98,6 +99,17 @@ class SelectTranslator extends BaseTranslator {

return ` GROUP BY ${group}`
}
translateOrders (context) {
if (!this.expression.orders.length) {
return ''
}

let columns = this.expression.orders.map(([selector, order]) => {
return `${this.provider.translator.translate(selector)} ${order || 'ASC'}`
})

return ` ORDER BY ${columns.join(', ')}`
}
translateKey (key, context) {
if (!this.expression[key]) {
return ''
Expand Down
11 changes: 11 additions & 0 deletions lib/sql/visitor/method.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ const methods = {
}
expression.groups.push(selector)
return expression
},
orderBy: function (source, selector, order) {
let expression = this.visitor.visit(source)
if (!Array.isArray(selector.args)) {
selector.args = [selector.args]
}
console.log(selector, order)
expression.orders.push([selector, order])
return expression
}
}

Expand All @@ -69,6 +78,8 @@ class MethodExpressionVisitor extends BaseVisitor {
case 'filter':
case 'groupBy':
return methods[expression.name].call(this.provider, expression.context, expression.args[0])
case 'orderBy':
return methods[expression.name].call(this.provider, expression.context, expression.args[0], expression.args[1])
case 'offset':
case 'limit':
case 'distinct':
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"main": "lib/index.js",
"scripts": {
"pretest": "standard --fix && cp ./spec/resources/*yml .",
"test": "istanbul cover jasmine --report cobertura",
"test": "RDUK_LOG_LEVEL=info istanbul cover jasmine --report cobertura",
"posttest": "rm ./*yml",
"report": "istanbul report",
"coveralls": "npm run report && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
Expand Down
18 changes: 4 additions & 14 deletions spec/dataSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ describe('data', function () {
const QueryProvider = require('../lib/query/default')
const Visitor = require('../lib/sql/visitor/expression')
const Translator = require('../lib/sql/translator/expression')
const errors = require('@rduk/errors')

let users
let profiles
Expand All @@ -61,15 +60,16 @@ describe('data', function () {
it('sql generation should success', function () {
let provider = new QueryProvider(Visitor, Translator, () => ({}))

let q0 = users.skip(20).take(10)
let q0 = users.skip(20).take(10).orderBy(u => u.id).orderByDesc(u => u.age)
let cmd0 = provider.getCommand(q0.expression, {})
expect(cmd0).toBe('SELECT * FROM user AS t0 WHERE true LIMIT 10 OFFSET 20')
expect(cmd0).toBe('SELECT * FROM user AS t0 WHERE true ORDER BY t0.id ASC, t0.age DESC LIMIT 10 OFFSET 20')

let q1 = users
.filter(u => u.email.toLowerCase() === this.email)
.filter(u => (u.age > 25 && u.age <= 30) || (u.age >= 61 && u.age < 66))
.orderByDesc(u => u.id)
let cmd1 = provider.getCommand(q1.expression, {email: 'j.doe@mail.test'})
expect(cmd1).toBe('SELECT * FROM user AS t0 WHERE ((true AND (LOWER(t0.email) = ?<email>)) AND (((t0.age > 25) AND (t0.age <= 30)) OR ((t0.age >= 61) AND (t0.age < 66))))')
expect(cmd1).toBe('SELECT * FROM user AS t0 WHERE ((true AND (LOWER(t0.email) = ?<email>)) AND (((t0.age > 25) AND (t0.age <= 30)) OR ((t0.age >= 61) AND (t0.age < 66)))) ORDER BY t0.id DESC')

let q2 = users
.filter(u => u.email.toLowerCase() === this.email)
Expand Down Expand Up @@ -178,16 +178,6 @@ describe('data', function () {
done()
})
})

it('queryable orderBy should throw a NotSupportedError', function () {
expect(function () {
users
.filter(u => (u.age > 25 && u.age <= 30) || (u.age >= 61 && u.age < 66))
.filter(u => u.email.endsWith('@test.com'))
.orderBy(u => (u.id))
.toArray({email: 'j.doe@mail.test'})
}).toThrowError(errors.NotSupportedError)
})
})

describe('queryable first', function () {
Expand Down
9 changes: 8 additions & 1 deletion spec/helpers/mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@ const BaseDataProvider = require('../../lib/base')

class MockDataProvider extends BaseDataProvider {
execute (command, parameters) {
return Promise.resolve([{id: 1, fk1: 1, fk2: 1}])
return Promise.resolve([{
id: 1,
fk1: 1,
fk2: 1,
email: 'john.doe@example.com',
firstName: 'John',
lastName: 'Doe'
}])
}
}

Expand Down
41 changes: 41 additions & 0 deletions spec/sql/collectionSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* MIT License
*
* Copyright (c) 2016 - 2018 RDUK <tech@rduk.fr>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

/* eslint-env jasmine */

'use strict'

const EntityCollection = require('../../lib/sql/schema/entity/iterable')

describe('EntityCollection', function () {
const entities = EntityCollection.ofType(Object).create()
describe('create', function () {
describe('with null coll', function () {
it('should create an empty coll', function () {
expect(Array.isArray(entities.coll)).toBe(true)
expect(entities.coll.length).toBe(0)
})
})
})
})
35 changes: 29 additions & 6 deletions spec/sql/selectSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,37 @@

describe('select from schema generation', function () {
it('should success', function (done) {
const QueryProvider = require('../../lib/query/default')
const Visitor = require('../../lib/sql/visitor/expression')
const Translator = require('../../lib/sql/translator/expression')
const Schema = require('../../lib/sql/schema')
const db = new Schema(require('../resources/db.json'))

db.User.query()
.toArray()
.then(users => {
expect(users).toBeDefined()
done()
})
let provider = new QueryProvider(Visitor, Translator, () => ({}))
let query = db.User.query().skip(10).take(10)

let cmd = provider.getCommand(query.expression, {})
expect(cmd).toBe('SELECT t0.id AS id, t0.email AS email, t0.firstName AS first_name, t0.lastName AS last_name FROM db_users AS t0 WHERE true LIMIT 10 OFFSET 10')

Promise.all([
query
.toIterator()
.then(users => {
for (let user of users) {
expect(user.constructor.name).toBe('User')
}
}),
query
.toArray()
.then(users => {
expect(users.length).toBe(1)
expect(users[0].email).toBe('john.doe@example.com')
users.forEach(user => {
expect(user.constructor.name).toBe('User')
let json = user.toJSON()
expect(json).toBeDefined()
})
})
]).then(() => done())
})
})

0 comments on commit c436821

Please sign in to comment.