docs: function prepare_select_query(self, types, search_opts, traits) {
  	var ObjType, document_type, document_type_obj;
  	return nr_fcall("nopg:prepare_select_query", function() {
  
  		// If true then this is recursive function call
  		var _recursive = false;
  		if(is.obj(traits) && traits._recursive) {
  			_recursive = traits._recursive;
  		}
  
  		//
  		if( is.array(search_opts) && (search_opts.length === 1) && is_operator(search_opts[0]) ) {
  			throw new TypeError('search_opts invalid: ' + util.inspect(search_opts) );
  		}
  
  		// Object type and document type
  		if(is.array(types)) {
  			types = [].concat(types);
  			ObjType = types.shift();
  			document_type = types.shift();
  		} else {
  			ObjType = types;
  		}
  
  		// Create the initial query object
  		var q = new Query({
  			'method': 'select',
  			'ObjType': ObjType,
  			'document_type': document_type
  		});
  
  		// Traits for search operation
  		traits = parse_search_traits(traits);
  
  		if(traits.hasOwnProperty('count')) {
  			q.count(traits.count);
  		}
  
  		// Search options for documents
  		search_opts = parse_search_opts(search_opts, traits);
  
  		/* Build `type_condition` */
  
  		// If we have the document_type we can limit the results with it
  		if(document_type) {
  			parse_where_type_condition(q, document_type);
  		}
  
  		/* Parse `opts_condition` */
  
  		var type_predicate = search_opts ? _recursive_parse_predicates(ObjType, q, ((traits.match === 'any') ? 'OR' : 'AND'), traits, search_opts) : undefined;
  		if(type_predicate) {
  			q.where(type_predicate);
  		}
  
  		if(traits.limit) {
  			q.limit(traits.limit);
  		}
  
  		if(traits.offset) {
  			q.offset(traits.offset);
  		}
  
  		if(is.obj(document_type) && (document_type instanceof NoPg.Type) ) {
  			document_type_obj = document_type;
  		}
  
  		return Q.fcall(function() {
  
  			// Do not search type if recursive call
  			if(_recursive) {
  				return q;
  			}
  
  			// Do not search type again if we already have it
  			if(document_type_obj) {
  				return q;
  			}
  
  			// Do not search type if not a string
  			if(!is.string(document_type)) {
  				return q;
  			}
  
  			var order_enabled = (traits.order && has_property_names(traits.order)) ? true : false;
  			var group_enabled = (traits.group && has_property_names(traits.group)) ? true : false;
  
  			// Only search type if order has been enabled or traits.typeAwareness enabled
  			if(!( group_enabled || order_enabled || traits.typeAwareness )) {
  				return q;
  			}
  
  			return _get_type_by_name(self, document_type).then(function(type) {
  				document_type_obj = type;
  				return q;
  			});
  
  		}).then(function our_query(q) {
  
  			if(traits.order) {
  				q.orders( _parse_select_order(ObjType, document_type_obj, traits.order, q, traits) );
  			}
  
  			if(traits.group) {
  				q.group( _parse_select_order(ObjType, document_type_obj, traits.group, q, traits) );
  			}
  
  			//debug.log('traits.typeAwareness = ', traits.typeAwareness);
  			//if(document_type_obj) {
  			//	debug.log('document_type_obj = ', document_type_obj);
  			//	debug.log('document_type_obj.documents = ', document_type_obj.documents);
  			//}
  
  			if(traits.typeAwareness && document_type_obj && document_type_obj.hasOwnProperty('documents')) {
  				// FIXME: Maybe we should make sure these documents settings do not collide?
  				//debug.log('traits.documents = ', traits.documents);
  				//debug.log('document_type_obj.documents = ', document_type_obj.documents);
  				traits.documents = [].concat(traits.documents || []).concat(document_type_obj.documents);
  				//debug.log('traits.documents = ', traits.documents);
  			}
  
  			// Fields to search for
  			var fields = parse_select_fields(ObjType, traits);
  			q.fields(fields);
  
  			return q;
  		});
  
  	}); // nr_fcall
  }