nose 0.1.0pre

Sign up to get free protection for your applications and to get access to all the features.
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