sequel 3.34.0 → 3.35.0
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.
- data/CHANGELOG +56 -0
- data/README.rdoc +3 -1
- data/Rakefile +2 -10
- data/doc/active_record.rdoc +1 -0
- data/doc/migration.rdoc +18 -7
- data/doc/model_hooks.rdoc +6 -0
- data/doc/opening_databases.rdoc +3 -0
- data/doc/prepared_statements.rdoc +0 -1
- data/doc/release_notes/3.35.0.txt +144 -0
- data/doc/schema_modification.rdoc +16 -1
- data/doc/thread_safety.rdoc +17 -0
- data/lib/sequel/adapters/do/postgres.rb +1 -52
- data/lib/sequel/adapters/do/sqlite.rb +0 -5
- data/lib/sequel/adapters/do.rb +2 -2
- data/lib/sequel/adapters/firebird.rb +1 -1
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/db2.rb +0 -5
- data/lib/sequel/adapters/jdbc/derby.rb +29 -2
- data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
- data/lib/sequel/adapters/jdbc/h2.rb +1 -1
- data/lib/sequel/adapters/jdbc/hsqldb.rb +7 -0
- data/lib/sequel/adapters/jdbc/informix.rb +0 -5
- data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
- data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
- data/lib/sequel/adapters/jdbc/postgresql.rb +4 -35
- data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
- data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
- data/lib/sequel/adapters/jdbc/transactions.rb +4 -4
- data/lib/sequel/adapters/jdbc.rb +23 -19
- data/lib/sequel/adapters/mysql2.rb +1 -1
- data/lib/sequel/adapters/odbc/mssql.rb +14 -1
- data/lib/sequel/adapters/odbc.rb +3 -3
- data/lib/sequel/adapters/oracle.rb +6 -18
- data/lib/sequel/adapters/postgres.rb +36 -53
- data/lib/sequel/adapters/shared/db2.rb +16 -2
- data/lib/sequel/adapters/shared/mssql.rb +40 -9
- data/lib/sequel/adapters/shared/mysql.rb +16 -4
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +2 -2
- data/lib/sequel/adapters/shared/oracle.rb +2 -0
- data/lib/sequel/adapters/shared/postgres.rb +135 -211
- data/lib/sequel/adapters/sqlite.rb +2 -2
- data/lib/sequel/adapters/swift/postgres.rb +1 -71
- data/lib/sequel/adapters/swift.rb +1 -1
- data/lib/sequel/adapters/tinytds.rb +3 -3
- data/lib/sequel/core.rb +27 -4
- data/lib/sequel/database/connecting.rb +7 -8
- data/lib/sequel/database/logging.rb +6 -1
- data/lib/sequel/database/misc.rb +20 -4
- data/lib/sequel/database/query.rb +38 -18
- data/lib/sequel/database/schema_generator.rb +5 -2
- data/lib/sequel/database/schema_methods.rb +34 -8
- data/lib/sequel/dataset/prepared_statements.rb +1 -1
- data/lib/sequel/dataset/sql.rb +18 -24
- data/lib/sequel/extensions/core_extensions.rb +0 -23
- data/lib/sequel/extensions/migration.rb +22 -8
- data/lib/sequel/extensions/pg_auto_parameterize.rb +4 -0
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/model/associations.rb +95 -70
- data/lib/sequel/model/base.rb +19 -20
- data/lib/sequel/model.rb +2 -2
- data/lib/sequel/plugins/dirty.rb +214 -0
- data/lib/sequel/plugins/identity_map.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +16 -1
- data/lib/sequel/plugins/many_through_many.rb +22 -32
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +2 -2
- data/lib/sequel/plugins/prepared_statements.rb +22 -8
- data/lib/sequel/plugins/prepared_statements_associations.rb +2 -3
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/subclasses.rb +10 -2
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/xml_serializer.rb +12 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +30 -79
- data/spec/core/database_spec.rb +46 -2
- data/spec/core/dataset_spec.rb +28 -22
- data/spec/core/schema_generator_spec.rb +1 -1
- data/spec/core/schema_spec.rb +51 -0
- data/spec/extensions/arbitrary_servers_spec.rb +0 -4
- data/spec/extensions/association_autoreloading_spec.rb +17 -0
- data/spec/extensions/association_proxies_spec.rb +4 -4
- data/spec/extensions/core_extensions_spec.rb +1 -24
- data/spec/extensions/dirty_spec.rb +155 -0
- data/spec/extensions/json_serializer_spec.rb +13 -0
- data/spec/extensions/migration_spec.rb +28 -15
- data/spec/extensions/named_timezones_spec.rb +6 -8
- data/spec/extensions/pg_auto_parameterize_spec.rb +6 -5
- data/spec/extensions/schema_dumper_spec.rb +3 -1
- data/spec/extensions/single_table_inheritance_spec.rb +5 -0
- data/spec/extensions/xml_serializer_spec.rb +13 -0
- data/spec/files/{transactionless_migrations → transaction_specified_migrations}/001_create_alt_basic.rb +1 -1
- data/spec/integration/associations_test.rb +5 -7
- data/spec/integration/dataset_test.rb +25 -7
- data/spec/integration/plugin_test.rb +1 -1
- data/spec/integration/schema_test.rb +16 -1
- data/spec/model/associations_spec.rb +2 -2
- metadata +14 -9
- data/lib/sequel/adapters/odbc/db2.rb +0 -17
- /data/spec/files/{transactionless_migrations → transaction_specified_migrations}/002_create_basic.rb +0 -0
- /data/spec/files/{transaction_migrations → transaction_unspecified_migrations}/001_create_alt_basic.rb +0 -0
- /data/spec/files/{transaction_migrations → transaction_unspecified_migrations}/002_create_basic.rb +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The dirty plugin makes Sequel save the initial value of
|
|
4
|
+
# a column when setting a new value for the column. This
|
|
5
|
+
# makes it easier to see what changes were made to the object:
|
|
6
|
+
#
|
|
7
|
+
# artist.name # => 'Foo'
|
|
8
|
+
# artist.name = 'Bar'
|
|
9
|
+
# artist.initial_value(:name) # 'Foo'
|
|
10
|
+
# artist.column_change(:name) # ['Foo', 'Bar']
|
|
11
|
+
# artist.column_changes # {:name => ['Foo', 'Bar']}
|
|
12
|
+
# artist.column_changed?(:name) # true
|
|
13
|
+
# artist.reset_column(:name)
|
|
14
|
+
# artist.name # => 'Foo'
|
|
15
|
+
# artist.column_changed?(:name) # false
|
|
16
|
+
#
|
|
17
|
+
# It allows makes changed_columns more accurate in that it
|
|
18
|
+
# can detect when a the column value is changed and then
|
|
19
|
+
# changed back:
|
|
20
|
+
#
|
|
21
|
+
# artist.name # => 'Foo'
|
|
22
|
+
# artist.name = 'Bar'
|
|
23
|
+
# artist.changed_columns # => [:name]
|
|
24
|
+
# artist.name = 'Foo'
|
|
25
|
+
# artist.changed_columns # => []
|
|
26
|
+
#
|
|
27
|
+
# It can handle situations where a column value is
|
|
28
|
+
# modified in place:
|
|
29
|
+
#
|
|
30
|
+
# artist.will_change_column(:name)
|
|
31
|
+
# artist.name.gsub!(/o/, 'u')
|
|
32
|
+
# artist.changed_columns # => [:name]
|
|
33
|
+
# artist.initial_value(:name) # => 'Foo'
|
|
34
|
+
# artist.column_change(:name) # => ['Foo', 'Fuu']
|
|
35
|
+
#
|
|
36
|
+
# It also saves the previously changed values after an update:
|
|
37
|
+
#
|
|
38
|
+
# artist.update(:name=>'Bar')
|
|
39
|
+
# artist.column_changes # => {}
|
|
40
|
+
# artist.previous_changes # => {:name=>['Foo', 'Bar']}
|
|
41
|
+
#
|
|
42
|
+
# Usage:
|
|
43
|
+
#
|
|
44
|
+
# # Make all model subclass instances record previous values (called before loading subclasses)
|
|
45
|
+
# Sequel::Model.plugin :dirty
|
|
46
|
+
#
|
|
47
|
+
# # Make the Album class record previous values
|
|
48
|
+
# Album.plugin :dirty
|
|
49
|
+
module Dirty
|
|
50
|
+
module InstanceMethods
|
|
51
|
+
# A hash of previous changes before the object was
|
|
52
|
+
# saved, in the same format as #column_changes.
|
|
53
|
+
# Note that this is not necessarily the same as the columns
|
|
54
|
+
# that were used in the update statement.
|
|
55
|
+
attr_reader :previous_changes
|
|
56
|
+
|
|
57
|
+
# An array with the initial value and the current value
|
|
58
|
+
# of the column, if the column has been changed. If the
|
|
59
|
+
# column has not been changed, returns nil.
|
|
60
|
+
#
|
|
61
|
+
# column_change(:name) # => ['Initial', 'Current']
|
|
62
|
+
def column_change(column)
|
|
63
|
+
[initial_value(column), send(column)] if column_changed?(column)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# A hash with column symbol keys and pairs of initial and
|
|
67
|
+
# current values for all changed columns.
|
|
68
|
+
#
|
|
69
|
+
# column_changes # => {:name => ['Initial', 'Current']}
|
|
70
|
+
def column_changes
|
|
71
|
+
h = {}
|
|
72
|
+
initial_values.each do |column, value|
|
|
73
|
+
h[column] = [value, send(column)]
|
|
74
|
+
end
|
|
75
|
+
h
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Either true or false depending on whether the column has
|
|
79
|
+
# changed. Note that this is not exactly the same as checking if
|
|
80
|
+
# the column is in changed_columns, if the column was not set
|
|
81
|
+
# initially.
|
|
82
|
+
#
|
|
83
|
+
# column_changed?(:name) # => true
|
|
84
|
+
def column_changed?(column)
|
|
85
|
+
initial_values.has_key?(column)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The initial value of the given column. If the column value has
|
|
89
|
+
# not changed, this will be the same as the current value of the
|
|
90
|
+
# column.
|
|
91
|
+
#
|
|
92
|
+
# initial_value(:name) # => 'Initial'
|
|
93
|
+
def initial_value(column)
|
|
94
|
+
initial_values.fetch(column){send(column)}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# A hash with column symbol keys and initial values.
|
|
98
|
+
#
|
|
99
|
+
# initial_values # {:name => 'Initial'}
|
|
100
|
+
def initial_values
|
|
101
|
+
@initial_values ||= {}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Reset the column to its initial value. If the column was not set
|
|
105
|
+
# initial, removes it from the values.
|
|
106
|
+
#
|
|
107
|
+
# reset_column(:name)
|
|
108
|
+
# name # => 'Initial'
|
|
109
|
+
def reset_column(column)
|
|
110
|
+
if initial_values.has_key?(column)
|
|
111
|
+
send(:"#{column}=", initial_values[column])
|
|
112
|
+
end
|
|
113
|
+
if missing_initial_values.include?(column)
|
|
114
|
+
values.delete(column)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Manually specify that a column will change. This should only be used
|
|
119
|
+
# if you plan to modify a column value in place, which is not recommended.
|
|
120
|
+
#
|
|
121
|
+
# will_change_column(:name)
|
|
122
|
+
# name.gsub(/i/i, 'o')
|
|
123
|
+
# column_change(:name) # => ['Initial', 'onotoal']
|
|
124
|
+
def will_change_column(column)
|
|
125
|
+
changed_columns << column unless changed_columns.include?(column)
|
|
126
|
+
check_missing_initial_value(column)
|
|
127
|
+
|
|
128
|
+
value = if initial_values.has_key?(column)
|
|
129
|
+
initial_values[column]
|
|
130
|
+
else
|
|
131
|
+
send(column)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
initial_values[column] = if value && value != true && value.respond_to?(:clone)
|
|
135
|
+
begin
|
|
136
|
+
value.clone
|
|
137
|
+
rescue TypeError
|
|
138
|
+
value
|
|
139
|
+
end
|
|
140
|
+
else
|
|
141
|
+
value
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Reset the initial values after saving.
|
|
148
|
+
def after_save
|
|
149
|
+
super
|
|
150
|
+
reset_initial_values
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Save the current changes so they are available after updating. This happens
|
|
154
|
+
# before after_save resets them.
|
|
155
|
+
def after_update
|
|
156
|
+
super
|
|
157
|
+
@previous_changes = column_changes
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Reset the initial values when refreshing.
|
|
161
|
+
def _refresh(dataset)
|
|
162
|
+
super
|
|
163
|
+
reset_initial_values
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# When changing the column value, save the initial column value. If the column
|
|
167
|
+
# value is changed back to the initial value, update changed columns to remove
|
|
168
|
+
# the column.
|
|
169
|
+
def change_column_value(column, value)
|
|
170
|
+
if (iv = initial_values).has_key?(column)
|
|
171
|
+
initial = iv[column]
|
|
172
|
+
super
|
|
173
|
+
if value == initial
|
|
174
|
+
changed_columns.delete(column) unless missing_initial_values.include?(column)
|
|
175
|
+
iv.delete(column)
|
|
176
|
+
end
|
|
177
|
+
else
|
|
178
|
+
check_missing_initial_value(column)
|
|
179
|
+
iv[column] = send(column)
|
|
180
|
+
super
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# If the values hash does not contain the column, make sure missing_initial_values
|
|
185
|
+
# does so that it doesn't get deleted from changed_columns if changed back,
|
|
186
|
+
# and so that reseting the column value can be handled correctly.
|
|
187
|
+
def check_missing_initial_value(column)
|
|
188
|
+
unless values.has_key?(column) || (miv = missing_initial_values).include?(column)
|
|
189
|
+
miv << column
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Reset the initial values when initializing.
|
|
194
|
+
def initialize_set(h)
|
|
195
|
+
super
|
|
196
|
+
reset_initial_values
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Array holding column symbols that were not present initially. This is necessary
|
|
200
|
+
# to differentiate between values that were not present and values that were
|
|
201
|
+
# present but equal to nil.
|
|
202
|
+
def missing_initial_values
|
|
203
|
+
@missing_initial_values ||= []
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Clear the data structures that store the initial values.
|
|
207
|
+
def reset_initial_values
|
|
208
|
+
@initial_values.clear if @initial_values
|
|
209
|
+
@missing_initial_values.clear if @missing_initial_values
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -111,7 +111,7 @@ module Sequel
|
|
|
111
111
|
eo[:rows].each{|object| object.associations[name] = []}
|
|
112
112
|
ds = opts.associated_class
|
|
113
113
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
114
|
-
ft = opts
|
|
114
|
+
ft = opts.final_reverse_edge
|
|
115
115
|
conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, h.keys]] : [[left_key, h.keys]]
|
|
116
116
|
|
|
117
117
|
# See above comment in many_to_many eager_loader
|
|
@@ -56,6 +56,11 @@ module Sequel
|
|
|
56
56
|
# Album.to_json
|
|
57
57
|
# Album.filter(:artist_id=>1).to_json(:include=>:tags)
|
|
58
58
|
#
|
|
59
|
+
# If you have an existing array of model instances you want to convert to
|
|
60
|
+
# JSON, you can call the class to_json method with the :array option:
|
|
61
|
+
#
|
|
62
|
+
# Album.to_json(:array=>[Album[1], Album[2]])
|
|
63
|
+
#
|
|
59
64
|
# Usage:
|
|
60
65
|
#
|
|
61
66
|
# # Add JSON output capability to all model subclass instances (called before loading subclasses)
|
|
@@ -203,7 +208,17 @@ module Sequel
|
|
|
203
208
|
else
|
|
204
209
|
opts = model.json_serializer_opts
|
|
205
210
|
end
|
|
206
|
-
res = row_proc
|
|
211
|
+
res = if row_proc
|
|
212
|
+
array = if opts[:array]
|
|
213
|
+
opts = opts.dup
|
|
214
|
+
opts.delete(:array)
|
|
215
|
+
else
|
|
216
|
+
all
|
|
217
|
+
end
|
|
218
|
+
array.map{|obj| Literal.new(obj.to_json(opts))}
|
|
219
|
+
else
|
|
220
|
+
all
|
|
221
|
+
end
|
|
207
222
|
opts[:root] ? {model.send(:pluralize, model.send(:underscore, model.to_s)) => res}.to_json(*a) : res.to_json(*a)
|
|
208
223
|
end
|
|
209
224
|
end
|
|
@@ -47,30 +47,18 @@ module Sequel
|
|
|
47
47
|
class ManyThroughManyAssociationReflection < Sequel::Model::Associations::ManyToManyAssociationReflection
|
|
48
48
|
Sequel::Model::Associations::ASSOCIATION_TYPES[:many_through_many] = self
|
|
49
49
|
|
|
50
|
-
# The table containing the column to use for the associated key when eagerly loading
|
|
51
|
-
def associated_key_table
|
|
52
|
-
self[:associated_key_table] = self[:final_reverse_edge][:alias]
|
|
53
|
-
end
|
|
54
|
-
|
|
55
50
|
# The default associated key alias(es) to use when eager loading
|
|
56
51
|
# associations via eager.
|
|
57
52
|
def default_associated_key_alias
|
|
58
53
|
self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
|
|
59
54
|
end
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
qualify(f[:alias], e[:right])
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# The list of joins to use when eager graphing
|
|
72
|
-
def edges
|
|
73
|
-
self[:edges] || calculate_edges || self[:edges]
|
|
56
|
+
%w'associated_key_table eager_loading_predicate_key edges final_edge final_reverse_edge reverse_edges'.each do |meth|
|
|
57
|
+
class_eval(<<-END, __FILE__, __LINE__+1)
|
|
58
|
+
def #{meth}
|
|
59
|
+
cached_fetch(:#{meth}){calculate_edges[:#{meth}]}
|
|
60
|
+
end
|
|
61
|
+
END
|
|
74
62
|
end
|
|
75
63
|
|
|
76
64
|
# Many through many associations don't have a reciprocal
|
|
@@ -78,11 +66,6 @@ module Sequel
|
|
|
78
66
|
nil
|
|
79
67
|
end
|
|
80
68
|
|
|
81
|
-
# The list of joins to use when lazy loading or eager loading
|
|
82
|
-
def reverse_edges
|
|
83
|
-
self[:reverse_edges] || calculate_edges || self[:reverse_edges]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
69
|
private
|
|
87
70
|
|
|
88
71
|
# Make sure to use unique table aliases when lazy loading or eager loading
|
|
@@ -119,11 +102,18 @@ module Sequel
|
|
|
119
102
|
reverse_edges = es.reverse.map{|e| {:table=>e[:left_table], :left=>e[:left_key], :right=>e[:right_key]}}
|
|
120
103
|
reverse_edges.pop
|
|
121
104
|
calculate_reverse_edge_aliases(reverse_edges)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
105
|
+
final_reverse_edge = reverse_edges.pop
|
|
106
|
+
final_reverse_alias = final_reverse_edge[:alias]
|
|
107
|
+
|
|
108
|
+
h = {:final_edge=>edges.pop,
|
|
109
|
+
:final_reverse_edge=>final_reverse_edge,
|
|
110
|
+
:edges=>edges,
|
|
111
|
+
:reverse_edges=>reverse_edges,
|
|
112
|
+
:eager_loading_predicate_key=>qualify(final_reverse_alias, edges.first[:right]),
|
|
113
|
+
:associated_key_table=>final_reverse_edge[:alias],
|
|
114
|
+
}
|
|
115
|
+
h.each{|k, v| cached_set(k, v)}
|
|
116
|
+
h
|
|
127
117
|
end
|
|
128
118
|
end
|
|
129
119
|
|
|
@@ -185,7 +175,7 @@ module Sequel
|
|
|
185
175
|
opts[:dataset] ||= lambda do
|
|
186
176
|
ds = opts.associated_class
|
|
187
177
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
188
|
-
ft = opts
|
|
178
|
+
ft = opts.final_reverse_edge
|
|
189
179
|
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias])
|
|
190
180
|
end
|
|
191
181
|
|
|
@@ -196,7 +186,7 @@ module Sequel
|
|
|
196
186
|
rows.each{|object| object.associations[name] = []}
|
|
197
187
|
ds = opts.associated_class
|
|
198
188
|
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
199
|
-
ft = opts
|
|
189
|
+
ft = opts.final_reverse_edge
|
|
200
190
|
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.eager_loading_predicate_key, h.keys]], :table_alias=>ft[:alias])
|
|
201
191
|
ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
|
|
202
192
|
case opts.eager_limit_strategy
|
|
@@ -240,7 +230,7 @@ module Sequel
|
|
|
240
230
|
ds = ds.graph(t[:table], t.fetch(:only_conditions, (Array(t[:right]).zip(Array(t[:left])) + t[:conditions])), :select=>false, :table_alias=>ds.unused_table_alias(t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
|
|
241
231
|
iq = nil
|
|
242
232
|
end
|
|
243
|
-
fe = opts
|
|
233
|
+
fe = opts.final_edge
|
|
244
234
|
ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :join_type=>join_type, &graph_block)
|
|
245
235
|
end
|
|
246
236
|
|
|
@@ -269,7 +259,7 @@ module Sequel
|
|
|
269
259
|
end
|
|
270
260
|
meths = ref.right_primary_keys
|
|
271
261
|
meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
|
|
272
|
-
exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref
|
|
262
|
+
exp = association_filter_key_expression(ref.qualify(last_alias, Array(ref.final_edge[:left])), meths, obj)
|
|
273
263
|
if exp == SQL::Constants::FALSE
|
|
274
264
|
association_filter_handle_inversion(op, exp, Array(lpks))
|
|
275
265
|
else
|
|
@@ -38,8 +38,8 @@ module Sequel
|
|
|
38
38
|
# caching if the associated model is using caching.
|
|
39
39
|
def _load_associated_object(opts, dynamic_opts)
|
|
40
40
|
klass = opts.associated_class
|
|
41
|
-
cache_lookup = opts.
|
|
42
|
-
opts[:
|
|
41
|
+
cache_lookup = opts.send(:cached_fetch, :many_to_one_pk_lookup) do
|
|
42
|
+
opts[:type] == :many_to_one &&
|
|
43
43
|
opts[:key] &&
|
|
44
44
|
opts.primary_key == klass.primary_key
|
|
45
45
|
end
|
|
@@ -31,14 +31,14 @@ module Sequel
|
|
|
31
31
|
|
|
32
32
|
# Setup the datastructure used to hold the prepared statements in the model.
|
|
33
33
|
def self.apply(model)
|
|
34
|
-
model.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{})
|
|
34
|
+
model.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{}, :fixed=>{})
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
module ClassMethods
|
|
38
38
|
# Setup the datastructure used to hold the prepared statements in the subclass.
|
|
39
39
|
def inherited(subclass)
|
|
40
40
|
super
|
|
41
|
-
subclass.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{})
|
|
41
|
+
subclass.instance_variable_set(:@prepared_statements, :insert=>{}, :insert_select=>{}, :update=>{}, :lookup_sql=>{}, :fixed=>{})
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
private
|
|
@@ -58,30 +58,30 @@ module Sequel
|
|
|
58
58
|
|
|
59
59
|
# Return a prepared statement that can be used to delete a row from this model's dataset.
|
|
60
60
|
def prepared_delete
|
|
61
|
-
|
|
61
|
+
cached_prepared_statement(:fixed, :delete){prepare_statement(filter(prepared_statement_key_array(primary_key)), :delete)}
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
# Return a prepared statement that can be used to insert a row using the given columns.
|
|
65
65
|
def prepared_insert(cols)
|
|
66
|
-
|
|
66
|
+
cached_prepared_statement(:insert, prepared_columns(cols)){prepare_statement(dataset, :insert, prepared_statement_key_hash(cols))}
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
# Return a prepared statement that can be used to insert a row using the given columns
|
|
70
70
|
# and return that column values for the row created.
|
|
71
71
|
def prepared_insert_select(cols)
|
|
72
72
|
if dataset.supports_insert_select?
|
|
73
|
-
|
|
73
|
+
cached_prepared_statement(:insert_select, prepared_columns(cols)){prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)), :insert_select, prepared_statement_key_hash(cols))}
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
# Return a prepared statement that can be used to lookup a row solely based on the primary key.
|
|
78
78
|
def prepared_lookup
|
|
79
|
-
|
|
79
|
+
cached_prepared_statement(:fixed, :lookup){prepare_statement(filter(prepared_statement_key_array(primary_key)), :first)}
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
# Return a prepared statement that can be used to refresh a row to get new column values after insertion.
|
|
83
83
|
def prepared_refresh
|
|
84
|
-
|
|
84
|
+
cached_prepared_statement(:fixed, :refresh){prepare_statement(naked.clone(:server=>dataset.opts.fetch(:server, :default)).filter(prepared_statement_key_array(primary_key)), :first)}
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
# Return an array of two element arrays with the column symbol as the first entry and the
|
|
@@ -108,13 +108,27 @@ module Sequel
|
|
|
108
108
|
|
|
109
109
|
# Return a prepared statement that can be used to update row using the given columns.
|
|
110
110
|
def prepared_update(cols)
|
|
111
|
-
|
|
111
|
+
cached_prepared_statement(:update, prepared_columns(cols)){prepare_statement(filter(prepared_statement_key_array(primary_key)), :update, prepared_statement_key_hash(cols))}
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
# Use a prepared statement to query the database for the row matching the given primary key.
|
|
115
115
|
def primary_key_lookup(pk)
|
|
116
116
|
prepared_lookup.call(primary_key_hash(pk))
|
|
117
117
|
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
# If a prepared statement has already been cached for the given type and subtype,
|
|
122
|
+
# return it. Otherwise, yield to the block to get the prepared statement, and cache it.
|
|
123
|
+
def cached_prepared_statement(type, subtype)
|
|
124
|
+
h = @prepared_statements[type]
|
|
125
|
+
Sequel.synchronize do
|
|
126
|
+
if v = h[subtype]
|
|
127
|
+
return v end
|
|
128
|
+
end
|
|
129
|
+
ps = yield
|
|
130
|
+
Sequel.synchronize{h[subtype] = ps}
|
|
131
|
+
end
|
|
118
132
|
end
|
|
119
133
|
|
|
120
134
|
module InstanceMethods
|
|
@@ -53,8 +53,7 @@ module Sequel
|
|
|
53
53
|
when :many_to_many
|
|
54
54
|
association_bound_variable_hash(opts.join_table_alias, opts[:left_keys], opts[:left_primary_keys])
|
|
55
55
|
when :many_through_many
|
|
56
|
-
opts.
|
|
57
|
-
association_bound_variable_hash(opts[:final_reverse_edge][:alias], Array(opts[:left_key]), opts[:left_primary_keys])
|
|
56
|
+
association_bound_variable_hash(opts.final_reverse_edge[:alias], Array(opts[:left_key]), opts[:left_primary_keys])
|
|
58
57
|
end
|
|
59
58
|
end
|
|
60
59
|
|
|
@@ -62,7 +61,7 @@ module Sequel
|
|
|
62
61
|
# that, given appropriate bound variables, the prepared statement will work correctly for any
|
|
63
62
|
# instance.
|
|
64
63
|
def association_prepared_statement(opts)
|
|
65
|
-
opts
|
|
64
|
+
opts.send(:cached_fetch, :prepared_statement) do
|
|
66
65
|
ps = _associated_dataset(opts, {}).unbind.first.prepare(opts.returns_array? ? :select : :first, :"smpsap_#{NEXT.call}")
|
|
67
66
|
ps.log_sql = true
|
|
68
67
|
ps
|
|
@@ -31,7 +31,7 @@ module Sequel
|
|
|
31
31
|
# Return a prepared statement that can be used to lookup a row given a dataset for the row matching
|
|
32
32
|
# the primary key.
|
|
33
33
|
def prepared_lookup_dataset(ds)
|
|
34
|
-
|
|
34
|
+
cached_prepared_statement(:lookup_sql, ds.sql){prepare_statement(ds.filter(prepared_statement_key_array(primary_key).map{|k, v| [SQL::QualifiedIdentifier.new(ds.model.table_name, k), v]}), :first)}
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
@@ -28,7 +28,7 @@ module Sequel
|
|
|
28
28
|
|
|
29
29
|
# All descendent classes of this model.
|
|
30
30
|
def descendents
|
|
31
|
-
|
|
31
|
+
Sequel.synchronize{_descendents}
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Add the subclass to this model's current subclasses,
|
|
@@ -36,9 +36,17 @@ module Sequel
|
|
|
36
36
|
# in the subclass.
|
|
37
37
|
def inherited(subclass)
|
|
38
38
|
super
|
|
39
|
-
subclasses << subclass
|
|
39
|
+
Sequel.synchronize{subclasses << subclass}
|
|
40
40
|
subclass.instance_variable_set(:@subclasses, [])
|
|
41
41
|
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Recursive, non-thread safe version of descendents, since
|
|
46
|
+
# the mutex Sequel uses isn't reentrant.
|
|
47
|
+
def _descendents
|
|
48
|
+
subclasses.map{|x| [x] + x.send(:_descendents)}.flatten
|
|
49
|
+
end
|
|
42
50
|
end
|
|
43
51
|
end
|
|
44
52
|
end
|
|
@@ -19,7 +19,7 @@ module Sequel
|
|
|
19
19
|
# # timestamp, and setting the update timestamp when creating
|
|
20
20
|
# Album.plugin :timestamps, :force=>true, :update_on_create=>true
|
|
21
21
|
module Timestamps
|
|
22
|
-
# Configure the plugin by setting the
|
|
22
|
+
# Configure the plugin by setting the available options. Note that
|
|
23
23
|
# if this method is run more than once, previous settings are ignored,
|
|
24
24
|
# and it will just use the settings given or the default settings. Options:
|
|
25
25
|
# * :create - The field to hold the create timestamp (default: :created_at)
|
|
@@ -79,6 +79,11 @@ module Sequel
|
|
|
79
79
|
#
|
|
80
80
|
# Album.array_from_xml(Album.to_xml) # same as Album.all
|
|
81
81
|
#
|
|
82
|
+
# If you have an existing array of model instances you want to convert to
|
|
83
|
+
# XML, you can call the class to_xml method with the :array option:
|
|
84
|
+
#
|
|
85
|
+
# Album.to_xml(:array=>[Album[1], Album[2]])
|
|
86
|
+
#
|
|
82
87
|
# Usage:
|
|
83
88
|
#
|
|
84
89
|
# # Add XML output capability to all model subclass instances (called before loading subclasses)
|
|
@@ -316,8 +321,14 @@ module Sequel
|
|
|
316
321
|
raise(Sequel::Error, "Dataset#to_xml") unless row_proc
|
|
317
322
|
x = model.xml_builder(opts)
|
|
318
323
|
name_proc = model.xml_serialize_name_proc(opts)
|
|
324
|
+
array = if opts[:array]
|
|
325
|
+
opts = opts.dup
|
|
326
|
+
opts.delete(:array)
|
|
327
|
+
else
|
|
328
|
+
all
|
|
329
|
+
end
|
|
319
330
|
x.send(name_proc[opts.fetch(:array_root_name, model.send(:pluralize, model.send(:underscore, model.name))).to_s]) do |x1|
|
|
320
|
-
|
|
331
|
+
array.each do |obj|
|
|
321
332
|
obj.to_xml(opts.merge(:builder=>x1))
|
|
322
333
|
end
|
|
323
334
|
end
|
data/lib/sequel/sql.rb
CHANGED
|
@@ -138,7 +138,7 @@ module Sequel
|
|
|
138
138
|
MATHEMATICAL_OPERATORS = [:+, :-, :/, :*]
|
|
139
139
|
|
|
140
140
|
# Bitwise mathematical operators used in +NumericMethods+
|
|
141
|
-
BITWISE_OPERATORS = [:&, :|, :^, :<<,
|
|
141
|
+
BITWISE_OPERATORS = [:&, :|, :^, :<<, :>>, :%]
|
|
142
142
|
|
|
143
143
|
# Inequality operators used in +InequalityMethods+
|
|
144
144
|
INEQUALITY_OPERATORS = [:<, :>, :<=, :>=]
|
data/lib/sequel/version.rb
CHANGED
|
@@ -3,7 +3,7 @@ module Sequel
|
|
|
3
3
|
MAJOR = 3
|
|
4
4
|
# The minor version of Sequel. Bumped for every non-patch level
|
|
5
5
|
# release, generally around once a month.
|
|
6
|
-
MINOR =
|
|
6
|
+
MINOR = 35
|
|
7
7
|
# The tiny version of Sequel. Usually 0, only bumped for bugfix
|
|
8
8
|
# releases that fix regressions from previous versions.
|
|
9
9
|
TINY = 0
|