class NoSE::Random::StatementGenerator
Generates random queries over entities in a given model
Public Class Methods
# File lib/nose/random.rb, line 126 def initialize(model) @model = model end
Public Instance Methods
Generate a random connection for an Insert
# File lib/nose/random.rb, line 146 def random_connection(entity) connections = entity.foreign_keys.values.sample(2) 'AND CONNECT TO ' + connections.map do |connection| "#{connection.name}(?)" end.join(', ') end
Generate a new random deletion of entities in the model @return [Delete]
# File lib/nose/random.rb, line 181 def random_delete path = random_path(1) from = [path.first.parent.name] + path.entries[1..-1].map(&:name) delete = "DELETE #{from.first} FROM #{from.join '.'} " + random_where_clause(path, 1) Statement.parse delete, @model end
Produce a random query graph over the entity graph
# File lib/nose/random.rb, line 274 def random_graph(max_nodes) graph = QueryGraph::Graph.new last_node = graph.add_node @model.entities.values.sample while graph.size < max_nodes # Get the possible foreign keys to use keys = last_node.entity.foreign_keys.values keys.reject! { |key| graph.nodes.map(&:entity).include? key.entity } break if keys.empty? # Pick a random foreign key to traverse next_key = keys.sample graph.add_edge last_node, next_key.entity, next_key # Select a new node to start from, making sure we pick one # that still has valid outgoing edges last_node = graph.nodes.reject do |node| (node.entity.foreign_keys.each_value.map(&:entity) - graph.nodes.map(&:entity)).empty? end.sample break if last_node.nil? end graph end
Generate a new random insertion to entities in the model @return [Insert]
# File lib/nose/random.rb, line 132 def random_insert(connect = true) entity = @model.entities.values.sample settings = entity.fields.each_value.map do |field| "#{field.name}=?" end.join ', ' insert = "INSERT INTO #{entity.name} SET #{settings} " # Optionally add connections to other entities insert += random_connection(entity) if connect Statement.parse insert, @model end
Return a random path through the entity graph @return [KeyPath]
# File lib/nose/random.rb, line 251 def random_path(max_length) # Pick the start of path weighted based on # the number of deges from each entity pick = Pickup.new(Hash[@model.entities.each_value.map do |entity| [entity, entity.foreign_keys.length] end]) path = [pick.pick.id_field] while path.length < max_length # Find a list of keys to entities we have not seen before last_entity = path.last.entity keys = last_entity.foreign_keys.values keys.reject! { |key| path.map(&:entity).include? key.entity } break if keys.empty? # Add a random new key to the path path << keys.sample end KeyPath.new path end
Generate a new random query from entities in the model @return [Query]
# File lib/nose/random.rb, line 193 def random_query(path_length = 3, selected_fields = 2, condition_count = 2, order = false) path = random_path path_length graph = QueryGraph::Graph.from_path path conditions = [ Condition.new(path.entities.first.fields.values.sample, :'=', nil) ] condition_count -= 1 new_fields = random_where_conditions(path, condition_count, conditions.map(&:field).to_set) conditions += new_fields.map do |field| Condition.new(field, :'>', nil) end conditions = Hash[conditions.map do |condition| [condition.field.id, condition] end] params = { select: random_select(path, selected_fields), model: @model, graph: graph, key_path: graph.longest_path, entity: graph.longest_path.first.parent, conditions: conditions, order: order ? [graph.entities.to_a.sample.fields.values.sample] : [] } query = Query.new params, nil query.hash query end
Get random fields to select for a Query
@return [Set<Fields::Field>]
# File lib/nose/random.rb, line 231 def random_select(path, selected_fields) fields = Set.new while fields.length < selected_fields fields.add path.entities.sample.fields.values.sample end fields end
Get random settings for an update @return [String]
# File lib/nose/random.rb, line 169 def random_settings(path, updated_fields) # Don't update key fields update_fields = path.entities.first.fields.values update_fields.reject! { |field| field.is_a? Fields::IDField } update_fields.sample(updated_fields).map do |field| "#{field.name}=?" end.join ', ' end
Produce a random statement according to a given set of weights @return [Statement]
# File lib/nose/random.rb, line 242 def random_statement(weights = { query: 80, insert: 10, update: 5, delete: 5 }) pick = Pickup.new(weights) type = pick.pick send(('random_' + type.to_s).to_sym) end
Generate a new random update of entities in the model @return [Update]
# File lib/nose/random.rb, line 155 def random_update(path_length = 1, updated_fields = 2, condition_count = 1) path = random_path(path_length) settings = random_settings path, updated_fields from = [path.first.parent.name] + path.entries[1..-1].map(&:name) update = "UPDATE #{from.first} FROM #{from.join '.'} " \ "SET #{settings} " + random_where_clause(path, condition_count) Statement.parse update, @model end
Private Instance Methods
Get the name to be used in the query for a condition field @return [String]
# File lib/nose/random.rb, line 327 def condition_field_name(field, path) field_path = path.first.name path_end = path.index(field.parent) last_entity = path.first path[1..path_end].each do |entity| fk = last_entity.foreign_keys.each_value.find do |key| key.entity == entity end field_path += '.' << fk.name last_entity = entity end field_path end
Produce a random where clause using fields along a given path @return [String]
# File lib/nose/random.rb, line 303 def random_where_clause(path, count = 2) # Ensure we have at least one condition at the beginning of the path conditions = [path.entities.first.fields.values.sample] conditions += random_where_conditions path, count - 1 return '' if conditions.empty? "WHERE #{conditions.map do |field| "#{path.find_field_parent(field).name}.#{field.name} = ?" end.join ' AND '}" end
Produce a random set of fields for a where clause @return [Array<Fields::Field>]
# File lib/nose/random.rb, line 316 def random_where_conditions(path, count, exclude = Set.new) 1.upto(count).map do field = path.entities.sample.fields.values.sample next nil if field.name == '**' || exclude.include?(field) field end.compact end