A couple of design points:
- OmegaDB itself is not stateful; instead, all configuration resides in the connection object.
cSel
.db('...') // Optional database selection (returns a new CollectionSelector)
.ns('...') // Optional namespace selection (returns a new CollectionSelector)
.schema('...') // Alias for 'ns'
.collection('...') // Collection selection (returns a new Query)
.table('...') // Alias for 'collection'
.bucket('...') // Alias for 'collection'
od(driver, config) // Create a new connection with the given driver and config
// Connection inherits CollectionSelector, and contains all the same methods; see CollectionSelector for details.
query
// Selecting data
.filter(filter) // Only return items that match the given filter
.between(field, lowerVal, upperVal, [leftBoundOpen, rightBoundOpen])
// Selects all items that have `field` between `lowerVal` and `upperVal`.
// Supports specifying whether the left bound or right bound is open. (Default for both is true.
// Joins
.as(alias) // Create an alias for the current query. (useful when it is a subquery of another query)
// You can then refer to this query's fields using dot notation: "alias.fieldName"
.join(query or collectionName, filter, ['left'|'inner'|'full'])
// Join the current query with the given query (or the given collection's contents)
.union(query) // Concatenates 'query' to the current query's results
// Transformations
.orderBy(field, [...]) // Sorts items by the specified field(s); prepend "-" to the field name to reverse the sort order
.limit(limit) // Limit the number of results
.offset(offset) // Skip the first `offset` number of results
.slice(start, [end]) // limit the sequence to only those items between 'start' and 'end'
// Modification
.delete(filter) // delete all items that match `filter`
.insert(query | json | list(json) | function() { ... }, [upsert]) // Inserts new documents into the table.
.update(query | json | function(item, key) { ... }) // Update documents with either the results of query, or of json (treated as a partial).
.replace(query | json | function(item, key) { ... }) // Replace the documents with either the results of query, or json.
// Special
.map(function) // Maps values through the mapping function
.reduce(function) // Reduces values through the reducing function
.execute() // Begins executing the current query, and returns a ResultSet
resultSet
.on('item', function(item){ ... }); // Emitted with each returned item
.on('finished', function(){ ... }); // Emitted when the query finishes successfully
.on('error', function(error){ ... }); // Emitted when the query generates an error
resultSet
// Get a count of total items in the result set.
.count(function(error, numItems){ ... });
// Accumulate all items into an array, and handle them all in one callback:
.all(function(errors, items){ ... });
// Retrieve exactly one item, by index
.nth(index, function(error, item){ ... });
// Retrieve only the first item (same as `.nth(0, ...)`)
.first(function(error, item){ ... });
// Handle each item individually, with no item accumulation:
.each(function(item, callback){ ...; callback(error); },
function(errors){ ... });
// Handle each item individually, yielding an array of transformed item values from each item handler call:
.map(function(item, callback){ ...; callback(error, item); },
function(errors, items){ ... });
// Handle each item individually, yielding the final value of an accumulator value:
.reduce({}, function(accum, item, callback){ ...; callback(error, accum); },
function(errors, accum){ ... });
resultSet
// Get a count of total items in the result set.
.count()
.then(function(error, numItems){ ... });
// Accumulate all items into an array, and handle them all via the returned promise:
.all()
.then(function(items){ ... }, function(errors){ ... });
// Retrieve exactly one item, by index
.nth(index)
.then(function(item){ ... }, function(error){ ... });
// Retrieve only the first item (same as `.nth(0, ...)`)
.first()
.then(function(item){ ... }, function(error){ ... });
// Handle each item individually, with no item accumulation:
.each(function(item, callback){ ...; callback(error); })
.then(function(){ ... }, function(errors){ ... });
// Handle each item individually, yielding an array of transformed item values from each item handler call:
.map(function(item, callback){ ...; callback(error, item); })
.then(function(items){ ... }, function(errors){ ... });
// Handle each item individually, yielding the final value of an accumulator value:
.reduce({}, function(accum, item, callback){ ...; callback(error, accum); })
.then(function(accum){ ... }, function(errors){ ... });
The per-item methods above each expect an item handler function, which will be called once per item in the result set. The method will not finish successfully (i.e., resolve the returned promise object, or call the finished callback without an error) until all item handler calls have completed.
Item handler functions may return different types of values, which influence when that result handler call is considered "completed":
- a promise object - the handler call is completed when the returned promise is resolved.
undefined
- the handler call is completed when the passedcallback
is called.
Filters are expressions that accept a value and a boolean. Selectors are like filters, but they return a value, rather than a boolean. A filter expression is a chain of filters and selectors. Filter expressions are initially passed the item
We are assuming the user calls the base object for filters and selectors f
.
f
// Selectors
.field('...') // Select a field from the current item
// Filters
.eq([field,] value)
.le([field,] value)
.contains([field,] value | list())
.in([field,] query | value | list())
.isNull([field])
.and(filter, [filter, ...])
.or(filter, [filter, ...])
Find the top 5 highest rated reviews in a product database.
var results = od('postgres', "tcp://user:p@ssw0rd@example-svr")
.db('example_db')
.schema('product_schema')
.table('reviews')
.between('score', 80, 100)
.orderBy('-score', 'created')
.limit(5)
.execute();
// Print the first review
results.first(function(error, firstReview)
{
console.log("FIRST!!!", firstReview);
});
// Or handle all the reviews (Don't try and do both)
results.all(function(error, reviews)
{
reviews.forEach(function(review)
{
console.log('Review:', review);
});
});
I've added a modification API. In doing so, I've hit on something:
We need to be able to pass in javascript callbacks.
Not for everything, but I can see it being really useful to be able to call
.update()
and pass in a callback that is called for every result, and the new value is calculated in that function. Under the hood this means we'd have to do a select query, execute it, call the callback for every value, take the value the callback spit out, and then do a replace query on all the values... but it would work. And, sometimes, you want to pay the performance cost for the convenience. (Also, it opens up the possibility of workarounds for any missing functionality we might have in this API. That is either good, or bad, depending on your point of view.)