query-engine.coffee

http://www.mongodb.org/display/DOCS/Advanced+Queries


Helpers

Get the value of the object

get = (obj,key) ->
	if obj.get?
		obj.get key
	else
		obj[key]

Set the value of the object

set = (obj,key,value) ->
	if obj.set?
		obj.set key, value
	else
		obj[key] = value

To array

toArray = (value) ->
	unless value
		[]
	else unless value instanceof Array
		[value]
	else
		value

Array Prototypes

Hash = class

Array

	arr: []

Constructor

	constructor: (arr) ->
		@arr = toArray(arr)
		

Has In

	hasIn: (options) ->
		options = toArray(options)
		for value in @arr
			if value in options
				return true
		return false

Has All

	hasAll: (options) ->
		@arr.sort().join() is options.sort().join()

for key,value of Array::
	Hash::[key] = (args...) ->
		value.apply(@arr,args)

Collection

Collection = class

Constructor

	constructor: (data={}) ->
		for own key,value of data
			@[key] = value

Find

	find: (query={},next) ->

Matches

		matches = new Collection
		length = 0
		$nor = false
		$or = false
		$and = false
		matchType = 'and'
		

Determine matching type

		if query.$type
			matchType = query.$type
			delete query.$type

The $nor operator lets you use a boolean or expression to do queries. You give $nor a list of expressions, none of which can satisfy the query.

		if query.$nor
			$nor = query.$nor
			delete query.$nor

The $or operator lets you use a boolean or expression to do queries. You give $or a list of expressions, any of which can satisfy the query.

		if query.$or
			$or = query.$or
			delete query.$or

The $and operator lets you use boolean and in a query. You give $and an array of expressions, all of which must match to satisfy the query.

		if query.$and
			$and = query.$and
			delete query.$and

Start with entire set

		for own id,record of @

Match

			matchAll = true
			matchAny = false
			empty = true

Selectors

			for own field, selector of query
				match = false
				empty = false
				selectorType = typeof selector
				value = get(record,field)
				id = get(record,'id') or id
				exists = typeof value isnt 'undefined'
				value = false  unless exists

Standard

				if selectorType in ['string','number'] or selectorType instanceof String
					if exists and value is selector
						match = true

Array

				else if selector instanceof Array
					if exists and (new Hash value).hasAll(selector)
						match = true  
					

Date

				else if selector instanceof Date
					if exists and value.toString() is selector.toString()
						match = true  
				

Regular Expression

				else if selector instanceof RegExp
					if exists and selector.test(value)
						match = true

Conditional Operators

				else if selector instanceof Object

The $beginsWith operator checks if the value begins with a particular value

					if selector.$beginsWith
						if exists
							if typeof value is 'string' and value.substr(0,selector.$beginsWith.length) is selector.$beginsWith
								match = true
					

The $endsWith operator checks if the value ends with a particular value

					if selector.$endsWith
						if exists
							if typeof value is 'string' and value.substr(selector.$endsWith.length*-1) is selector.$endsWith
								match = true
					

The $all operator is similar to $in, but instead of matching any value in the specified array all values in the array must be matched.

					if selector.$all
						if exists
							if (new Hash value).hasAll(selector.$all)
								match = true
					

The $in operator is analogous to the SQL IN modifier, allowing you to specify an array of possible matches.

					if selector.$in
						if exists
							if (new Hash value).hasIn(selector.$in)
								match = true
							else if (new Hash selector.$in).hasIn(value)
								match = true
					

The $nin operator is similar to $in except that it selects objects for which the specified field does not have any value in the specified array.

					if selector.$nin
						if exists
							if (new Hash value).hasIn(selector.$nin) is false and (new Hash selector.$nin).hasIn(value) is false
								match = true
					

The $size operator matches any array with the specified number of elements. The following example would match the object {a:["foo"]}, since that array has just one element:

					if selector.$size
						if value.length? and value.length is selector.$size
							match = true
				

The $type operator matches values based on their BSON type.

					if selector.$type
						if typeof value is selector.$type
							match = true
					

Check for existence (or lack thereof) of a field.

					if selector.$exists
						if selector.$exists
							if exists is true
								match = true
						else
							if exists is false
								match = true
					

The $mod operator allows you to do fast modulo queries to replace a common case for where clauses.

					if selector.$mod
						match = false
					

Use $ne for "not equals".

					if selector.$ne
						if exists and value isnt selector.$ne
							match = true

less than

					if selector.$lt
						if exists and value < selector.$lt
							match = true
					

greater than

					if selector.$gt
						if exists and value > selector.$gt
							match = true
					

less than or equal to

					if selector.$lte
						if exists and value <= selector.$lte
							match = true

greater than or equal to

					if selector.$gte
						if exists and value >= selector.$gte
							match = true
				

Matched

				if match
					matchAny = true
				else
					matchAll = false
			

Match all

			if matchAll and !matchAny
				matchAll = false

Append

			append = false
			if empty
				append = true
			else
				switch matchType
					when 'none','nor'
						append = true  unless matchAny
						
					when 'any','or'
						append = true  if matchAny

when 'all','and'

					else
						append = true  if matchAll
			

Append

			matches[id] = record  if append

The $nor operator lets you use a boolean or expression to do queries. You give $nor a list of expressions, none of which can satisfy the query.

		if $nor
			newMatches = {}
			for expression in $nor
				for own key,value of matches.find expression
					newMatches[key] = value
			for own key,value of newMatches
				if matches[key]?
					delete matches[key]

The $or operator lets you use a boolean or expression to do queries. You give $or a list of expressions, any of which can satisfy the query.

		if $or
			newMatches = {}
			for expression in $or
				for own key,value of matches.find expression
					newMatches[key] = value
			matches = newMatches

The $and operator lets you use boolean and in a query. You give $and an array of expressions, all of which must match to satisfy the query.

		if $and
			for expression in $and
				matches = matches.find expression
		

Calculate length

		length = 0
		for own match of matches
			++length

Async

		if next?
			next false, matches, length

Sync

		else
			matches

Find One

	findOne: (query={},next) ->

Cycle

		matches = @find(query).toArray()
		match = if matches.length >= 1 then matches[0] else undefined

Async

		if next?
			next false, match

Sync

		else
			match

For Each

	forEach: (callback) ->

Prepare

		for own id,record of @
			callback record, id

To Array

	toArray: (next) ->

Prepare

		arr = []

Cycle

		for own key,value of @
			arr.push value

Async

		if next?
			next false, arr

Sync

		else
			arr

Sort

	sort: (comparison,next) ->

Prepare

		arr = @toArray()
		if comparison instanceof Function
			arr.sort(comparison)
		else
			for own key,value of comparison

Descending

				if value is -1
					arr.sort (a,b) ->
						get(b,key) - get(a,key)

Ascending

				else if value is 1
					arr.sort (a,b) ->
						get(a,key) - get(b,key)

Async

		if next?
			next false, arr

Sync

		else
			arr

Remove

	remove: (query={},next) ->

Prepare

		matches = @find(query)

Delete

		for own id,record of @
			delete @[id]
		

Async

		if next?
			next false, @

Sync

		else
			@

extendNatives = ->
	Array.prototype[key] ?= value  for own key,value of Hash.prototype
	Object.prototype[key] ?= value  for own key,value of Collection.prototype

Exports

Exports

if module? and module.exports?
	module.exports = {set,get,Collection,Hash,extendNatives}
else if window?
	window.queryEngine = {set,get,Collection,Hash,extendNatives}