nose 0.1.0pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/lib/nose/backend/cassandra.rb +390 -0
  3. data/lib/nose/backend/file.rb +185 -0
  4. data/lib/nose/backend/mongo.rb +242 -0
  5. data/lib/nose/backend.rb +557 -0
  6. data/lib/nose/cost/cassandra.rb +33 -0
  7. data/lib/nose/cost/entity_count.rb +27 -0
  8. data/lib/nose/cost/field_size.rb +31 -0
  9. data/lib/nose/cost/request_count.rb +32 -0
  10. data/lib/nose/cost.rb +68 -0
  11. data/lib/nose/debug.rb +45 -0
  12. data/lib/nose/enumerator.rb +199 -0
  13. data/lib/nose/indexes.rb +239 -0
  14. data/lib/nose/loader/csv.rb +99 -0
  15. data/lib/nose/loader/mysql.rb +199 -0
  16. data/lib/nose/loader/random.rb +48 -0
  17. data/lib/nose/loader/sql.rb +105 -0
  18. data/lib/nose/loader.rb +38 -0
  19. data/lib/nose/model/entity.rb +136 -0
  20. data/lib/nose/model/fields.rb +293 -0
  21. data/lib/nose/model.rb +113 -0
  22. data/lib/nose/parser.rb +202 -0
  23. data/lib/nose/plans/execution_plan.rb +282 -0
  24. data/lib/nose/plans/filter.rb +99 -0
  25. data/lib/nose/plans/index_lookup.rb +302 -0
  26. data/lib/nose/plans/limit.rb +42 -0
  27. data/lib/nose/plans/query_planner.rb +361 -0
  28. data/lib/nose/plans/sort.rb +49 -0
  29. data/lib/nose/plans/update.rb +60 -0
  30. data/lib/nose/plans/update_planner.rb +270 -0
  31. data/lib/nose/plans.rb +135 -0
  32. data/lib/nose/proxy/mysql.rb +275 -0
  33. data/lib/nose/proxy.rb +102 -0
  34. data/lib/nose/query_graph.rb +481 -0
  35. data/lib/nose/random/barbasi_albert.rb +48 -0
  36. data/lib/nose/random/watts_strogatz.rb +50 -0
  37. data/lib/nose/random.rb +391 -0
  38. data/lib/nose/schema.rb +89 -0
  39. data/lib/nose/search/constraints.rb +143 -0
  40. data/lib/nose/search/problem.rb +328 -0
  41. data/lib/nose/search/results.rb +200 -0
  42. data/lib/nose/search.rb +266 -0
  43. data/lib/nose/serialize.rb +747 -0
  44. data/lib/nose/statements/connection.rb +160 -0
  45. data/lib/nose/statements/delete.rb +83 -0
  46. data/lib/nose/statements/insert.rb +146 -0
  47. data/lib/nose/statements/query.rb +161 -0
  48. data/lib/nose/statements/update.rb +101 -0
  49. data/lib/nose/statements.rb +645 -0
  50. data/lib/nose/timing.rb +79 -0
  51. data/lib/nose/util.rb +305 -0
  52. data/lib/nose/workload.rb +244 -0
  53. data/lib/nose.rb +37 -0
  54. data/templates/workload.erb +42 -0
  55. metadata +700 -0
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ # Superclass for connect and disconnect statements
5
+ class Connection < Statement
6
+ include StatementSupportQuery
7
+
8
+ attr_reader :source_pk, :target, :target_pk, :conditions
9
+ alias source entity
10
+
11
+ def initialize(params, text, group: nil, label: nil)
12
+ super params, text, group: group, label: label
13
+ fail InvalidStatementException, 'Incorrect connection initialization' \
14
+ unless text.split.first == self.class.name.split('::').last.upcase
15
+
16
+ populate_conditions params
17
+ end
18
+
19
+ # Build a new disconnect from a provided parse tree
20
+ # @return [Connection]
21
+ def self.parse(tree, params, text, group: nil, label: nil)
22
+ keys_from_tree tree, params
23
+
24
+ new params, text, group: group, label: label
25
+ end
26
+
27
+ # @return[void]
28
+ def self.keys_from_tree(tree, params)
29
+ params[:source_pk] = tree[:source_pk]
30
+ params[:target] = params[:entity].foreign_keys[tree[:target].to_s]
31
+ params[:target_pk] = tree[:target_pk]
32
+ end
33
+
34
+ # Produce the SQL text corresponding to this connection
35
+ # @return [String]
36
+ def unparse
37
+ "CONNECT #{source.name}(\"#{source_pk}\") TO " \
38
+ "#{target.name}(\"#{target_pk}\")"
39
+ end
40
+
41
+ def ==(other)
42
+ self.class == other.class &&
43
+ @graph == other.graph &&
44
+ @source == other.source &&
45
+ @target == other.target &&
46
+ @conditions == other.conditions
47
+ end
48
+ alias eql? ==
49
+
50
+ def hash
51
+ @hash ||= [@graph, @source, @target, @conditions].hash
52
+ end
53
+
54
+ # A connection modifies an index if the relationship is in the path
55
+ def modifies_index?(index)
56
+ index.path.include?(@target) || index.path.include?(@target.reverse)
57
+ end
58
+
59
+ # Get the support queries for updating an index
60
+ def support_queries(index)
61
+ return [] unless modifies_index?(index)
62
+
63
+ select = index.all_fields - @conditions.each_value.map(&:field).to_set
64
+ return [] if select.empty?
65
+
66
+ index.graph.split(entity).map do |graph|
67
+ support_fields = select.select do |field|
68
+ graph.entities.include? field.parent
69
+ end.to_set
70
+ conditions = @conditions.select do |_, c|
71
+ graph.entities.include? c.field.parent
72
+ end
73
+
74
+ split_entity = split_entity graph, index.graph, entity
75
+ build_support_query split_entity, index, graph, support_fields,
76
+ conditions
77
+ end.compact
78
+ end
79
+
80
+ protected
81
+
82
+ # The two key fields are provided with the connection
83
+ def given_fields
84
+ [@target.parent.id_field, @target.entity.id_field]
85
+ end
86
+
87
+ private
88
+
89
+ # Validate the types of the primary keys
90
+ # @return [void]
91
+ def validate_keys
92
+ # XXX Only works for non-composite PKs
93
+ source_type = source.id_field.class.const_get 'TYPE'
94
+ fail TypeError unless source_type.nil? || source_pk.is_a?(type)
95
+
96
+ target_type = @target.class.const_get 'TYPE'
97
+ fail TypeError unless target_type.nil? || target_pk.is_a?(type)
98
+ end
99
+
100
+ # Populate the list of condition objects
101
+ # @return [void]
102
+ def populate_conditions(params)
103
+ @source_pk = params[:source_pk]
104
+ @target = params[:target]
105
+ @target_pk = params[:target_pk]
106
+
107
+ validate_keys
108
+
109
+ # This is needed later when planning updates
110
+ @eq_fields = [@target.parent.id_field,
111
+ @target.entity.id_field]
112
+
113
+ source_id = source.id_field
114
+ target_id = @target.entity.id_field
115
+ @conditions = {
116
+ source_id.id => Condition.new(source_id, :'=', @source_pk),
117
+ target_id.id => Condition.new(target_id, :'=', @target_pk)
118
+ }
119
+ end
120
+
121
+ # Get the where clause for a support query over the given path
122
+ # @return [String]
123
+ def support_query_condition_for_path(path, reversed)
124
+ key = (reversed ? target.entity : target.parent).id_field
125
+ path = path.reverse if path.entities.last != key.entity
126
+ eq_key = path.entries[-1]
127
+ if eq_key.is_a? Fields::ForeignKeyField
128
+ where = "WHERE #{eq_key.name}.#{eq_key.entity.id_field.name} = ?"
129
+ else
130
+ where = "WHERE #{eq_key.parent.name}." \
131
+ "#{eq_key.parent.id_field.name} = ?"
132
+ end
133
+
134
+ where
135
+ end
136
+ end
137
+
138
+ # A representation of a connect in the workload
139
+ class Connect < Connection
140
+ # Specifies that connections require insertion
141
+ def requires_insert?(_index)
142
+ true
143
+ end
144
+ end
145
+
146
+ # A representation of a disconnect in the workload
147
+ class Disconnect < Connection
148
+ # Produce the SQL text corresponding to this disconnection
149
+ # @return [String]
150
+ def unparse
151
+ "DISCONNECT #{source.name}(\"#{source_pk}\") FROM " \
152
+ "#{target.name}(\"#{target_pk}\")"
153
+ end
154
+
155
+ # Specifies that disconnections require deletion
156
+ def requires_delete?(_index)
157
+ true
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ # A representation of a delete in the workload
5
+ class Delete < Statement
6
+ include StatementConditions
7
+ include StatementSupportQuery
8
+
9
+ def initialize(params, text, group: nil, label: nil)
10
+ super params, text, group: group, label: label
11
+
12
+ populate_conditions params
13
+ end
14
+
15
+ # Build a new delete from a provided parse tree
16
+ # @return [Delete]
17
+ def self.parse(tree, params, text, group: nil, label: nil)
18
+ conditions_from_tree tree, params
19
+
20
+ Delete.new params, text, group: group, label: label
21
+ end
22
+
23
+ # Produce the SQL text corresponding to this delete
24
+ # @return [String]
25
+ def unparse
26
+ delete = "DELETE #{entity.name} "
27
+ delete += "FROM #{from_path @key_path}"
28
+ delete << where_clause
29
+
30
+ delete
31
+ end
32
+
33
+ def ==(other)
34
+ other.is_a?(Delete) &&
35
+ @graph == other.graph &&
36
+ entity == other.entity &&
37
+ @conditions == other.conditions
38
+ end
39
+ alias eql? ==
40
+
41
+ def hash
42
+ @hash ||= [@graph, entity, @conditions].hash
43
+ end
44
+
45
+ # Index contains the entity to be deleted
46
+ def modifies_index?(index)
47
+ index.graph.entities.include? entity
48
+ end
49
+
50
+ # Specifies that deletes require deletion
51
+ def requires_delete?(_index)
52
+ true
53
+ end
54
+
55
+ # Get the support queries for deleting from an index
56
+ def support_queries(index)
57
+ return [] unless modifies_index? index
58
+ select = (index.hash_fields + index.order_fields.to_set) -
59
+ @conditions.each_value.map(&:field).to_set
60
+ return [] if select.empty?
61
+
62
+ support_queries = []
63
+
64
+ # Build a support query which gets the IDs of the entities being deleted
65
+ graph = @graph.dup
66
+ support_fields = select.select do |field|
67
+ field.parent == entity
68
+ end.to_set
69
+ support_fields << entity.id_field \
70
+ unless @conditions.each_value.map(&:field).include? entity.id_field
71
+ conditions = Hash[@conditions.map { |k, v| [k.dup, v.dup] }]
72
+
73
+ support_queries << build_support_query(entity, index, graph,
74
+ support_fields, conditions)
75
+ support_queries.compact + support_queries_for_entity(index, select)
76
+ end
77
+
78
+ # The condition fields are provided with the deletion
79
+ def given_fields
80
+ @conditions.each_value.map(&:field)
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ # A representation of an insert in the workload
5
+ class Insert < Statement
6
+ include StatementConditions
7
+ include StatementSettings
8
+ include StatementSupportQuery
9
+
10
+ def initialize(params, text, group: nil, label: nil)
11
+ super params, text, group: group, label: label
12
+
13
+ @settings = params[:settings]
14
+ fail InvalidStatementException, 'Must insert primary key' \
15
+ unless @settings.map(&:field).include?(entity.id_field)
16
+
17
+ populate_conditions params
18
+ end
19
+
20
+ # Build a new insert from a provided parse tree
21
+ # @return [Insert]
22
+ def self.parse(tree, params, text, group: nil, label: nil)
23
+ settings_from_tree tree, params
24
+ conditions_from_tree tree, params
25
+
26
+ Insert.new params, text, group: group, label: label
27
+ end
28
+
29
+ # Extract conditions from a parse tree
30
+ # @return [Hash]
31
+ def self.conditions_from_tree(tree, params)
32
+ connections = tree[:connections] || []
33
+ connections = connections.map do |connection|
34
+ field = params[:entity][connection[:target].to_s]
35
+ value = connection[:target_pk]
36
+
37
+ type = field.class.const_get 'TYPE'
38
+ value = field.class.value_from_string(value.to_s) \
39
+ unless type.nil? || value.nil?
40
+
41
+ connection.delete :value
42
+ Condition.new field, :'=', value
43
+ end
44
+
45
+ params[:conditions] = Hash[connections.map do |connection|
46
+ [connection.field.id, connection]
47
+ end]
48
+ end
49
+ private_class_method :conditions_from_tree
50
+
51
+ # Produce the SQL text corresponding to this insert
52
+ # @return [String]
53
+ def unparse
54
+ insert = "INSERT INTO #{entity.name} "
55
+ insert += settings_clause
56
+
57
+ insert << ' AND CONNECT TO ' << @conditions.values.map do |condition|
58
+ value = maybe_quote condition.value, condition.field
59
+ "#{condition.field.name}(#{value})"
60
+ end.join(', ') unless @conditions.empty?
61
+
62
+ insert
63
+ end
64
+
65
+ def ==(other)
66
+ other.is_a?(Insert) &&
67
+ @graph == other.graph &&
68
+ entity == other.entity &&
69
+ @settings == other.settings &&
70
+ @conditions == other.conditions
71
+ end
72
+ alias eql? ==
73
+
74
+ def hash
75
+ @hash ||= [@graph, entity, @settings, @conditions].hash
76
+ end
77
+
78
+ # Determine if this insert modifies an index
79
+ def modifies_index?(index)
80
+ return true if modifies_single_entity_index?(index)
81
+ return false if index.graph.size == 1
82
+ return false unless index.graph.entities.include? entity
83
+
84
+ # Check if the index crosses all of the connection keys
85
+ keys = @conditions.each_value.map(&:field)
86
+ index.graph.keys_from_entity(entity).all? { |k| keys.include? k }
87
+ end
88
+
89
+ # Specifies that inserts require insertion
90
+ def requires_insert?(_index)
91
+ true
92
+ end
93
+
94
+ # Support queries are required for index insertion with connection
95
+ # to select attributes of the other related entities
96
+ # @return [Array<SupportQuery>]
97
+ def support_queries(index)
98
+ return [] unless modifies_index?(index) &&
99
+ !modifies_single_entity_index?(index)
100
+
101
+ # Get all fields which need to be selected by support queries
102
+ select = index.all_fields -
103
+ @settings.map(&:field).to_set -
104
+ @conditions.each_value.map do |condition|
105
+ condition.field.entity.id_field
106
+ end.to_set
107
+ return [] if select.empty?
108
+
109
+ index.graph.split(entity).map do |graph|
110
+ support_fields = select.select do |field|
111
+ graph.entities.include? field.parent
112
+ end.to_set
113
+
114
+ # Build conditions by traversing the foreign keys
115
+ conditions = @conditions.each_value.map do |c|
116
+ next unless graph.entities.include? c.field.entity
117
+
118
+ Condition.new c.field.entity.id_field, c.operator, c.value
119
+ end.compact
120
+ conditions = Hash[conditions.map do |condition|
121
+ [condition.field.id, condition]
122
+ end]
123
+
124
+ split_entity = split_entity graph, index.graph, entity
125
+ build_support_query split_entity, index, graph, support_fields,
126
+ conditions
127
+ end.compact
128
+ end
129
+
130
+ # The settings fields are provided with the insertion
131
+ def given_fields
132
+ @settings.map(&:field) + @conditions.each_value.map do |condition|
133
+ condition.field.entity.id_field
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ # Check if the insert modifies a single entity index
140
+ # @return [Boolean]
141
+ def modifies_single_entity_index?(index)
142
+ !(@settings.map(&:field).to_set & index.all_fields).empty? &&
143
+ index.graph.size == 1 && index.graph.entities.first == entity
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ # A representation of a query in the workload
5
+ class Query < Statement
6
+ include StatementConditions
7
+
8
+ attr_reader :select, :order, :limit
9
+
10
+ def initialize(params, text, group: nil, label: nil)
11
+ super params, text, group: group, label: label
12
+
13
+ populate_conditions params
14
+ @select = params[:select]
15
+ @order = params[:order] || []
16
+
17
+ fail InvalidStatementException, 'can\'t order by IDs' \
18
+ if @order.any? { |f| f.is_a? Fields::IDField }
19
+
20
+ if join_order.first != @key_path.entities.first
21
+ @key_path = @key_path.reverse
22
+ end
23
+
24
+ fail InvalidStatementException, 'must have an equality predicate' \
25
+ if @conditions.empty? || @conditions.values.all?(&:is_range)
26
+
27
+ @limit = params[:limit]
28
+ end
29
+
30
+ # Build a new query from a provided parse tree
31
+ # @return [Query]
32
+ def self.parse(tree, params, text, group: nil, label: nil)
33
+ conditions_from_tree tree, params
34
+ fields_from_tree tree, params
35
+ order_from_tree tree, params
36
+ params[:limit] = tree[:limit].to_i if tree[:limit]
37
+
38
+ new params, text, group: group, label: label
39
+ end
40
+
41
+ # Produce the SQL text corresponding to this query
42
+ # @return [String]
43
+ def unparse
44
+ field_namer = -> (f) { field_path f }
45
+
46
+ query = 'SELECT ' + @select.map(&field_namer).join(', ')
47
+ query << " FROM #{from_path @graph.longest_path}"
48
+ query << where_clause(field_namer)
49
+
50
+ query << ' ORDER BY ' << @order.map(&field_namer).join(', ') \
51
+ unless @order.empty?
52
+ query << " LIMIT #{@limit}" unless @limit.nil?
53
+ query << " -- #{@comment}" unless @comment.nil?
54
+
55
+ query
56
+ end
57
+
58
+ def ==(other)
59
+ other.is_a?(Query) &&
60
+ @graph == other.graph &&
61
+ @select == other.select &&
62
+ @conditions == other.conditions &&
63
+ @order == other.order &&
64
+ @limit == other.limit &&
65
+ @comment == other.comment
66
+ end
67
+ alias eql? ==
68
+
69
+ def hash
70
+ @hash ||= [@graph, @select, @conditions, @order, @limit, @comment].hash
71
+ end
72
+
73
+ # The order entities should be joined according to the query graph
74
+ # @return [Array<Entity>]
75
+ def join_order
76
+ @graph.join_order(@eq_fields)
77
+ end
78
+
79
+ # Specifies that queries don't modify data
80
+ def read_only?
81
+ true
82
+ end
83
+
84
+ # All fields referenced anywhere in the query
85
+ # @return [Set<Fields::Field>]
86
+ def all_fields
87
+ (@select + @conditions.each_value.map(&:field) + @order).to_set
88
+ end
89
+
90
+ # Extract fields to be selected from a parse tree
91
+ # @return [Set<Field>]
92
+ def self.fields_from_tree(tree, params)
93
+ params[:select] = tree[:select].flat_map do |field|
94
+ if field.last == '*'
95
+ # Find the entity along the path
96
+ entity = params[:key_path].entities[tree[:path].index(field.first)]
97
+ entity.fields.values
98
+ else
99
+ field = add_field_with_prefix tree[:path], field, params
100
+
101
+ fail InvalidStatementException, 'Foreign keys cannot be selected' \
102
+ if field.is_a? Fields::ForeignKeyField
103
+
104
+ field
105
+ end
106
+ end.to_set
107
+ end
108
+ private_class_method :fields_from_tree
109
+
110
+ # Extract ordering fields from a parse tree
111
+ # @return [Array<Field>]
112
+ def self.order_from_tree(tree, params)
113
+ return params[:order] = [] if tree[:order].nil?
114
+
115
+ params[:order] = tree[:order][:fields].each_slice(2).map do |field|
116
+ field = field.first if field.first.is_a?(Array)
117
+ add_field_with_prefix tree[:path], field, params
118
+ end
119
+ end
120
+ private_class_method :order_from_tree
121
+
122
+ private
123
+
124
+ def field_path(field)
125
+ path = @graph.path_between @graph.longest_path.entities.first,
126
+ field.parent
127
+ path = path.drop_while { |k| @graph.longest_path.include? k } << path[-1]
128
+ path = KeyPath.new(path) unless path.is_a?(KeyPath)
129
+
130
+ from_path path, @graph.longest_path, field
131
+ end
132
+ end
133
+
134
+ # A query required to support an update
135
+ class SupportQuery < Query
136
+ attr_reader :statement, :index, :entity
137
+
138
+ def initialize(entity, params, text, group: nil, label: nil)
139
+ super params, text, group: group, label: label
140
+
141
+ @entity = entity
142
+ end
143
+
144
+ # Support queries must also have their statement and index checked
145
+ def ==(other)
146
+ other.is_a?(SupportQuery) && @statement == other.statement &&
147
+ @index == other.index && @comment == other.comment
148
+ end
149
+ alias eql? ==
150
+
151
+ def hash
152
+ @hash ||= Zlib.crc32_combine super, @index.hash, @index.hash_str.length
153
+ end
154
+
155
+ # :nocov:
156
+ def to_color
157
+ super.to_color + ' for [magenta]' + @index.key + '[/]'
158
+ end
159
+ # :nocov:
160
+ end
161
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ # A representation of an update in the workload
5
+ class Update < Statement
6
+ include StatementConditions
7
+ include StatementSettings
8
+ include StatementSupportQuery
9
+
10
+ def initialize(params, text, group: nil, label: nil)
11
+ super params, text, group: group, label: label
12
+
13
+ populate_conditions params
14
+ @settings = params[:settings]
15
+ end
16
+
17
+ # Build a new update from a provided parse tree
18
+ # @return [Update]
19
+ def self.parse(tree, params, text, group: nil, label: nil)
20
+ conditions_from_tree tree, params
21
+ settings_from_tree tree, params
22
+
23
+ Update.new params, text, group: group, label: label
24
+ end
25
+
26
+ # Produce the SQL text corresponding to this update
27
+ # @return [String]
28
+ def unparse
29
+ update = "UPDATE #{entity.name} "
30
+ update += "FROM #{from_path @key_path} "
31
+ update << settings_clause
32
+ update << where_clause
33
+
34
+ update
35
+ end
36
+
37
+ def ==(other)
38
+ other.is_a?(Update) &&
39
+ @graph == other.graph &&
40
+ entity == other.entity &&
41
+ @settings == other.settings &&
42
+ @conditions == other.conditions
43
+ end
44
+ alias eql? ==
45
+
46
+ def hash
47
+ @hash ||= [@graph, entity, @settings, @conditions].hash
48
+ end
49
+
50
+ # Specifies that updates require insertion
51
+ def requires_insert?(_index)
52
+ true
53
+ end
54
+
55
+ # Specifies that updates require deletion
56
+ def requires_delete?(index)
57
+ !(settings.map(&:field).to_set &
58
+ (index.hash_fields + index.order_fields.to_set)).empty?
59
+ end
60
+
61
+ # Get the support queries for updating an index
62
+ # @return [Array<SupportQuery>]
63
+ def support_queries(index)
64
+ return [] unless modifies_index? index
65
+
66
+ # Get the updated fields and check if an update is necessary
67
+ set_fields = settings.map(&:field).to_set
68
+
69
+ # We only need to fetch all the fields if we're updating a key
70
+ updated_key = !(set_fields &
71
+ (index.hash_fields + index.order_fields)).empty?
72
+
73
+ select = if updated_key
74
+ index.all_fields
75
+ else
76
+ index.hash_fields + index.order_fields
77
+ end - set_fields - @conditions.each_value.map(&:field)
78
+ return [] if select.empty?
79
+
80
+ support_queries = []
81
+
82
+ graph = @graph.dup
83
+ support_fields = select.select do |field|
84
+ field.parent == entity
85
+ end.to_set
86
+ support_fields << entity.id_field \
87
+ unless @conditions.each_value.map(&:field).include? entity.id_field
88
+
89
+ support_queries << build_support_query(entity, index, graph,
90
+ support_fields, conditions)
91
+ support_queries.compact + support_queries_for_entity(index, select)
92
+ end
93
+
94
+ # The condition fields are provided with the update
95
+ # Note that we don't include the settings here because we
96
+ # care about the previously existing values in the database
97
+ def given_fields
98
+ @conditions.each_value.map(&:field)
99
+ end
100
+ end
101
+ end