sequel 5.51.0 → 5.54.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.
- checksums.yaml +4 -4
- data/CHANGELOG +44 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -0
- data/doc/opening_databases.rdoc +1 -1
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/testing.rdoc +3 -1
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/jdbc/h2.rb +55 -10
- data/lib/sequel/adapters/jdbc.rb +12 -14
- data/lib/sequel/adapters/mysql.rb +80 -67
- data/lib/sequel/adapters/mysql2.rb +53 -48
- data/lib/sequel/adapters/postgres.rb +17 -21
- data/lib/sequel/adapters/shared/mysql.rb +3 -2
- data/lib/sequel/adapters/shared/postgres.rb +2 -2
- data/lib/sequel/adapters/sqlite.rb +16 -18
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- data/lib/sequel/connection_pool/single.rb +6 -8
- data/lib/sequel/core.rb +17 -18
- data/lib/sequel/database/query.rb +1 -1
- data/lib/sequel/extensions/core_refinements.rb +36 -11
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/pg_array_ops.rb +2 -2
- data/lib/sequel/extensions/pg_hstore_ops.rb +1 -1
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +1 -0
- data/lib/sequel/extensions/pg_json.rb +3 -5
- data/lib/sequel/extensions/pg_json_ops.rb +1 -1
- data/lib/sequel/extensions/pg_range_ops.rb +1 -1
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/server_block.rb +8 -12
- data/lib/sequel/extensions/sql_comments.rb +110 -3
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/model/base.rb +8 -12
- data/lib/sequel/plugins/column_encryption.rb +1 -1
- data/lib/sequel/plugins/enum.rb +124 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/unused_associations.rb +2 -2
- data/lib/sequel/timezones.rb +12 -14
- data/lib/sequel/version.rb +1 -1
- metadata +12 -3
@@ -88,12 +88,10 @@ module Sequel
|
|
88
88
|
module UnthreadedServerBlock
|
89
89
|
# Set a default server/shard to use inside the block.
|
90
90
|
def with_server(default_server, read_only_server=default_server)
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
clear_default_server
|
96
|
-
end
|
91
|
+
set_default_server(default_server, read_only_server)
|
92
|
+
yield
|
93
|
+
ensure
|
94
|
+
clear_default_server
|
97
95
|
end
|
98
96
|
|
99
97
|
private
|
@@ -131,12 +129,10 @@ module Sequel
|
|
131
129
|
# Set a default server/shard to use inside the block for the current
|
132
130
|
# thread.
|
133
131
|
def with_server(default_server, read_only_server=default_server)
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
clear_default_server
|
139
|
-
end
|
132
|
+
set_default_server(default_server, read_only_server)
|
133
|
+
yield
|
134
|
+
ensure
|
135
|
+
clear_default_server
|
140
136
|
end
|
141
137
|
|
142
138
|
private
|
@@ -44,11 +44,51 @@
|
|
44
44
|
#
|
45
45
|
# DB.extension(:sql_comments)
|
46
46
|
#
|
47
|
+
# Loading the sql_comments extension into the database also adds
|
48
|
+
# support for block-level comment support via Database#with_comments.
|
49
|
+
# You call #with_comments with a hash. Queries inside the hash will
|
50
|
+
# include a comment based on the hash (assuming they are inside the
|
51
|
+
# same thread):
|
52
|
+
#
|
53
|
+
# DB.with_comments(model: Album, action: :all) do
|
54
|
+
# DB[:albums].all
|
55
|
+
# # SELECT * FROM albums -- model:Album,action:all
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# You can nest calls to #with_comments, which will combine the
|
59
|
+
# entries from both calls:
|
60
|
+
#
|
61
|
+
# DB.with_comments(application: App, path: :scrubbed_path) do
|
62
|
+
# DB.with_comments(model: Album, action: :all) do
|
63
|
+
# ds = DB[:albums].all
|
64
|
+
# # SELECT * FROM albums
|
65
|
+
# # -- application:App,path:scrubbed_path,model:Album,action:all
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# You can override comment entries specified in earlier blocks, or
|
70
|
+
# remove entries specified earlier using a nil value:
|
71
|
+
#
|
72
|
+
# DB.with_comments(application: App, path: :scrubbed_path) do
|
73
|
+
# DB.with_comments(application: Foo, path: nil) do
|
74
|
+
# ds = DB[:albums].all
|
75
|
+
# # SELECT * FROM albums # -- application:Foo
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# You can combine block-level comments with dataset-specific
|
80
|
+
# comments:
|
81
|
+
#
|
82
|
+
# DB.with_comments(model: Album, action: :all) do
|
83
|
+
# DB[:table].comment("Some Comment").all
|
84
|
+
# # SELECT * FROM albums -- model:Album,action:all -- Some Comment
|
85
|
+
# end
|
86
|
+
#
|
47
87
|
# Note that Microsoft Access does not support inline comments,
|
48
88
|
# and attempting to use comments on it will result in SQL syntax
|
49
89
|
# errors.
|
50
90
|
#
|
51
|
-
# Related
|
91
|
+
# Related modules: Sequel::SQLComments, Sequel::Database::SQLComments
|
52
92
|
|
53
93
|
#
|
54
94
|
module Sequel
|
@@ -62,7 +102,7 @@ module Sequel
|
|
62
102
|
%w'select insert update delete'.each do |type|
|
63
103
|
define_method(:"#{type}_sql") do |*a|
|
64
104
|
sql = super(*a)
|
65
|
-
if comment =
|
105
|
+
if comment = _sql_comment
|
66
106
|
# This assumes that the comment stored in the dataset has
|
67
107
|
# already been formatted. If not, this could result in SQL
|
68
108
|
# injection.
|
@@ -74,8 +114,10 @@ module Sequel
|
|
74
114
|
if sql.frozen?
|
75
115
|
sql += comment
|
76
116
|
sql.freeze
|
77
|
-
|
117
|
+
elsif @opts[:append_sql] || @opts[:placeholder_literalizer]
|
78
118
|
sql << comment
|
119
|
+
else
|
120
|
+
sql += comment
|
79
121
|
end
|
80
122
|
end
|
81
123
|
sql
|
@@ -84,6 +126,11 @@ module Sequel
|
|
84
126
|
|
85
127
|
private
|
86
128
|
|
129
|
+
# The comment to include in the SQL query, if any.
|
130
|
+
def _sql_comment
|
131
|
+
@opts[:comment]
|
132
|
+
end
|
133
|
+
|
87
134
|
# Format the comment. For maximum compatibility, this uses a
|
88
135
|
# single line SQL comment, and converts all consecutive whitespace
|
89
136
|
# in the comment to a single space.
|
@@ -92,5 +139,65 @@ module Sequel
|
|
92
139
|
end
|
93
140
|
end
|
94
141
|
|
142
|
+
module Database::SQLComments
|
143
|
+
def self.extended(db)
|
144
|
+
db.instance_variable_set(:@comment_hashes, {})
|
145
|
+
db.extend_datasets DatasetSQLComments
|
146
|
+
end
|
147
|
+
|
148
|
+
# A map of threads to comment hashes, used for correctly setting
|
149
|
+
# comments for all queries inside #with_comments blocks.
|
150
|
+
attr_reader :comment_hashes
|
151
|
+
|
152
|
+
# Store the comment hash and use it to create comments inside the block
|
153
|
+
def with_comments(comment_hash)
|
154
|
+
hashes = @comment_hashes
|
155
|
+
t = Sequel.current
|
156
|
+
new_hash = if hash = Sequel.synchronize{hashes[t]}
|
157
|
+
hash.merge(comment_hash)
|
158
|
+
else
|
159
|
+
comment_hash.dup
|
160
|
+
end
|
161
|
+
yield Sequel.synchronize{hashes[t] = new_hash}
|
162
|
+
ensure
|
163
|
+
if hash
|
164
|
+
Sequel.synchronize{hashes[t] = hash}
|
165
|
+
else
|
166
|
+
t && Sequel.synchronize{hashes.delete(t)}
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
module DatasetSQLComments
|
171
|
+
include Sequel::SQLComments
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
# Include comments added via Database#with_comments in the output SQL.
|
176
|
+
def _sql_comment
|
177
|
+
specific_comment = super
|
178
|
+
return specific_comment if @opts[:append_sql]
|
179
|
+
|
180
|
+
t = Sequel.current
|
181
|
+
hashes = db.comment_hashes
|
182
|
+
block_comment = if comment_hash = Sequel.synchronize{hashes[t]}
|
183
|
+
comment_array = comment_hash.map{|k,v| "#{k}:#{v}" unless v.nil?}
|
184
|
+
comment_array.compact!
|
185
|
+
comment_array.join(",")
|
186
|
+
end
|
187
|
+
|
188
|
+
if block_comment
|
189
|
+
if specific_comment
|
190
|
+
format_sql_comment(block_comment + specific_comment)
|
191
|
+
else
|
192
|
+
format_sql_comment(block_comment)
|
193
|
+
end
|
194
|
+
else
|
195
|
+
specific_comment
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
95
201
|
Dataset.register_extension(:sql_comments, SQLComments)
|
202
|
+
Database.register_extension(:sql_comments, Database::SQLComments)
|
96
203
|
end
|
@@ -4,6 +4,10 @@
|
|
4
4
|
# for converting the strings to a date (e.g. String#to_date), allowing
|
5
5
|
# for backwards compatibility with legacy Sequel code.
|
6
6
|
#
|
7
|
+
# These methods calls +parse+ on the related class, and as such, can
|
8
|
+
# result in denial of service in older versions of Ruby for large
|
9
|
+
# untrusted input, and raise exceptions in newer versions of Ruby.
|
10
|
+
#
|
7
11
|
# To load the extension:
|
8
12
|
#
|
9
13
|
# Sequel.extension :string_date_time
|
@@ -11,42 +15,34 @@
|
|
11
15
|
class String
|
12
16
|
# Converts a string into a Date object.
|
13
17
|
def to_date
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
18
|
-
end
|
18
|
+
Date.parse(self, Sequel.convert_two_digit_years)
|
19
|
+
rescue => e
|
20
|
+
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
19
21
|
end
|
20
22
|
|
21
23
|
# Converts a string into a DateTime object.
|
22
24
|
def to_datetime
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
27
|
-
end
|
25
|
+
DateTime.parse(self, Sequel.convert_two_digit_years)
|
26
|
+
rescue => e
|
27
|
+
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
28
28
|
end
|
29
29
|
|
30
30
|
# Converts a string into a Time or DateTime object, depending on the
|
31
31
|
# value of Sequel.datetime_class
|
32
32
|
def to_sequel_time
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
Sequel.datetime_class.parse(self)
|
38
|
-
end
|
39
|
-
rescue => e
|
40
|
-
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
33
|
+
if Sequel.datetime_class == DateTime
|
34
|
+
DateTime.parse(self, Sequel.convert_two_digit_years)
|
35
|
+
else
|
36
|
+
Sequel.datetime_class.parse(self)
|
41
37
|
end
|
38
|
+
rescue => e
|
39
|
+
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
42
40
|
end
|
43
41
|
|
44
42
|
# Converts a string into a Time object.
|
45
43
|
def to_time
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
50
|
-
end
|
44
|
+
Time.parse(self)
|
45
|
+
rescue => e
|
46
|
+
raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
|
51
47
|
end
|
52
48
|
end
|
data/lib/sequel/model/base.rb
CHANGED
@@ -682,13 +682,11 @@ module Sequel
|
|
682
682
|
|
683
683
|
# Yield to the passed block and if do_raise is false, swallow all errors other than DatabaseConnectionErrors.
|
684
684
|
def check_non_connection_error(do_raise=require_valid_table)
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
raise if do_raise
|
691
|
-
end
|
685
|
+
db.transaction(:savepoint=>:only){yield}
|
686
|
+
rescue Sequel::DatabaseConnectionError
|
687
|
+
raise
|
688
|
+
rescue Sequel::Error
|
689
|
+
raise if do_raise
|
692
690
|
end
|
693
691
|
|
694
692
|
# Convert the given object to a Dataset that should be used as
|
@@ -1630,11 +1628,9 @@ module Sequel
|
|
1630
1628
|
# artist.set(name: 'Invalid').valid? # => false
|
1631
1629
|
# artist.errors.full_messages # => ['name cannot be Invalid']
|
1632
1630
|
def valid?(opts = OPTS)
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
false
|
1637
|
-
end
|
1631
|
+
_valid?(opts)
|
1632
|
+
rescue HookFailed
|
1633
|
+
false
|
1638
1634
|
end
|
1639
1635
|
|
1640
1636
|
private
|
@@ -356,7 +356,7 @@ module Sequel
|
|
356
356
|
|
357
357
|
# Keys should be an array of arrays containing key_id, key string, auth_data, and padding.
|
358
358
|
def initialize(keys)
|
359
|
-
if keys.empty?
|
359
|
+
if !keys || keys.empty?
|
360
360
|
raise Error, "Cannot initialize encryptor without encryption key"
|
361
361
|
end
|
362
362
|
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The enum plugin allows for easily adding methods to modify the value of
|
6
|
+
# a column. It allows treating the column itself as an enum, returning a
|
7
|
+
# symbol for the related enum value. It also allows for setting up dataset
|
8
|
+
# methods to easily find records having or not having each enum value.
|
9
|
+
#
|
10
|
+
# After loading the plugin, you can call the +enum+ method to define the
|
11
|
+
# methods. The +enum+ method accepts a symbol for the underlying
|
12
|
+
# database column, and a hash with symbol keys for the enum values.
|
13
|
+
# For example, the following call:
|
14
|
+
#
|
15
|
+
# Album.enum :status_id, good: 1, bad: 2
|
16
|
+
#
|
17
|
+
# Will define the following instance methods:
|
18
|
+
#
|
19
|
+
# Album#good! :: Change +status_id+ to +1+ (does not save the receiver)
|
20
|
+
# Album#bad! :: Change +status_id+ to +2+ (does not save the receiver)
|
21
|
+
# Album#good? :: Return whether +status_id+ is +1+
|
22
|
+
# Album#bad? :: Return whether +status_id+ is +2+
|
23
|
+
#
|
24
|
+
# It will override the following instance methods:
|
25
|
+
#
|
26
|
+
# Album#status_id :: Return +:good+/+:bad+ instead of +1+/+2+ (other values returned as-is)
|
27
|
+
# Album#status_id= :: Allow calling with +:good+/+:bad+ to set +status_id+ to +1+/+2+ (other values,
|
28
|
+
# such as <tt>'good'</tt>/<tt>'bad'</tt> set as-is)
|
29
|
+
#
|
30
|
+
# If will define the following dataset methods:
|
31
|
+
#
|
32
|
+
# Album.dataset.good :: Return a dataset filtered to rows where +status_id+ is +1+
|
33
|
+
# Album.dataset.not_good :: Return a dataset filtered to rows where +status_id+ is not +1+
|
34
|
+
# Album.dataset.bad:: Return a dataset filtered to rows where +status_id+ is +2+
|
35
|
+
# Album.dataset.not_bad:: Return a dataset filtered to rows where +status_id+ is not +2+
|
36
|
+
#
|
37
|
+
# When calling +enum+, you can also provide the following options:
|
38
|
+
#
|
39
|
+
# :prefix :: Use a prefix for methods defined for each enum value. If +true+ is provided at the value, use the column name as the prefix.
|
40
|
+
# For example, with <tt>prefix: 'status'</tt>, the instance methods defined above would be +status_good?+, +status_bad?+,
|
41
|
+
# +status_good!+, and +status_bad!+, and the dataset methods defined would be +status_good+, +status_not_good+, +status_bad+,
|
42
|
+
# and +status_not_bad+.
|
43
|
+
# :suffix :: Use a suffix for methods defined for each enum value. If +true+ is provided at the value, use the column name as the suffix.
|
44
|
+
# For example, with <tt>suffix: 'status'</tt>, the instance methods defined above would be +good_status?+, +bad_status?+,
|
45
|
+
# +good_status!+, and +bad_status!+, and the dataset methods defined would be +good_status+, +not_good_status+, +bad_status+,
|
46
|
+
# and +not_bad_status+.
|
47
|
+
# :override_accessors :: Set to +false+ to not override the column accessor methods.
|
48
|
+
# :dataset_methods :: Set to +false+ to not define dataset methods.
|
49
|
+
#
|
50
|
+
# Note that this does not use a true enum column in the database. If you are
|
51
|
+
# looking for enum support in the database, and your are using PostgreSQL,
|
52
|
+
# Sequel supports that via the pg_enum Database extension.
|
53
|
+
#
|
54
|
+
# Usage:
|
55
|
+
#
|
56
|
+
# # Make all model subclasses handle enums
|
57
|
+
# Sequel::Model.plugin :enum
|
58
|
+
#
|
59
|
+
# # Make the Album class handle enums
|
60
|
+
# Album.plugin :enum
|
61
|
+
module Enum
|
62
|
+
module ClassMethods
|
63
|
+
# Define instance and dataset methods in this class to treat column
|
64
|
+
# as a enum. See Enum documentation for usage.
|
65
|
+
def enum(column, values, opts=OPTS)
|
66
|
+
raise Sequel::Error, "enum column must be a symbol" unless column.is_a?(Symbol)
|
67
|
+
raise Sequel::Error, "enum values must be provided as a hash with symbol keys" unless values.is_a?(Hash) && values.all?{|k,| k.is_a?(Symbol)}
|
68
|
+
|
69
|
+
if prefix = opts[:prefix]
|
70
|
+
prefix = column if prefix == true
|
71
|
+
prefix = "#{prefix}_"
|
72
|
+
end
|
73
|
+
|
74
|
+
if suffix = opts[:suffix]
|
75
|
+
suffix = column if suffix == true
|
76
|
+
suffix = "_#{suffix}"
|
77
|
+
end
|
78
|
+
|
79
|
+
values = Hash[values].freeze
|
80
|
+
inverted = values.invert.freeze
|
81
|
+
|
82
|
+
unless @enum_methods
|
83
|
+
@enum_methods = Module.new
|
84
|
+
include @enum_methods
|
85
|
+
end
|
86
|
+
|
87
|
+
@enum_methods.module_eval do
|
88
|
+
unless opts[:override_accessors] == false
|
89
|
+
define_method(column) do
|
90
|
+
v = super()
|
91
|
+
inverted.fetch(v, v)
|
92
|
+
end
|
93
|
+
|
94
|
+
define_method(:"#{column}=") do |v|
|
95
|
+
super(values.fetch(v, v))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
values.each do |key, value|
|
100
|
+
define_method(:"#{prefix}#{key}#{suffix}!") do
|
101
|
+
self[column] = value
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
define_method(:"#{prefix}#{key}#{suffix}?") do
|
106
|
+
self[column] == value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
unless opts[:dataset_methods] == false
|
112
|
+
dataset_module do
|
113
|
+
values.each do |key, value|
|
114
|
+
cond = Sequel[column=>value]
|
115
|
+
where :"#{prefix}#{key}#{suffix}", cond
|
116
|
+
where :"#{prefix}not_#{key}#{suffix}", ~cond
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
module Sequel
|
4
|
+
module Plugins
|
5
|
+
# The sql_comments plugin will automatically use SQL comments on
|
6
|
+
# queries for the model it is loaded into. These comments will
|
7
|
+
# show the related model, what type of method was called, and
|
8
|
+
# the method name (or association name for queries to load
|
9
|
+
# associations):
|
10
|
+
#
|
11
|
+
# album = Album[1]
|
12
|
+
# # SELECT * FROM albums WHERE (id = 1) LIMIT 1
|
13
|
+
# # -- model:Album,method_type:class,method:[]
|
14
|
+
#
|
15
|
+
# album.update(:name=>'A')
|
16
|
+
# # UPDATE albums SET name = 'baz' WHERE (id = 1)
|
17
|
+
# # -- model:Album,method_type:instance,method:update
|
18
|
+
#
|
19
|
+
# album.artist
|
20
|
+
# # SELECT * FROM artists WHERE (artists.id = 1)
|
21
|
+
# # -- model:Album,method_type:association_load,association:artist
|
22
|
+
#
|
23
|
+
# Album.eager(:artists).all
|
24
|
+
# # SELECT * FROM albums
|
25
|
+
# # SELECT * FROM artists WHERE (artists.id IN (1))
|
26
|
+
# # -- model:Album,method_type:association_eager_load,association:artist
|
27
|
+
#
|
28
|
+
# Album.where(id: 1).delete
|
29
|
+
# # DELETE FROM albums WHERE (id = 1)
|
30
|
+
# # -- model:Album,method_type:dataset,method:delete
|
31
|
+
#
|
32
|
+
# This plugin automatically supports the class, instance, and dataset
|
33
|
+
# methods are are supported by default in Sequel::Model. To support
|
34
|
+
# custom class, instance, and dataset methods, such as those added by
|
35
|
+
# other plugins, you can use the appropriate <tt>sql_comments_*_methods</tt>
|
36
|
+
# class method:
|
37
|
+
#
|
38
|
+
# Album.sql_comments_class_methods :first_by_name # example from finder plugin, with :mod option
|
39
|
+
# Album.sql_comments_instance_methods :lazy_attribute_lookup # lazy_attributes plugin
|
40
|
+
# Album.sql_comments_dataset_methods :to_csv # csv_serializer plugin
|
41
|
+
#
|
42
|
+
# In order for the sql_comments plugin to work, the sql_comments
|
43
|
+
# Database extension must be loaded into the model's database.
|
44
|
+
#
|
45
|
+
# Note that in order to make sure SQL comments are included, some
|
46
|
+
# optimizations are disabled if this plugin is loaded.
|
47
|
+
#
|
48
|
+
# Usage:
|
49
|
+
#
|
50
|
+
# # Make all model subclasses support automatic SQL comments
|
51
|
+
# # (called before loading subclasses)
|
52
|
+
# Sequel::Model.plugin :sql_comments
|
53
|
+
#
|
54
|
+
# # Make the Album class support automatic SQL comments
|
55
|
+
# Album.plugin :sql_comments
|
56
|
+
module SqlComments
|
57
|
+
# Define a method +meth+ on the given module +mod+ that will use automatic
|
58
|
+
# SQL comments with the given model, method_type, and method.
|
59
|
+
def self.def_sql_commend_method(mod, model, method_type, meth)
|
60
|
+
mod.send(:define_method, meth) do |*a, &block|
|
61
|
+
model.db.with_comments(:model=>model, :method_type=>method_type, :method=>meth) do
|
62
|
+
super(*a, &block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
# :nocov:
|
66
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, false)
|
67
|
+
# :nocov:
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.configure(model)
|
71
|
+
model.send(:reset_fast_pk_lookup_sql)
|
72
|
+
end
|
73
|
+
|
74
|
+
module ClassMethods
|
75
|
+
# Use automatic SQL comments for the given class methods.
|
76
|
+
def sql_comments_class_methods(*meths)
|
77
|
+
_sql_comments_methods(singleton_class, :class, meths)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Use automatic SQL comments for the given instance methods.
|
81
|
+
def sql_comments_instance_methods(*meths)
|
82
|
+
_sql_comments_methods(self, :instance, meths)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Use automatic SQL comments for the given dataset methods.
|
86
|
+
def sql_comments_dataset_methods(*meths)
|
87
|
+
unless @_sql_comments_dataset_module
|
88
|
+
dataset_module(@_sql_comments_dataset_module = Module.new)
|
89
|
+
end
|
90
|
+
_sql_comments_methods(@_sql_comments_dataset_module, :dataset, meths)
|
91
|
+
end
|
92
|
+
|
93
|
+
[:[], :create, :find, :find_or_create, :with_pk, :with_pk!].each do |meth|
|
94
|
+
define_method(meth) do |*a, &block|
|
95
|
+
db.with_comments(:model=>self, :method_type=>:class, :method=>meth) do
|
96
|
+
super(*a, &block)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
# :nocov:
|
100
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, false)
|
101
|
+
# :nocov:
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Don't optimize the fast PK lookups, as it uses static SQL that
|
107
|
+
# won't support the SQL comments.
|
108
|
+
def reset_fast_pk_lookup_sql
|
109
|
+
@fast_pk_lookup_sql = @fast_instance_delete_sql = nil
|
110
|
+
end
|
111
|
+
|
112
|
+
# Define automatic SQL comment methods in +mod+ for each method in +meths+,
|
113
|
+
# with the given +method_type+.
|
114
|
+
def _sql_comments_methods(mod, method_type, meths)
|
115
|
+
meths.each do |meth|
|
116
|
+
SqlComments.def_sql_commend_method(mod, self, method_type, meth)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module InstanceMethods
|
122
|
+
[:delete, :destroy, :lock!, :refresh, :save, :save_changes, :update, :update_fields].each do |meth|
|
123
|
+
define_method(meth) do |*a, &block|
|
124
|
+
t = Sequel.current
|
125
|
+
return super(*a, &block) if (hash = Sequel.synchronize{db.comment_hashes[t]}) && hash[:model]
|
126
|
+
|
127
|
+
db.with_comments(:model=>model, :method_type=>:instance, :method=>meth) do
|
128
|
+
super(*a, &block)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
# :nocov:
|
132
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, false)
|
133
|
+
# :nocov:
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
# Do not use a placeholder loader for associations.
|
139
|
+
def _associated_object_loader(opts, dynamic_opts)
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
|
143
|
+
# Use SQL comments on normal association load queries, showing they are association loads.
|
144
|
+
def _load_associated_objects(opts, dynamic_opts=OPTS)
|
145
|
+
db.with_comments(:model=>model, :method_type=>:association_load, :association=>opts[:name]) do
|
146
|
+
super
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
module DatasetMethods
|
152
|
+
Dataset::ACTION_METHODS.each do |meth|
|
153
|
+
define_method(meth) do |*a, &block|
|
154
|
+
t = Sequel.current
|
155
|
+
return super(*a, &block) if (hash = Sequel.synchronize{db.comment_hashes[t]}) && hash[:model]
|
156
|
+
|
157
|
+
db.with_comments(:model=>model, :method_type=>:dataset, :method=>meth) do
|
158
|
+
super(*a, &block)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
# :nocov:
|
162
|
+
ruby2_keywords(meth) if respond_to?(:ruby2_keywords, false)
|
163
|
+
# :nocov:
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
# Add the association name as part of the eager load data, so
|
169
|
+
# perform_eager_load has access to it.
|
170
|
+
def prepare_eager_load(a, reflections, eager_assoc)
|
171
|
+
res = super
|
172
|
+
|
173
|
+
reflections.each do |r|
|
174
|
+
res[r[:eager_loader]][:association] = r[:name]
|
175
|
+
end
|
176
|
+
|
177
|
+
res
|
178
|
+
end
|
179
|
+
|
180
|
+
# Use SQL comments on eager load queries, showing they are eager loads.
|
181
|
+
def perform_eager_load(loader, eo)
|
182
|
+
db.with_comments(:model=>model, :method_type=>:association_eager_load, :method=>nil, :association=>eo[:association]) do
|
183
|
+
super
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -5,7 +5,7 @@ module Sequel
|
|
5
5
|
# The subclasses plugin keeps track of all subclasses of the
|
6
6
|
# current model class. Direct subclasses are available via the
|
7
7
|
# subclasses method, and all descendent classes are available via the
|
8
|
-
#
|
8
|
+
# descendants method:
|
9
9
|
#
|
10
10
|
# c = Class.new(Sequel::Model)
|
11
11
|
# c.plugin :subclasses
|
@@ -16,7 +16,7 @@ module Sequel
|
|
16
16
|
# sc1.subclasses # [ssc1]
|
17
17
|
# sc2.subclasses # []
|
18
18
|
# ssc1.subclasses # []
|
19
|
-
# c.
|
19
|
+
# c.descendants # [sc1, ssc1, sc2]
|
20
20
|
#
|
21
21
|
# You can also finalize the associations and then freeze the classes
|
22
22
|
# in all descendent classes. Doing so is a recommended practice after
|
@@ -35,9 +35,14 @@ module Sequel
|
|
35
35
|
# class B < Sequel::Model; end
|
36
36
|
# a # => [A, B]
|
37
37
|
module Subclasses
|
38
|
+
NEED_SUBCLASSES = !Object.respond_to?(:subclasses) || Object.method(:subclasses).source_location
|
39
|
+
private_constant :NEED_SUBCLASSES
|
40
|
+
|
38
41
|
# Initialize the subclasses instance variable for the model.
|
39
42
|
def self.apply(model, &block)
|
40
|
-
|
43
|
+
# :nocov:
|
44
|
+
model.instance_variable_set(:@subclasses, []) if NEED_SUBCLASSES
|
45
|
+
# :nocov:
|
41
46
|
model.instance_variable_set(:@on_subclass, block)
|
42
47
|
end
|
43
48
|
|
@@ -46,21 +51,31 @@ module Sequel
|
|
46
51
|
# class created.
|
47
52
|
attr_reader :on_subclass
|
48
53
|
|
49
|
-
#
|
50
|
-
|
51
|
-
|
54
|
+
# :nocov:
|
55
|
+
if NEED_SUBCLASSES
|
56
|
+
# All subclasses for the current model. Does not
|
57
|
+
# include the model itself.
|
58
|
+
attr_reader :subclasses
|
59
|
+
end
|
60
|
+
# :nocov:
|
52
61
|
|
53
62
|
# All descendent classes of this model.
|
54
|
-
def
|
55
|
-
Sequel.synchronize{subclasses.dup}.map{|x| [x] + x.send(:
|
63
|
+
def descendants
|
64
|
+
Sequel.synchronize{subclasses.dup}.map{|x| [x] + x.send(:descendants)}.flatten
|
56
65
|
end
|
57
66
|
|
67
|
+
# SEQUEL6: Remove
|
68
|
+
alias descendents descendants
|
69
|
+
|
58
70
|
# Freeze all descendent classes. This also finalizes the associations for those
|
59
71
|
# classes before freezing.
|
60
|
-
def
|
61
|
-
|
72
|
+
def freeze_descendants
|
73
|
+
descendants.each(&:finalize_associations).each(&:freeze)
|
62
74
|
end
|
63
75
|
|
76
|
+
# SEQUEL6: Remove
|
77
|
+
alias freeze_descendents freeze_descendants
|
78
|
+
|
64
79
|
Plugins.inherited_instance_variables(self, :@subclasses=>lambda{|v| []}, :@on_subclass=>nil)
|
65
80
|
|
66
81
|
private
|
@@ -70,7 +85,9 @@ module Sequel
|
|
70
85
|
# in the subclass.
|
71
86
|
def inherited(subclass)
|
72
87
|
super
|
73
|
-
|
88
|
+
# :nocov:
|
89
|
+
Sequel.synchronize{subclasses << subclass} if NEED_SUBCLASSES
|
90
|
+
# :nocov:
|
74
91
|
on_subclass.call(subclass) if on_subclass
|
75
92
|
end
|
76
93
|
end
|