A Node & AngularJS service providing RDBMS functionality for JavaScript arrays.
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:
<script src="path/to/angular-join.js"></script>
Add 'angular-join'
as a dependency in your Angular app:
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.
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:
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:
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 via
Join.selectFrom(...)
, but most functions below can also be called statically.
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
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 {async: true}
option,
a promise object is returned instead, which is resolved with the final
array.
Query objects are instances of JoinQuery
and each operation (with the
exception of execute
) results in the creation of a new object. The first time
that execute
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 to execute
, the cached results may become stale and
execute
should be called with the {force: true}
option.
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 subsequent
operations are applied to a new array whose elements are the return values of
callback
applied to each element of input
. Providing the callback works
identically to calling Join.selectFrom(input).select(callback)
instead.
Join
.selectFrom(input[, callback])
input
(array/JoinQuery)- If an array, the input array to start the query.
- If a JoinQuery, the result of that query is used to start this query.
callback
(function/string/array)- Optional
- Function executed as
callback(e)
, wheree
is an element ofinput
. This is used to transform theinput
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 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
input
are included in the resulting array. - Unlike the SQL SELECT command, which specifies the fields to return at the
end of a query, the
callback
only specifies the array elements to provide to the next operation, meaning if you want SQL-like behaviour, call.select
as the last operation in the query. Join.selectFrom(input, callback)
is equivalent toJoin.selectFrom(input).select(callback)
.
- query
- A query object with
input
(optionally transformed bycallback
) as the starting array.
- A query object with
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
called with the {force: true}
option (see options
below).
query
.execute([options])
options
(object)- Optional
- Object containing the following properties:
async
(boolean): If true, then instead of returning the resulting array, execute the query asynchronously and return a promise object that resolves to the resulting array. Default is false.force
(boolean): If false, then cached results (if available) from the last call toexecute
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.
- 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.
- If the
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 to
Join.selectFrom(input, callback)
.
query
.select(callback) // or .map(callback)
callback
(function/string/array)- Optional
- Function executed as
callback(e)
, wheree
is an element of the query 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
callback
only specifies the array elements to provide to the next operation, meaning if you want SQL-like behaviour, call.select
as the last operation in the query.
- query
- A query object where each element of the previous query's results have been
replaced by the values returned by
callback
.
- A query object where each element of the previous query's results have been
replaced by the values returned by
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
provided as syntactic sugar to make query construction more SQL-like; in
particular, .having
is provided to be used after a *GroupBy
operation.
query
.where(callback) // or .having|.filter(callback)
callback
(function)- Function executed as
callback(e)
, wheree
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().
- Function executed as
- query
- A query object where only elements of the previous query's results having
callback(e) == true
are included.
- A query object where only elements of the previous query's results having
SELECT ...
ORDER BY ...
Sort the query results according to comparator
.
The .orderBy
and .sort
functions are equivalent and both are provided as
syntactic sugar.
query
.orderBy(comparator[, options]) // or .sort(comparator[, options])
comparator
(function/string/array)- Function executed as
comaprator(e1, e2)
, wheree1
ande2
are elements 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
localeCompare
option; if the property is an object with adiff()
function, then this function is 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 executed as
options
(object)- Optional
- Object containing the following properties:
localeCompare
(boolean): Iftrue
, this signifies that strings should be sorted using the String.prototype.localeCompare() function. Iffalse
(default), strings are sorted according to each character's Unicode code point value. This parameter is only used ifcomparator
is a string or an array of property names. Setting this parameter totrue
results in generally slower sorts for string properties, but may be necessary if the properties are locale-sensitive.
- query
- A query object whose elements are sorted according to
comparator(e1, e2)
.
- A query object whose elements are sorted according to
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)
.
query
.limit(length[, offset])
query
.offset(offset)
length
(number)- 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.
offset
(number)- Optional for
limit()
- The starting index (zero-based) of the returned query result, before limiting to a specified length.
- Optional for
- query
- A query object whose results are a slice of the previous results.
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)
,
and .slice(begin)
is equivalent to .offset(begin)
. The variants are
provided as syntactic sugar.
query
.slice([begin[, end]])
begin
(number)- Optional
- The index (zero-based) of the query result at which to begin extraction. Default is 0.
end
(number)- Optional
- The index (zero-based) of the query result before which to end extraction. Default is the end of the current query result.
- query
- A query object whose results are a slice of the previous results.
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.
query
.hashJoin(right, hashFcn, callback)
right
(array/JoinQuery)- 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.
hashFcn
(function/string/array)- Function executed as
hashFcn(e)
, wheree
is an element of the current query result (the "left" array) orright
, and returning a number or string. Values ofe
for whichhashFcn(e)
returns the same value are considered equal and will be joined as specified by thecallback
function. - 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
function(e) { return e['x']; }
. - 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
['x', 'y']
is equivalent to passing infunction(e) { return JSON.stringify([e.x, e.y]); }
. - Using the function version and returning the same value for all inputs can be used to implement cross/cartesian joins.
- Function executed as
callback
(function)- Function executed as
callback(e1, e2)
, wheree1
ande2
are elements of the current query result andright
, respectively (or null; see below). If the returned value is truthy, it is added to the query result. - For each pair of
e1
ande2
wherehashFcn(e1) == hashFcn(e2)
,callback(e1, e2)
is called once per pair. Returning a value only when neithere1
ore2
are null can be used to implement inner joins. - For each
e1
where there is no matchinge2
,callback(e1, null)
is called once. Correspondingly, for eache2
where there is no matchinge1
,callback(null, e2)
is called once. Returning a value in these cases can be used to implement left/right/full outer joins. - Returning a value when only one of
e1
ore2
is non-null can be used to implement left/right anti-joins.
- Function executed as
- query
- A query object whose result is the join of the previous query result (the
"left" array) and
right
: for each call tocallback(left[m], right[n])
returning a truthy value, that element is added to the query result.
- A query object whose result is the join of the previous query result (the
"left" array) and
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 sorted
(see options.sorted
below).
query
.mergeJoin(right, comparator, callback[, options])
right
(array/JoinQuery)- 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.
comparator
(function/string/array)- Function executed as
comparator(e1, e2)
, wheree1
ande2
are elements of the current query result (the "left" array) andright
, respectively, and has the same spec ascompareFunction
in Array.prototype.sort(). Ifcomparator(e1, e2) === 0
, thene1
ande2
are considered equal and will be joined as specified by thecallback
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
localeCompare
option; if the property is an object with adiff()
function, then this function is 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 executed as
callback
(function)- Function executed as
callback(e1, e2)
, wheree1
ande2
are elements of the current query result (the "left" array) andright
, respectively (or null; see below). If the returned value is truthy, it is added to the query result. - For each equivalent pair of
e1
ande2
,callback(e1, e2)
is called once per pair. Returning a value only when neithere1
ore2
are null can be used to implement inner joins. - For each
e1
where there is no matchinge2
,callback(e1, null)
is called once. Correspondingly, for eache2
where there is no matchinge1
,callback(null, e2)
is called once. Returning a value in these cases can be used to implement left/right/full outer joins. - Returning a value when only one of
e1
ore2
is non-null can be used to implement left/right anti-joins.
- Function executed as
options
(object)- Optional
- Object containing the following properties:
sorted
(boolean): Iftrue
, this signifies that both input arrays are already sorted according tocomparator
. This provides a significant performance boost. Default isfalse
.localeCompare
(boolean): Iftrue
, this signifies that strings should be sorted using the String.prototype.localeCompare() function. Iffalse
(default), strings are sorted according to each character's Unicode code point value. This parameter is only used ifcomparator
is a string or an array of property names. Setting this parameter totrue
results in generally slower sorts for string properties, but may be necessary if the properties are locale-sensitive.
- query
- A query object whose result is the join of the previous query result
(the "left" array) and
right
: for each call tocallback(left[m], right[n])
returning a truthy value, that element is added to the query result.
- A query object whose result is the join of the previous query result
(the "left" array) and
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 elements
(i.e. where hashFcn(e1) === hashFcn(e2)
) 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.
query
.hashGroupBy(hashFcn, callback)
hashFcn
(function/string/array)- Function executed as
hashFcn(e)
, wheree
is an element of the current query result, and returning a number or string. Values ofe
for whichhashFcn(e)
returns the same value are considered to be in the same group. - 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
function(e) { return e['x']; }
. - 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
['x', 'y']
is equivalent to passing infunction(e) { return JSON.stringify([e.x, e.y]); }
.
- Function executed as
callback
(function)- Function executed as
callback(previousValue, e)
, wheree
is an element of the current query result andpreviousValue
is the last value returned bycallback
for the group to whiche
belongs. On the first call for a particular group,previousValue === null
. 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.
- Function executed as
- query
- A query object whose result has one element per group (as defined by
hashFcn
), where that element is the last value returned bycallback
for that group.
- A query object whose result has one element per group (as defined by
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 elements
(i.e. where comparator(e1, e2) === 0
) 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.
query
.sortGroupBy(comparator, callback[, options])
comparator
(function/string/array)- Function executed as
comparator(e1, e2)
, wheree1
ande2
are elements of the current query result. This function has the same spec ascompareFunction
in Array.prototype.sort(). Ifcomparator(e1, e2) === 0
, thene1
ande2
are considered to be in the 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
localeCompare
option; if the property is an object with adiff()
function, then this function is 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 executed as
callback
(function)- Function executed as
callback(previousValue, e)
, wheree
is an element of the current query result andpreviousValue
is the last value returned bycallback
for the group to whiche
belongs. On the first call for a particular group,previousValue === null
. 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.
- Function executed as
options
(object)- Optional
- Object containing the following properties:
sorted
(boolean): Iftrue
, this signifies that both input arrays are already sorted according tocomparator
. This provides a significant performance boost. Default isfalse
.localeCompare
(boolean): Iftrue
, this signifies that strings should be sorted using the String.prototype.localeCompare() function. Iffalse
(default), strings are sorted according to each character's Unicode code point value. This parameter is only used ifcomparator
is a string or an array of property names. Setting this parameter totrue
results in generally slower sorts for string properties, but may be necessary if the properties are locale-sensitive.
- query
- A query object whose result has one element per group (as defined by
comparator
), where that element is the last value returned bycallback
for that group.
- A query object whose result has one element per group (as defined by
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 of
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 execute
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 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.
To force all operations (including inspect
) to be executed, use the
{force:true}
option of execute
.
query
// operations
.inspect(callback)
// more operations
callback
(function)- Function executed as
callback(arr)
, wherearr
is the query result array (i.e. the array constructed by earlier operations in this query, and used as input to subsequent operations).
- Function executed as
- query
- A query object whose results are the same as those passed into
callback
.
- A query object whose results are the same as those passed into
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 Join
service. They are called by putting the input array, the one normally passed
into selectFrom
, as the first argument. In fact, excluding the exceptions
above, Join.operation(input, ...)
is equivalent to
Join.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})
.