A Node and AngularJS service for RDBMS join operations
npm install angular-joinangular-join
============
[![Build Status][travis-img]][travis]
[![Bower][bower-img]][bower]
[![NPM][npm-img]][npm]
[![License][license-img]](LICENSE)
A Node & AngularJS service providing RDBMS functionality for JavaScript arrays.
[Demo/Documentation][docs]
Installation
------------
If you're using Bower:
```
bower install angular-join --save
Angular JOIN does not have any dependencies beside Angular itself.
Include the angular-join.js script in your HTML:
`html`
Add 'angular-join' as a dependency in your Angular app:
`javascript`
angular.module('myApp', ['angular-join']);
Angular JOIN can also be installed via NPM:
``
npm install angular-join --save
The NPM module uses the [Q module by kriskowal][kriskowal-q].
Usage
-----
This module provides the Join service/module, which exposes functions to
create SQL-like queries on JavaScript arrays.
Inject Join in your Angular modules as needed. For example:
`javascript
angular.module('myApp')
.controller('MyCtrl', ['$scope', 'Join', function($scope, Join) {
// Use the Join module to build queries
var query = Join
.selectFrom(...)
.where(...)
.hashJoin(...)
.hashGroupBy(...)
.having(...)
.orderBy(...)
.limit(...);
var results = query.execute();
}]);
`
In your Node application, require('angular-join') will return an object
containing the functions below:
`javascript
var Join = require('angular-join');
// Use the Join module to build queries
var query = Join
.selectFrom(...)
.where(...)
.hashJoin(...)
.hashGroupBy(...)
.having(...)
.orderBy(...)
.limit(...);
var results = query.execute();
`
The Join service/module is mainly used to create new query objects viaJoin.selectFrom(...), but most functions below can also be called statically.
The Query Object
----------------
The query object represents an SQL-like query constructed using the API
described below. A query is constructed with a starting array and using
method-chaining to queue operations that will transform that array and/or join
it to other arrays. Each call will perform the requested operation on the
results of the previous operation; this allows a query to be incrementally
constructed using a [fluent interface][].
New queries are constructed using the Join.selectFrom function, which takes
an array as the starting data on which to perform subsequent operations.
Neither the starting array nor any other input arrays are ever modified;
all operations create a new array to be returned or passed in as the input
of the next operation.
None of the operations in the query are performed until the execute{async: true}
function is called, which returns the array resulting from sequentially running
all the operations queued on the query, which allows them to be constructed in
one place and executed in another. If called with the option,
a [promise object][] is returned instead, which is resolved with the final
array.
Query objects are instances of JoinQuery and each operation (with theexecute
exception of ) results in the creation of a new object. The first timeexecute
that is called, the results of the query (and all queries on whichexecute
this one is dependent) are cached, and subsequent calls return the cached
results instead of recalculating them. This results in more efficient queries,
especially when one query uses another as its starting point. However, if inputs
change in between calls to , the cached results may become stale andexecute should be called with the {force: true} option.
`sql`
SELECT ... FROM input ...
Returns a new query object that uses input as its starting array.
Note: .selectFrom() is a function of the Join service, and not of a
query object.
This is usually the first in a chain of function calls to create a more
complex query. If the optional callback is included, then subsequentcallback
operations are applied to a new array whose elements are the return values of applied to each element of input. Providing the callback worksJoin.selectFrom(input).select(callback)
identically to calling instead.
#### Syntax
`javascript`
Join
.selectFrom(input[, callback])
#### Arguments
- input (array/JoinQuery)callback
- If an array, the input array to start the query.
- If a JoinQuery, the result of that query is used to start this query.
- (function/string/array)callback(e)
- Optional
- Function executed as , where e is an element of input.input
This is used to transform the elements (e.g. by selecting only ainput
subset of their properties) before other operations are chained to the
query, and works identically to [Array.prototype.map()][].
- If a string is passed in, then only that property of the elements of input
is included in the resulting array.
- If an array of property names is passed in, then only those properties of
the elements of are included in the resulting array.callback
- Unlike the SQL SELECT command, which specifies the fields to return at the
end of a query, the only specifies the array elements to provide.select
to the next operation, meaning if you want SQL-like behaviour, call
as the last operation in the query.Join.selectFrom(input, callback)
- is equivalent toJoin.selectFrom(input).select(callback)
.
#### Returns
- query
- A query object with input (optionally transformed by callback) as the
starting array.
---
`sql`
SELECT ...
...
; / THIS PART /
Runs the query and returns the resulting array, or a [promise object][]
that resolves to the resulting array (see options below).
This function may be called more than once on the same query. The first time
it is called, the results of the query (and all queries on which this one is
dependent) are cached, and subsequent calls return the cached results instead
of recalculating them. This results in more efficient queries, especially when
one query uses another as its starting point. However, if inputs change in
between calls, the cached results may become stale and execute should be{force: true}
called with the option (see options below).
#### Syntax
`javascript`
query
.execute([options])
#### Arguments
- options (object)async
- Optional
- Object containing the following properties:
- (boolean): If true, then instead of returning the resultingforce
array, execute the query asynchronously and return a [promise object][]
that resolves to the resulting array. Default is false.
- (boolean): If false, then cached results (if available) from theexecute
last call to are returned. This results in faster queries,
but may return stale results if any input arrays have changed since the
last execution. If true, then the query is re-executed and its cache (and
those of all queries on which this one is dependent) is updated. Default
is false.
#### Returns
- array/promise
- If the {async: true} option was not used, the array resulting from
executing the query is returned. Otherwise, a [promise object][] is
returned that resolves to the resulting array.
---
`sql`
SELECT ...
Transforms each element in the query results with the return values of
callback.
The .select and .map functions are equivalent and both are provided as
syntactic sugar.
Join.selectFrom(input).select(callback) is equivalent toJoin.selectFrom(input, callback).
#### Syntax
`javascript`
query
.select(callback) // or .map(callback)
#### Arguments
- callback (function/string/array)callback(e)
- Optional
- Function executed as , where e is an element of the querycallback
array. This is used to transform the elements (e.g. by selecting only a
subset of their properties) before other operations are chained to the
query, and works identically to [Array.prototype.map()][].
- If a string is passed in, then only that property of the query array
elements is included in the resulting array.
- If an array of property names is passed in, then only those properties of
the elements of the query array are included in the resulting array.
- Unlike the SQL SELECT command, which specifies the fields to return at the
end of a query, the only specifies the array elements to provide.select
to the next operation, meaning if you want SQL-like behaviour, call
as the last operation in the query.
#### Returns
- query
- A query object where each element of the previous query's results have been
replaced by the values returned by callback.
---
`sql`
SELECT ... FROM ...
WHERE ... / THIS PART /
GROUP BY ...
HAVING ... / AND/OR THIS PART /
Filter the query results to only include elements passing the test implemented
by callback.
The .where, .having, and .filter functions are all equivalent and are.having
provided as syntactic sugar to make query construction more SQL-like; in
particular, is provided to be used after a *GroupBy operation.
#### Syntax
`javascript`
query
.where(callback) // or .having|.filter(callback)
#### Arguments
- callback (function)callback(e)
- Function executed as , where e is an element of the query
array. The resulting query contains only those elements for which a truthy
value is returned, identically to [Array.prototype.filter()][].
#### Returns
- query
- A query object where only elements of the previous query's results having
callback(e) == true are included.
---
`sql`
SELECT ...
ORDER BY ...
Sort the query results according to comparator.
The .orderBy and .sort functions are equivalent and both are provided as
syntactic sugar.
#### Syntax
`javascript`
query
.orderBy(comparator[, options]) // or .sort(comparator[, options])
#### Arguments
- comparator (function/string/array)comaprator(e1, e2)
- Function executed as , where e1 and e2 are elementslocaleCompare
of the query array. The resulting query's results are sorted according to
the returned values.
- This function follows the same spec as [Array.prototype.sort()][], except
that instead of sorting in-place, a new array will be created (as is the
case with every query operation).
- If a string is passed in, then the query elements are sorted by that
property in ascending order. If the property itself is a string, then
the sorting strategy is determined by the option; ifdiff()
the property is an object with a function, then this function isoptions
expected to have the same spec as the callback in
[Array.prototype.sort()][], and it is used to sort the query results.
Otherwise, the properties are converted to numbers and used for sorting.
- If an array of property names is passed in, then the query elements are
sorted in ascending order by each property in sequence, following the same
logic on each property as described above.
- (object)localeCompare
- Optional
- Object containing the following properties:
- (boolean): If true, this signifies that strings shouldfalse
be sorted using the [String.prototype.localeCompare()][] function. If
(default), strings are sorted according to each character'scomparator
Unicode code point value. This parameter is only used if istrue
a string or an array of property names. Setting this parameter to
results in generally slower sorts for string properties, but may be
necessary if the properties are locale-sensitive.
#### Returns
- query
- A query object whose elements are sorted according to comparator(e1, e2).
---
`sql`
SELECT ...
LIMIT ... [OFFSET ...]
Returns a slice of the query results according the specified length and/or
offset.
The .limit(length, offset) function is equivalent to.slice(offset, offset + length), and .offset(offset) is equivalent to.slice(offset). The variants are provided as syntactic sugar.
Calling .limit(length, offset) is equivalent to calling.offset(offset).limit(length).
#### Syntax
`javascript
query
.limit(length[, offset])
query
.offset(offset)
`
#### Arguments
- length (number)offset
- The maximum length to which to limit the query result. If this is longer
than the length of the curren query result, then the result is limited up
to and including the last element.
- (number)limit()
- Optional for
- The starting index (zero-based) of the returned query result, before
limiting to a specified length.
#### Returns
- query
- A query object whose results are a slice of the previous results.
---
`sql`
SELECT ...
LIMIT ... [OFFSET ...]
Returns a slice of the query results according the specified begin and end.
This function follows the same spec as [Array.prototype.slice()][].
The .slice(begin, end) function is equivalent to .limit(begin, end - begin),.slice(begin)
and is equivalent to .offset(begin). The variants are
provided as syntactic sugar.
#### Syntax
`javascript`
query
.slice([begin[, end]])
#### Arguments
- begin (number)end
- Optional
- The index (zero-based) of the query result at which to begin extraction.
Default is 0.
- (number)
- Optional
- The index (zero-based) of the query result before which to end extraction.
Default is the end of the current query result.
#### Returns
- query
- A query object whose results are a slice of the previous results.
---
`sql`
SELECT ...
FROM ...
[INNER|LEFT|RIGHT|FULL OUTER|CROSS] JOIN ... USING (...) / THIS PART /
Joins the query result to another array using a version of the [Hash Join][]
algorithm.
It is appropriate when the source arrays are small and unsorted, and if the
resulting array does not need to be returned in any particular order. For large
sorted arrays, mergeJoin is much more efficient.
#### Syntax
`javascript`
query
.hashJoin(right, hashFcn, callback)
#### Arguments
- right (array/JoinQuery)hashFcn
- If an array, the righthand array in the join operation.
- If a JoinQuery, the result of that query is used as the righthand array in
the join operation.
- (function/string/array)hashFcn(e)
- Function executed as , where e is an element of the currentright
query result (the "left" array) or , and returning a number ore
string. Values of for which hashFcn(e) returns the same value arecallback
considered equal and will be joined as specified by the function.function(e) { return e['x']; }
- If a string is passed in, then that property of each array element is used
as the hash. For example, passing in "x" is roughly equivalent to passing
in .['x', 'y']
- If an array of property names is passed in, then the JSON representation of
an array of those properties from each array element is used as the hash.
For example, passing in is equivalent to passing infunction(e) { return JSON.stringify([e.x, e.y]); }
.callback
- Using the function version and returning the same value for all inputs can
be used to implement cross/cartesian joins.
- (function)callback(e1, e2)
- Function executed as , where e1 and e2 are elementsright
of the current query result and , respectively (or null; see below).e1
If the returned value is truthy, it is added to the query result.
- For each pair of and e2 where hashFcn(e1) == hashFcn(e2),callback(e1, e2)
is called once per pair. Returning a value only whene1
neither or e2 are null can be used to implement inner joins.e1
- For each where there is no matching e2, callback(e1, null) ise2
called once. Correspondingly, for each where there is no matching e1,callback(null, e2)
is called once. Returning a value in these cases cane1
be used to implement left/right/full outer joins.
- Returning a value when only one of or e2 is non-null can be used to
implement left/right anti-joins.
#### Returns
- query
- A query object whose result is the join of the previous query result (the
"left" array) and right: for each call to callback(left[m], right[n])
returning a truthy value, that element is added to the query result.
---
`sql`
SELECT ...
FROM ...
[INNER|LEFT|RIGHT|FULL OUTER|CROSS] JOIN ... USING (...) / THIS PART /
Joins the query result to another array using a version of the
[Sort-Merge Join][] algorithm.
It is much more efficient than hashJoin when the source arrays already sortedoptions.sorted
(see below).
#### Syntax
`javascript`
query
.mergeJoin(right, comparator, callback[, options])
#### Arguments
- right (array/JoinQuery)comparator
- If an array, the righthand array in the join operation.
- If a JoinQuery, the result of that query is used as the righthand array in
the join operation.
- (function/string/array)comparator(e1, e2)
- Function executed as , where e1 and e2 areright
elements of the current query result (the "left" array) and ,compareFunction
respectively, and has the same spec as incomparator(e1, e2) === 0
[Array.prototype.sort()][]. If , then e1 ande2
are considered equal and will be joined as specified by the callbacklocaleCompare
function.
- If a string is passed in, then the query elements are sorted by that
property in ascending order. If the property itself is a string, then
the sorting strategy is determined by the option; ifdiff()
the property is an object with a function, then this function iscallback
expected to have the same spec as the callback in
[Array.prototype.sort()][], and it is used to sort the query results.
Otherwise, the properties are converted to numbers and used for sorting.
- If an array of property names is passed in, then the query elements are
sorted in ascending order by each property in sequence, following the same
logic on each property as described above.
- (function)callback(e1, e2)
- Function executed as , where e1 and e2 are elementsright
of the current query result (the "left" array) and , respectivelye1
(or null; see below). If the returned value is truthy, it is added to the
query result.
- For each equivalent pair of and e2, callback(e1, e2) is callede1
once per pair. Returning a value only when neither or e2 are nulle1
can be used to implement inner joins.
- For each where there is no matching e2, callback(e1, null) ise2
called once. Correspondingly, for each where there is no matchinge1
, callback(null, e2) is called once. Returning a value in these casese1
can be used to implement left/right/full outer joins.
- Returning a value when only one of or e2 is non-null can be used tooptions
implement left/right anti-joins.
- (object)sorted
- Optional
- Object containing the following properties:
- (boolean): If true, this signifies that both input arrays arecomparator
already sorted according to . This provides a significantfalse
performance boost. Default is .localeCompare
- (boolean): If true, this signifies that strings shouldfalse
be sorted using the [String.prototype.localeCompare()][] function. If
(default), strings are sorted according to each character'scomparator
Unicode code point value. This parameter is only used if istrue
a string or an array of property names. Setting this parameter to
results in generally slower sorts for string properties, but may be
necessary if the properties are locale-sensitive.
#### Returns
- query
- A query object whose result is the join of the previous query result
(the "left" array) and right: for each call to callback(left[m], right[n])
returning a truthy value, that element is added to the query result.
---
`sql`
SELECT ... / AGGREGATE FUNCTIONS /
FROM ...
...
GROUP BY ... / THIS PART /
Reduces query result elements that have the same hash into single elements.
Group membership is defined by the hashFcn function, where equal elementshashFcn(e1) === hashFcn(e2)
(i.e. where ) belong to the same group.
The result is similar to partitioning the query elements into sub-arrays of
elements that have the same hash (according to hashFcn), calling
[Array.prototype.reduce()][] on each, and returning an array of each returned
value.
#### Syntax
`javascript`
query
.hashGroupBy(hashFcn, callback)
#### Arguments
- hashFcn (function/string/array)hashFcn(e)
- Function executed as , where e is an element of the currente
query result, and returning a number or string. Values of for whichhashFcn(e)
returns the same value are considered to be in the same group.function(e) { return e['x']; }
- If a string is passed in, then that property of each array element is used
as the hash. For example, passing in "x" is roughly equivalent to passing
in .['x', 'y']
- If an array of property names is passed in, then the JSON representation of
an array of those properties from each array element is used as the hash.
For example, passing in is equivalent to passing infunction(e) { return JSON.stringify([e.x, e.y]); }
.callback
- (function)callback(previousValue, e)
- Function executed as , where e is an elementpreviousValue
of the current query result and is the last value returnedcallback
by for the group to which e belongs. On the first call for apreviousValue === null
particular group, . The last value returned for
each group is the one included for that group in the query result array.
- This function must always return a value.
#### Returns
- query
- A query object whose result has one element per group (as defined by
hashFcn), where that element is the last value returned by callback for
that group.
---
`sql`
SELECT ... / AGGREGATE FUNCTIONS /
FROM ...
...
GROUP BY ... / THIS PART /
Reduces query result elements that are equal into single elements. Group
membership is defined by the comparator function, where equal elementscomparator(e1, e2) === 0
(i.e. where ) belong to the same group.
The result is similar to partitioning the query elements into sub-arrays of
elements that are equal (according to comparator), calling
[Array.prototype.reduce()][] on each, and returning an array of each returned
value.
#### Syntax
`javascript`
query
.sortGroupBy(comparator, callback[, options])
#### Arguments
- comparator (function/string/array)comparator(e1, e2)
- Function executed as , where e1 and e2 are elementscompareFunction
of the current query result. This function has the same spec as
in [Array.prototype.sort()][]. Ifcomparator(e1, e2) === 0
, then e1 and e2 are considered to be in thelocaleCompare
same group.
- If a string is passed in, then the query elements are sorted by that
property in ascending order. If the property itself is a string, then
the sorting strategy is determined by the option; ifdiff()
the property is an object with a function, then this function iscallback
expected to have the same spec as the callback in
[Array.prototype.sort()][], and it is used to sort the query results.
Otherwise, the properties are converted to numbers and used for sorting.
- If an array of property names is passed in, then the query elements are
sorted in ascending order by each property in sequence, following the same
logic on each property as described above.
- (function)callback(previousValue, e)
- Function executed as , where e is an elementpreviousValue
of the current query result and is the last value returnedcallback
by for the group to which e belongs. On the first call for apreviousValue === null
particular group, . The last value returned foroptions
each group is the one included for that group in the query result array.
- This function must always return a value.
- (object)sorted
- Optional
- Object containing the following properties:
- (boolean): If true, this signifies that both input arrays arecomparator
already sorted according to . This provides a significantfalse
performance boost. Default is .localeCompare
- (boolean): If true, this signifies that strings shouldfalse
be sorted using the [String.prototype.localeCompare()][] function. If
(default), strings are sorted according to each character'scomparator
Unicode code point value. This parameter is only used if istrue
a string or an array of property names. Setting this parameter to
results in generally slower sorts for string properties, but may be
necessary if the properties are locale-sensitive.
#### Returns
- query
- A query object whose result has one element per group (as defined by
comparator), where that element is the last value returned by callback
for that group.
---
Note: This function has is no analogue in SQL.
Inspect the current query result using the provided callback.
This may be inserted in the middle of query construction to inspect or extract
the array at that point in execution. The provided callback function will be
called with the result array as its only argument.
The main difference between this function and execute is that instead ofexecute
an array or a promise, this function returns the query object, allowing you to
continue constructing your query. Also, like other operations, the callback is
not executed until is called.
The two main use-cases for this function are debugging and efficiency.
For example, the intermediate results in the Fluent Demo on the
[documentation page][docs] were constructed using .inspect() inserted at
various points within the construction of a single query, which allows us to see
each step of the query with less code than constructing/executing multiple
queries.
Like all other query operations, inspect is not executed if the cache is used.inspect
To force all operations (including ) to be executed, use the{force:true} option of execute.
#### Syntax
`javascript`
query
// operations
.inspect(callback)
// more operations
#### Arguments
- callback (function)callback(arr)
- Function executed as , where arr is the query result array
(i.e. the array constructed by earlier operations in this query, and used
as input to subsequent operations).
#### Returns
- query
- A query object whose results are the same as those passed into callback.
---
Static Functions
----------------
If all you want to do is use a single operation, all of the functions above
(excluding inspect, and execute) have static versions in the JoinselectFrom
service. They are called by putting the input array, the one normally passed
into , as the first argument. In fact, excluding the exceptionsJoin.operation(input, ...)
above, is equivalent toJoin.selectFrom(input).operation(...).execute().
The static versions currently do not have an asynchronous mode. That is,
there is no way to have them return a [promise object][] instead of the
resulting array, as can be done on query objects with .execute({async: true})`.
[kriskowal-q]: https://www.npmjs.com/package/q
[docs]: http://atavakoli.github.io/angular-join
[fluent interface]: http://en.wikipedia.org/wiki/Fluent_interface
[promise object]: http://docs.angularjs.org/api/ng/service/$q
[Hash Join]: http://en.wikipedia.org/wiki/Hash_join
[Sort-Merge Join]: http://en.wikipedia.org/wiki/Sort-merge_join
[String.prototype.localeCompare()]: http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
[Array.prototype.map()]: http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
[Array.prototype.sort()]: http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
[Array.prototype.reduce()]: http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
[Array.prototype.filter()]: http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
[travis-img]: https://travis-ci.org/atavakoli/angular-join.svg
[travis]: https://travis-ci.org/atavakoli/angular-join
[bower-img]: http://img.shields.io/bower/v/angular-join.svg
[bower]: http://bower.io/search/?q=angular-join
[npm-img]: https://img.shields.io/npm/v/angular-join.svg
[npm]: https://www.npmjs.com/package/angular-join
[license-img]: https://img.shields.io/badge/license-MIT-blue.svg