sequel 3.33.0 → 3.34.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 +140 -0
- data/Rakefile +7 -0
- data/bin/sequel +22 -2
- data/doc/dataset_basics.rdoc +1 -1
- data/doc/mass_assignment.rdoc +3 -1
- data/doc/querying.rdoc +28 -4
- data/doc/reflection.rdoc +23 -3
- data/doc/release_notes/3.34.0.txt +671 -0
- data/doc/schema_modification.rdoc +18 -2
- data/doc/virtual_rows.rdoc +49 -0
- data/lib/sequel/adapters/do/mysql.rb +0 -5
- data/lib/sequel/adapters/ibmdb.rb +9 -4
- data/lib/sequel/adapters/jdbc.rb +9 -4
- data/lib/sequel/adapters/jdbc/h2.rb +8 -2
- data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
- data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
- data/lib/sequel/adapters/mock.rb +24 -3
- data/lib/sequel/adapters/mysql.rb +29 -50
- data/lib/sequel/adapters/mysql2.rb +13 -28
- data/lib/sequel/adapters/oracle.rb +8 -2
- data/lib/sequel/adapters/postgres.rb +115 -20
- data/lib/sequel/adapters/shared/db2.rb +1 -1
- data/lib/sequel/adapters/shared/mssql.rb +14 -3
- data/lib/sequel/adapters/shared/mysql.rb +59 -11
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
- data/lib/sequel/adapters/shared/oracle.rb +1 -1
- data/lib/sequel/adapters/shared/postgres.rb +127 -30
- data/lib/sequel/adapters/shared/sqlite.rb +55 -38
- data/lib/sequel/adapters/sqlite.rb +9 -3
- data/lib/sequel/adapters/swift.rb +2 -2
- data/lib/sequel/adapters/swift/mysql.rb +0 -5
- data/lib/sequel/adapters/swift/postgres.rb +10 -0
- data/lib/sequel/ast_transformer.rb +4 -0
- data/lib/sequel/connection_pool.rb +8 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
- data/lib/sequel/connection_pool/single.rb +5 -0
- data/lib/sequel/connection_pool/threaded.rb +14 -0
- data/lib/sequel/core.rb +24 -3
- data/lib/sequel/database/connecting.rb +24 -14
- data/lib/sequel/database/dataset_defaults.rb +1 -0
- data/lib/sequel/database/misc.rb +16 -25
- data/lib/sequel/database/query.rb +20 -2
- data/lib/sequel/database/schema_generator.rb +2 -2
- data/lib/sequel/database/schema_methods.rb +120 -23
- data/lib/sequel/dataset/actions.rb +91 -18
- data/lib/sequel/dataset/features.rb +5 -0
- data/lib/sequel/dataset/prepared_statements.rb +6 -2
- data/lib/sequel/dataset/sql.rb +68 -51
- data/lib/sequel/extensions/_pretty_table.rb +79 -0
- data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
- data/lib/sequel/extensions/migration.rb +4 -0
- data/lib/sequel/extensions/null_dataset.rb +90 -0
- data/lib/sequel/extensions/pg_array.rb +460 -0
- data/lib/sequel/extensions/pg_array_ops.rb +220 -0
- data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
- data/lib/sequel/extensions/pg_hstore.rb +296 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
- data/lib/sequel/extensions/pretty_table.rb +5 -71
- data/lib/sequel/extensions/query_literals.rb +79 -0
- data/lib/sequel/extensions/schema_caching.rb +76 -0
- data/lib/sequel/extensions/schema_dumper.rb +227 -31
- data/lib/sequel/extensions/select_remove.rb +35 -0
- data/lib/sequel/extensions/sql_expr.rb +4 -110
- data/lib/sequel/extensions/to_dot.rb +1 -1
- data/lib/sequel/model.rb +11 -2
- data/lib/sequel/model/associations.rb +35 -7
- data/lib/sequel/model/base.rb +159 -36
- data/lib/sequel/no_core_ext.rb +2 -0
- data/lib/sequel/plugins/caching.rb +25 -18
- data/lib/sequel/plugins/composition.rb +1 -1
- data/lib/sequel/plugins/hook_class_methods.rb +1 -1
- data/lib/sequel/plugins/identity_map.rb +11 -3
- data/lib/sequel/plugins/instance_filters.rb +10 -0
- data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
- data/lib/sequel/plugins/nested_attributes.rb +4 -3
- data/lib/sequel/plugins/prepared_statements.rb +3 -1
- data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
- data/lib/sequel/plugins/schema.rb +7 -2
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/static_cache.rb +99 -0
- data/lib/sequel/plugins/validation_class_methods.rb +1 -1
- data/lib/sequel/sql.rb +417 -7
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/firebird_spec.rb +1 -1
- data/spec/adapters/mssql_spec.rb +12 -15
- data/spec/adapters/mysql_spec.rb +81 -23
- data/spec/adapters/postgres_spec.rb +444 -77
- data/spec/adapters/spec_helper.rb +2 -0
- data/spec/adapters/sqlite_spec.rb +8 -8
- data/spec/core/connection_pool_spec.rb +85 -0
- data/spec/core/database_spec.rb +29 -5
- data/spec/core/dataset_spec.rb +171 -3
- data/spec/core/expression_filters_spec.rb +364 -0
- data/spec/core/mock_adapter_spec.rb +17 -3
- data/spec/core/schema_spec.rb +133 -0
- data/spec/extensions/association_dependencies_spec.rb +13 -13
- data/spec/extensions/caching_spec.rb +26 -3
- data/spec/extensions/class_table_inheritance_spec.rb +2 -2
- data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
- data/spec/extensions/force_encoding_spec.rb +4 -2
- data/spec/extensions/hook_class_methods_spec.rb +5 -2
- data/spec/extensions/identity_map_spec.rb +17 -0
- data/spec/extensions/instance_filters_spec.rb +1 -1
- data/spec/extensions/lazy_attributes_spec.rb +2 -2
- data/spec/extensions/list_spec.rb +4 -4
- data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
- data/spec/extensions/migration_spec.rb +6 -2
- data/spec/extensions/nested_attributes_spec.rb +20 -0
- data/spec/extensions/null_dataset_spec.rb +85 -0
- data/spec/extensions/optimistic_locking_spec.rb +2 -2
- data/spec/extensions/pg_array_ops_spec.rb +105 -0
- data/spec/extensions/pg_array_spec.rb +196 -0
- data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
- data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
- data/spec/extensions/pg_hstore_spec.rb +195 -0
- data/spec/extensions/pg_statement_cache_spec.rb +209 -0
- data/spec/extensions/prepared_statements_spec.rb +4 -0
- data/spec/extensions/pretty_table_spec.rb +6 -0
- data/spec/extensions/query_literals_spec.rb +168 -0
- data/spec/extensions/schema_caching_spec.rb +41 -0
- data/spec/extensions/schema_dumper_spec.rb +231 -11
- data/spec/extensions/schema_spec.rb +14 -2
- data/spec/extensions/select_remove_spec.rb +38 -0
- data/spec/extensions/sharding_spec.rb +6 -6
- data/spec/extensions/skip_create_refresh_spec.rb +1 -1
- data/spec/extensions/spec_helper.rb +2 -1
- data/spec/extensions/sql_expr_spec.rb +28 -19
- data/spec/extensions/static_cache_spec.rb +145 -0
- data/spec/extensions/touch_spec.rb +1 -1
- data/spec/extensions/typecast_on_load_spec.rb +9 -1
- data/spec/integration/associations_test.rb +6 -6
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +89 -26
- data/spec/integration/migrator_test.rb +2 -3
- data/spec/integration/model_test.rb +3 -3
- data/spec/integration/plugin_test.rb +85 -22
- data/spec/integration/prepared_statement_test.rb +28 -8
- data/spec/integration/schema_test.rb +78 -7
- data/spec/integration/spec_helper.rb +1 -0
- data/spec/integration/timezone_test.rb +1 -1
- data/spec/integration/transaction_test.rb +4 -6
- data/spec/integration/type_test.rb +2 -2
- data/spec/model/associations_spec.rb +94 -8
- data/spec/model/base_spec.rb +4 -4
- data/spec/model/hooks_spec.rb +2 -2
- data/spec/model/model_spec.rb +19 -7
- data/spec/model/record_spec.rb +135 -58
- data/spec/model/spec_helper.rb +1 -0
- metadata +35 -7
|
@@ -53,9 +53,21 @@ module Sequel
|
|
|
53
53
|
# The time to live for the cache store, in seconds.
|
|
54
54
|
attr_reader :cache_ttl
|
|
55
55
|
|
|
56
|
-
#
|
|
57
|
-
def
|
|
58
|
-
|
|
56
|
+
# Delete the cached object with the given primary key.
|
|
57
|
+
def cache_delete_pk(pk)
|
|
58
|
+
cache_delete(cache_key(pk))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Return the cached object with the given primary key,
|
|
62
|
+
# or nil if no such object is in the cache.
|
|
63
|
+
def cache_get_pk(pk)
|
|
64
|
+
cache_get(cache_key(pk))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Return a key string for the given primary key.
|
|
68
|
+
def cache_key(pk)
|
|
69
|
+
raise(Error, 'no primary key for this record') unless pk.is_a?(Array) ? pk.all? : pk
|
|
70
|
+
"#{self}:#{Array(pk).join(',')}"
|
|
59
71
|
end
|
|
60
72
|
|
|
61
73
|
# Copy the necessary class instance variables to the subclass.
|
|
@@ -69,8 +81,13 @@ module Sequel
|
|
|
69
81
|
@cache_ttl = ttl
|
|
70
82
|
@cache_ignore_exceptions = cache_ignore_exceptions
|
|
71
83
|
end
|
|
72
|
-
end
|
|
84
|
+
end
|
|
73
85
|
|
|
86
|
+
# Set the time to live for the cache store, in seconds (default is 3600, # so 1 hour).
|
|
87
|
+
def set_cache_ttl(ttl)
|
|
88
|
+
@cache_ttl = ttl
|
|
89
|
+
end
|
|
90
|
+
|
|
74
91
|
private
|
|
75
92
|
|
|
76
93
|
# Delete the entry with the matching key from the cache
|
|
@@ -79,6 +96,8 @@ module Sequel
|
|
|
79
96
|
nil
|
|
80
97
|
end
|
|
81
98
|
|
|
99
|
+
# Returned the cached object, or nil if the object was not
|
|
100
|
+
# in the cached
|
|
82
101
|
def cache_get(ck)
|
|
83
102
|
if @cache_ignore_exceptions
|
|
84
103
|
@cache_store.get(ck) rescue nil
|
|
@@ -87,11 +106,6 @@ module Sequel
|
|
|
87
106
|
end
|
|
88
107
|
end
|
|
89
108
|
|
|
90
|
-
# Return a key string for the pk
|
|
91
|
-
def cache_key(pk)
|
|
92
|
-
"#{self}:#{Array(pk).join(',')}"
|
|
93
|
-
end
|
|
94
|
-
|
|
95
109
|
# Set the object in the cache_store with the given key for cache_ttl seconds.
|
|
96
110
|
def cache_set(ck, obj)
|
|
97
111
|
@cache_store.set(ck, obj, @cache_ttl)
|
|
@@ -120,14 +134,7 @@ module Sequel
|
|
|
120
134
|
# primary key value(s) for the object. If the model does not have a primary
|
|
121
135
|
# key, raise an Error.
|
|
122
136
|
def cache_key
|
|
123
|
-
|
|
124
|
-
pk = case key
|
|
125
|
-
when Array
|
|
126
|
-
key.collect{|k| @values[k]}
|
|
127
|
-
else
|
|
128
|
-
@values[key] || (raise Error, 'no primary key for this record')
|
|
129
|
-
end
|
|
130
|
-
model.send(:cache_key, pk)
|
|
137
|
+
model.cache_key(pk)
|
|
131
138
|
end
|
|
132
139
|
|
|
133
140
|
# Remove the object from the cache when deleting
|
|
@@ -140,7 +147,7 @@ module Sequel
|
|
|
140
147
|
|
|
141
148
|
# Delete this object from the cache
|
|
142
149
|
def cache_delete
|
|
143
|
-
model.
|
|
150
|
+
model.cache_delete_pk(pk)
|
|
144
151
|
end
|
|
145
152
|
end
|
|
146
153
|
end
|
|
@@ -35,7 +35,7 @@ module Sequel
|
|
|
35
35
|
# will be instance_evaled before saving. The above example could
|
|
36
36
|
# also be implemented as:
|
|
37
37
|
#
|
|
38
|
-
# Album.composition
|
|
38
|
+
# Album.composition :date,
|
|
39
39
|
# :composer=>proc{Date.new(year, month, day) if year || month || day},
|
|
40
40
|
# :decomposer=>(proc do
|
|
41
41
|
# if d = compositions[:date]
|
|
@@ -90,9 +90,9 @@ module Sequel
|
|
|
90
90
|
|
|
91
91
|
# Make a copy of the current class's hooks for the subclass.
|
|
92
92
|
def inherited(subclass)
|
|
93
|
-
super
|
|
94
93
|
hooks = subclass.instance_variable_set(:@hooks, {})
|
|
95
94
|
instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup}
|
|
95
|
+
super
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
private
|
|
@@ -228,10 +228,18 @@ module Sequel
|
|
|
228
228
|
# key option has a value and the association uses the primary key of
|
|
229
229
|
# the associated class as the :primary_key option, check the identity
|
|
230
230
|
# map for the associated object and return it if present.
|
|
231
|
-
def
|
|
231
|
+
def _load_associated_object(opts, dynamic_opts)
|
|
232
232
|
klass = opts.associated_class
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
cache_lookup = opts.fetch(:idm_cache_lookup) do
|
|
234
|
+
opts[:idm_cache_lookup] = klass.respond_to?(:identity_map) &&
|
|
235
|
+
opts[:type] == :many_to_one &&
|
|
236
|
+
opts[:key] &&
|
|
237
|
+
opts.primary_key == klass.primary_key
|
|
238
|
+
end
|
|
239
|
+
if cache_lookup &&
|
|
240
|
+
!dynamic_opts[:callback] &&
|
|
241
|
+
(idm = klass.identity_map) &&
|
|
242
|
+
(o = idm[klass.identity_map_key(_associated_object_pk(opts[:key]))])
|
|
235
243
|
o
|
|
236
244
|
else
|
|
237
245
|
super
|
|
@@ -68,6 +68,16 @@ module Sequel
|
|
|
68
68
|
|
|
69
69
|
private
|
|
70
70
|
|
|
71
|
+
# If there are any instance filters, make sure not to use the
|
|
72
|
+
# instance delete optimization.
|
|
73
|
+
def _delete_without_checking
|
|
74
|
+
if @instance_filters && !@instance_filters.empty?
|
|
75
|
+
_delete_dataset.delete
|
|
76
|
+
else
|
|
77
|
+
super
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
71
81
|
# Lazily initialize the instance filter array.
|
|
72
82
|
def instance_filters
|
|
73
83
|
@instance_filters ||= []
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# This is a fairly simple plugin that modifies the internal association loading logic
|
|
4
|
+
# for many_to_one associations to use a simple primary key lookup on the associated
|
|
5
|
+
# class, which is generally faster as it uses mostly static SQL. Additional, if the
|
|
6
|
+
# associated class is caching primary key lookups, you get the benefit of a cached
|
|
7
|
+
# lookup.
|
|
8
|
+
#
|
|
9
|
+
# This plugin is generally not as fast as the prepared_statements_associations plugin
|
|
10
|
+
# in the case where the model is not caching primary key lookups, however, it is
|
|
11
|
+
# probably significantly faster if the model is caching primary key lookups. If
|
|
12
|
+
# the prepared_statements_associations plugin has been loaded first, this
|
|
13
|
+
# plugin will only use the primary key lookup code if the associated model is
|
|
14
|
+
# caching primary key lookups.
|
|
15
|
+
#
|
|
16
|
+
# This plugin attempts to determine cases where the primary key lookup would have
|
|
17
|
+
# different results than the regular lookup, and use the regular lookup in that case,
|
|
18
|
+
# but it cannot handle all situations correctly, which is why it is not Sequel's
|
|
19
|
+
# default behavior.
|
|
20
|
+
#
|
|
21
|
+
# You can disable primary key lookups on a per association basis with this
|
|
22
|
+
# plugin using the :many_to_one_pk_lookup=>false association option.
|
|
23
|
+
#
|
|
24
|
+
# Usage:
|
|
25
|
+
#
|
|
26
|
+
# # Make all model subclass instances use primary key lookups for many_to_one
|
|
27
|
+
# # association loading
|
|
28
|
+
# Sequel::Model.plugin :many_to_one_pk_lookup
|
|
29
|
+
#
|
|
30
|
+
# # Do so for just the album class.
|
|
31
|
+
# Album.plugin :many_to_one_pk_lookup
|
|
32
|
+
module ManyToOnePkLookup
|
|
33
|
+
module InstanceMethods
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# If the current association is a fairly simple many_to_one association, use
|
|
37
|
+
# a simple primary key lookup on the associated model, which can benefit from
|
|
38
|
+
# caching if the associated model is using caching.
|
|
39
|
+
def _load_associated_object(opts, dynamic_opts)
|
|
40
|
+
klass = opts.associated_class
|
|
41
|
+
cache_lookup = opts.fetch(:many_to_one_pk_lookup) do
|
|
42
|
+
opts[:many_to_one_pk_lookup] = opts[:type] == :many_to_one &&
|
|
43
|
+
opts[:key] &&
|
|
44
|
+
opts.primary_key == klass.primary_key
|
|
45
|
+
end
|
|
46
|
+
if cache_lookup &&
|
|
47
|
+
!dynamic_opts[:callback] &&
|
|
48
|
+
(o = klass.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| send(c)} : send(fk))))
|
|
49
|
+
o
|
|
50
|
+
else
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Deal with the situation where the prepared_statements_associations plugin is
|
|
56
|
+
# loaded first, by using a primary key lookup for many_to_one associations if
|
|
57
|
+
# the associated class is using caching, and using the default code otherwise.
|
|
58
|
+
# This is done because the prepared_statements_associations code is probably faster
|
|
59
|
+
# than the primary key lookup this plugin uses if the model is not caching lookups,
|
|
60
|
+
# but probably slower if the model is caching lookups.
|
|
61
|
+
def _load_associated_objects(opts, dynamic_opts={})
|
|
62
|
+
if opts.can_have_associated_objects?(self) && opts[:type] == :many_to_one && opts.associated_class.respond_to?(:cache_get_pk)
|
|
63
|
+
_load_associated_object(opts, dynamic_opts)
|
|
64
|
+
else
|
|
65
|
+
super
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -159,7 +159,7 @@ module Sequel
|
|
|
159
159
|
# is not found and the :strict option is not false, raise an Error.
|
|
160
160
|
def nested_attributes_find(reflection, pk)
|
|
161
161
|
pk = pk.to_s
|
|
162
|
-
unless obj = Array(
|
|
162
|
+
unless obj = Array(send(reflection[:name])).find{|x| x.pk.to_s == pk}
|
|
163
163
|
raise(Error, "no matching associated object with given primary key (association: #{reflection[:name]}, pk: #{pk})") unless reflection[:nested_attributes][:strict] == false
|
|
164
164
|
end
|
|
165
165
|
obj
|
|
@@ -218,9 +218,10 @@ module Sequel
|
|
|
218
218
|
modified!
|
|
219
219
|
klass = reflection.associated_class
|
|
220
220
|
if pk = attributes.delete(klass.primary_key) || attributes.delete(klass.primary_key.to_s)
|
|
221
|
-
|
|
221
|
+
attributes = attributes.dup
|
|
222
|
+
if reflection[:nested_attributes][:destroy] && klass.db.send(:typecast_value_boolean, attributes.delete(:_delete) || attributes.delete('_delete'))
|
|
222
223
|
nested_attributes_remove(reflection, pk, :destroy=>true)
|
|
223
|
-
elsif klass.db.send(:typecast_value_boolean, attributes
|
|
224
|
+
elsif reflection[:nested_attributes][:remove] && klass.db.send(:typecast_value_boolean, attributes.delete(:_remove) || attributes.delete('_remove'))
|
|
224
225
|
nested_attributes_remove(reflection, pk)
|
|
225
226
|
else
|
|
226
227
|
nested_attributes_update(reflection, pk, attributes)
|
|
@@ -46,7 +46,9 @@ module Sequel
|
|
|
46
46
|
# Create a prepared statement based on the given dataset with a unique name for the given
|
|
47
47
|
# type of query and values.
|
|
48
48
|
def prepare_statement(ds, type, vals={})
|
|
49
|
-
ds.prepare(type, :"smpsp_#{NEXT.call}", vals)
|
|
49
|
+
ps = ds.prepare(type, :"smpsp_#{NEXT.call}", vals)
|
|
50
|
+
ps.log_sql = true
|
|
51
|
+
ps
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
# Return a sorted array of columns for use as a hash key.
|
|
@@ -62,7 +62,11 @@ module Sequel
|
|
|
62
62
|
# that, given appropriate bound variables, the prepared statement will work correctly for any
|
|
63
63
|
# instance.
|
|
64
64
|
def association_prepared_statement(opts)
|
|
65
|
-
opts[:prepared_statement] ||=
|
|
65
|
+
opts[:prepared_statement] ||= begin
|
|
66
|
+
ps = _associated_dataset(opts, {}).unbind.first.prepare(opts.returns_array? ? :select : :first, :"smpsap_#{NEXT.call}")
|
|
67
|
+
ps.log_sql = true
|
|
68
|
+
ps
|
|
69
|
+
end
|
|
66
70
|
end
|
|
67
71
|
|
|
68
72
|
# If a prepared statement can be used to load the associated objects, execute it to retrieve them. Otherwise,
|
|
@@ -29,7 +29,7 @@ module Sequel
|
|
|
29
29
|
# Drops the table if it exists and then runs create_table. Should probably
|
|
30
30
|
# not be used except in testing.
|
|
31
31
|
def create_table!(*args, &block)
|
|
32
|
-
drop_table
|
|
32
|
+
drop_table?
|
|
33
33
|
create_table(*args, &block)
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -38,11 +38,16 @@ module Sequel
|
|
|
38
38
|
create_table(*args, &block) unless table_exists?
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
# Drops table.
|
|
41
|
+
# Drops table. If the table doesn't exist, this will probably raise an error.
|
|
42
42
|
def drop_table
|
|
43
43
|
db.drop_table(table_name)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
# Drops table if it already exists, do nothing if it doesn't exist.
|
|
47
|
+
def drop_table?
|
|
48
|
+
db.drop_table?(table_name)
|
|
49
|
+
end
|
|
50
|
+
|
|
46
51
|
# Returns table schema created with set_schema for direct descendant of Model.
|
|
47
52
|
# Does not retreive schema information from the database, see db_schema if you
|
|
48
53
|
# want that.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The static_cache plugin is designed for models that are not modified at all
|
|
4
|
+
# in production use cases, or at least where modifications to them would usually
|
|
5
|
+
# coincide with an application restart. When loaded into a model class, it
|
|
6
|
+
# retrieves all rows in the database and staticly caches a ruby array and hash
|
|
7
|
+
# keyed on primary key containing all of the model instances. All of these instances
|
|
8
|
+
# are frozen so they won't be modified unexpectedly.
|
|
9
|
+
#
|
|
10
|
+
# The caches this plugin creates are used for the following things:
|
|
11
|
+
#
|
|
12
|
+
# * Primary key lookups (e.g. Model[1])
|
|
13
|
+
# * Model.all calls
|
|
14
|
+
# * Model.each calls
|
|
15
|
+
# * Model.map calls without an argument
|
|
16
|
+
# * Model.to_hash calls without an argument
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
#
|
|
20
|
+
# # Cache the AlbumType class staticly
|
|
21
|
+
# AlbumType.plugin :static_cache
|
|
22
|
+
module StaticCache
|
|
23
|
+
# Populate the static caches when loading the plugin.
|
|
24
|
+
def self.configure(model)
|
|
25
|
+
model.send(:load_cache)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module ClassMethods
|
|
29
|
+
# A frozen ruby hash holding all of the model's frozen instances, keyed by frozen primary key.
|
|
30
|
+
attr_reader :cache
|
|
31
|
+
|
|
32
|
+
# An array of all of the model's frozen instances, without issuing a database
|
|
33
|
+
# query.
|
|
34
|
+
def all
|
|
35
|
+
@all.dup
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Return the frozen object with the given pk, or nil if no such object exists
|
|
39
|
+
# in the cache, without issuing a database query.
|
|
40
|
+
def cache_get_pk(pk)
|
|
41
|
+
cache[pk]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Yield each of the model's frozen instances to the block, without issuing a database
|
|
45
|
+
# query.
|
|
46
|
+
def each(&block)
|
|
47
|
+
@all.each(&block)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# If no arguments are given, yield each of the model's frozen instances to the block.
|
|
51
|
+
# and return a new array, without issuing a database query. If any arguments are
|
|
52
|
+
# given, use the default Sequel behavior.
|
|
53
|
+
def map(*a)
|
|
54
|
+
if a.empty?
|
|
55
|
+
@all.map(&(Proc.new if block_given?))
|
|
56
|
+
else
|
|
57
|
+
super
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Reload the cache when the dataset changes.
|
|
62
|
+
def set_dataset(*)
|
|
63
|
+
s = super
|
|
64
|
+
load_cache
|
|
65
|
+
s
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# If no arguments are given, yield an identity map for the model with frozen primary keys
|
|
69
|
+
# and instances, without issuing a database query. If any arguments are
|
|
70
|
+
# given, use the default Sequel behavior.
|
|
71
|
+
def to_hash(*a)
|
|
72
|
+
if a.empty?
|
|
73
|
+
cache.dup
|
|
74
|
+
else
|
|
75
|
+
super
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
# Return the frozen object with the given pk, or nil if no such object exists
|
|
82
|
+
# in the cache, without issuing a database query.
|
|
83
|
+
def primary_key_lookup(pk)
|
|
84
|
+
cache[pk]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Reload the cache for this model by retrieving all of the instances in the dataset
|
|
88
|
+
# freezing them, and populating the cached array and hash.
|
|
89
|
+
def load_cache
|
|
90
|
+
a = dataset.all
|
|
91
|
+
h = {}
|
|
92
|
+
a.each{|o| h[o.pk.freeze] = o.freeze}
|
|
93
|
+
@all = a.freeze
|
|
94
|
+
@cache = h.freeze
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -60,7 +60,6 @@ module Sequel
|
|
|
60
60
|
|
|
61
61
|
# Setup the validations and validation_reflections hash in the subclass.
|
|
62
62
|
def inherited(subclass)
|
|
63
|
-
super
|
|
64
63
|
vr = @validation_reflections
|
|
65
64
|
subclass.class_eval do
|
|
66
65
|
@validation_mutex = Mutex.new
|
|
@@ -69,6 +68,7 @@ module Sequel
|
|
|
69
68
|
vr.each{|k,v| h[k] = v.dup}
|
|
70
69
|
@validation_reflections = h
|
|
71
70
|
end
|
|
71
|
+
super
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
# Instructs the model to skip validations defined in superclasses
|
data/lib/sequel/sql.rb
CHANGED
|
@@ -281,6 +281,293 @@ module Sequel
|
|
|
281
281
|
end
|
|
282
282
|
end
|
|
283
283
|
|
|
284
|
+
# These methods are designed as replacements for the core extensions, so that
|
|
285
|
+
# Sequel is still easy to use if the core extensions are not enabled.
|
|
286
|
+
module Builders
|
|
287
|
+
# Create an SQL::AliasedExpression for the given expression and alias.
|
|
288
|
+
#
|
|
289
|
+
# Sequel.as(:column, :alias) # "column" AS "alias"
|
|
290
|
+
def as(exp, aliaz)
|
|
291
|
+
SQL::AliasedExpression.new(exp, aliaz)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Order the given argument ascending.
|
|
295
|
+
# Options:
|
|
296
|
+
#
|
|
297
|
+
# :nulls :: Set to :first to use NULLS FIRST (so NULL values are ordered
|
|
298
|
+
# before other values), or :last to use NULLS LAST (so NULL values
|
|
299
|
+
# are ordered after other values).
|
|
300
|
+
#
|
|
301
|
+
# Sequel.asc(:a) # a ASC
|
|
302
|
+
# Sequel.asc(:b, :nulls=>:last) # b ASC NULLS LAST
|
|
303
|
+
def asc(arg, opts={})
|
|
304
|
+
SQL::OrderedExpression.new(arg, false, opts)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Return an <tt>SQL::Blob</tt> that holds the same data as this string.
|
|
308
|
+
# Blobs provide proper escaping of binary data. If given a blob, returns it
|
|
309
|
+
# directly.
|
|
310
|
+
def blob(s)
|
|
311
|
+
if s.is_a?(SQL::Blob)
|
|
312
|
+
s
|
|
313
|
+
else
|
|
314
|
+
SQL::Blob.new(s)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Return an <tt>SQL::CaseExpression</tt> created with the given arguments.
|
|
319
|
+
#
|
|
320
|
+
# Sequel.case([[{:a=>[2,3]}, 1]], 0) # SQL: CASE WHEN a IN (2, 3) THEN 1 ELSE 0 END
|
|
321
|
+
# Sequel.case({:a=>1}, 0, :b) # SQL: CASE b WHEN a THEN 1 ELSE 0 END
|
|
322
|
+
def case(*args) # core_sql ignore
|
|
323
|
+
SQL::CaseExpression.new(*args)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Cast the reciever to the given SQL type. You can specify a ruby class as a type,
|
|
327
|
+
# and it is handled similarly to using a database independent type in the schema methods.
|
|
328
|
+
#
|
|
329
|
+
# Sequel.cast(:a, :integer) # CAST(a AS integer)
|
|
330
|
+
# Sequel.cast(:a, String) # CAST(a AS varchar(255))
|
|
331
|
+
def cast(arg, sql_type)
|
|
332
|
+
SQL::Cast.new(arg, sql_type)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Cast the reciever to the given SQL type (or the database's default Integer type if none given),
|
|
336
|
+
# and return the result as a +NumericExpression+, so you can use the bitwise operators
|
|
337
|
+
# on the result.
|
|
338
|
+
#
|
|
339
|
+
# Sequel.cast_numeric(:a) # CAST(a AS integer)
|
|
340
|
+
# Sequel.cast_numeric(:a, Float) # CAST(a AS double precision)
|
|
341
|
+
def cast_numeric(arg, sql_type = nil)
|
|
342
|
+
cast(arg, sql_type || Integer).sql_number
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Cast the reciever to the given SQL type (or the database's default String type if none given),
|
|
346
|
+
# and return the result as a +StringExpression+, so you can use +
|
|
347
|
+
# directly on the result for SQL string concatenation.
|
|
348
|
+
#
|
|
349
|
+
# Sequel.cast_string(:a) # CAST(a AS varchar(255))
|
|
350
|
+
# Sequel.cast_string(:a, :text) # CAST(a AS text)
|
|
351
|
+
def cast_string(arg, sql_type = nil)
|
|
352
|
+
cast(arg, sql_type || String).sql_string
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Order the given argument descending.
|
|
356
|
+
# Options:
|
|
357
|
+
#
|
|
358
|
+
# :nulls :: Set to :first to use NULLS FIRST (so NULL values are ordered
|
|
359
|
+
# before other values), or :last to use NULLS LAST (so NULL values
|
|
360
|
+
# are ordered after other values).
|
|
361
|
+
#
|
|
362
|
+
# Sequel.desc(:a) # b DESC
|
|
363
|
+
# Sequel.desc(:b, :nulls=>:first) # b DESC NULLS FIRST
|
|
364
|
+
def desc(arg, opts={})
|
|
365
|
+
SQL::OrderedExpression.new(arg, true, opts)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Wraps the given object in an appropriate Sequel wrapper.
|
|
369
|
+
# If the given object is already a Sequel object, return it directly.
|
|
370
|
+
# For condition specifiers (hashes and arrays of two pairs), true, and false,
|
|
371
|
+
# return a boolean expressions. For numeric objects, return a numeric
|
|
372
|
+
# expression. For strings, return a string expression. For procs or when
|
|
373
|
+
# the method is passed a block, evaluate it as a virtual row and wrap it
|
|
374
|
+
# appropriately. In all other cases, use a generic wrapper.
|
|
375
|
+
#
|
|
376
|
+
# This method allows you to construct SQL expressions that are difficult
|
|
377
|
+
# to construct via other methods. For example:
|
|
378
|
+
#
|
|
379
|
+
# Sequel.expr(1) - :a # SQL: (1 - a)
|
|
380
|
+
def expr(arg=(no_arg=true), &block)
|
|
381
|
+
if block_given?
|
|
382
|
+
if no_arg
|
|
383
|
+
return expr(block)
|
|
384
|
+
else
|
|
385
|
+
raise Error, 'cannot provide both an argument and a block to Sequel.expr'
|
|
386
|
+
end
|
|
387
|
+
elsif no_arg
|
|
388
|
+
raise Error, 'must provide either an argument or a block to Sequel.expr'
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
case arg
|
|
392
|
+
when SQL::Expression, LiteralString, SQL::Blob
|
|
393
|
+
arg
|
|
394
|
+
when Hash
|
|
395
|
+
SQL::BooleanExpression.from_value_pairs(arg, :AND)
|
|
396
|
+
when Array
|
|
397
|
+
if condition_specifier?(arg)
|
|
398
|
+
SQL::BooleanExpression.from_value_pairs(arg, :AND)
|
|
399
|
+
else
|
|
400
|
+
SQL::Wrapper.new(arg)
|
|
401
|
+
end
|
|
402
|
+
when Numeric
|
|
403
|
+
SQL::NumericExpression.new(:NOOP, arg)
|
|
404
|
+
when String
|
|
405
|
+
SQL::StringExpression.new(:NOOP, arg)
|
|
406
|
+
when TrueClass, FalseClass
|
|
407
|
+
SQL::BooleanExpression.new(:NOOP, arg)
|
|
408
|
+
when Proc
|
|
409
|
+
expr(virtual_row(&arg))
|
|
410
|
+
else
|
|
411
|
+
SQL::Wrapper.new(arg)
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Extract a datetime_part (e.g. year, month) from the given
|
|
416
|
+
# expression:
|
|
417
|
+
#
|
|
418
|
+
# Sequel.extract(:year, :date) # extract(year FROM "date")
|
|
419
|
+
def extract(datetime_part, exp)
|
|
420
|
+
SQL::NumericExpression.new(:extract, datetime_part, exp)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
# Returns a <tt>Sequel::SQL::Function</tt> with the function name
|
|
424
|
+
# and the given arguments.
|
|
425
|
+
#
|
|
426
|
+
# Sequel.function(:now) # SQL: now()
|
|
427
|
+
# Sequel.function(:substr, :a, 1) # SQL: substr(a, 1)
|
|
428
|
+
def function(name, *args)
|
|
429
|
+
SQL::Function.new(name, *args)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
# Return the argument wrapped as an <tt>SQL::Identifier</tt>.
|
|
433
|
+
#
|
|
434
|
+
# Sequel.identifier(:a__b) # "a__b"
|
|
435
|
+
def identifier(name)
|
|
436
|
+
SQL::Identifier.new(name)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Return a <tt>Sequel::SQL::StringExpression</tt> representing an SQL string made up of the
|
|
440
|
+
# concatenation of the given array's elements. If an argument is passed,
|
|
441
|
+
# it is used in between each element of the array in the SQL
|
|
442
|
+
# concatenation.
|
|
443
|
+
#
|
|
444
|
+
# Sequel.join([:a]) # SQL: a
|
|
445
|
+
# Sequel.join([:a, :b]) # SQL: a || b
|
|
446
|
+
# Sequel.join([:a, 'b']) # SQL: a || 'b'
|
|
447
|
+
# Sequel.join(['a', :b], ' ') # SQL: 'a' || ' ' || b
|
|
448
|
+
def join(args, joiner=nil)
|
|
449
|
+
raise Error, 'argument to Sequel.join must be an array' unless args.is_a?(Array)
|
|
450
|
+
if joiner
|
|
451
|
+
args = args.zip([joiner]*args.length).flatten
|
|
452
|
+
args.pop
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
return SQL::StringExpression.new(:NOOP, '') if args.empty?
|
|
456
|
+
|
|
457
|
+
args = args.map do |a|
|
|
458
|
+
case a
|
|
459
|
+
when Symbol, ::Sequel::SQL::Expression, ::Sequel::LiteralString, TrueClass, FalseClass, NilClass
|
|
460
|
+
a
|
|
461
|
+
else
|
|
462
|
+
a.to_s
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
SQL::StringExpression.new(:'||', *args)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# Create a <tt>BooleanExpression</tt> case insensitive (if the database supports it) pattern match of the receiver with
|
|
469
|
+
# the given patterns. See <tt>SQL::StringExpression.like</tt>.
|
|
470
|
+
#
|
|
471
|
+
# Sequel.ilike(:a, 'A%') # "a" ILIKE 'A%'
|
|
472
|
+
def ilike(*args)
|
|
473
|
+
SQL::StringExpression.like(*(args << {:case_insensitive=>true}))
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
# Create a <tt>SQL::BooleanExpression</tt> case sensitive (if the database supports it) pattern match of the receiver with
|
|
477
|
+
# the given patterns. See <tt>SQL::StringExpression.like</tt>.
|
|
478
|
+
#
|
|
479
|
+
# Sequel.like(:a, 'A%') # "a" LIKE 'A%'
|
|
480
|
+
def like(*args)
|
|
481
|
+
SQL::StringExpression.like(*args)
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
# Converts a string into a <tt>Sequel::LiteralString</tt>, in order to override string
|
|
485
|
+
# literalization, e.g.:
|
|
486
|
+
#
|
|
487
|
+
# DB[:items].filter(:abc => 'def').sql #=>
|
|
488
|
+
# "SELECT * FROM items WHERE (abc = 'def')"
|
|
489
|
+
#
|
|
490
|
+
# DB[:items].filter(:abc => Sequel.lit('def')).sql #=>
|
|
491
|
+
# "SELECT * FROM items WHERE (abc = def)"
|
|
492
|
+
#
|
|
493
|
+
# You can also provide arguments, to create a <tt>Sequel::SQL::PlaceholderLiteralString</tt>:
|
|
494
|
+
#
|
|
495
|
+
# DB[:items].select{|o| o.count(Sequel.lit('DISTINCT ?', :a))}.sql #=>
|
|
496
|
+
# "SELECT count(DISTINCT a) FROM items"
|
|
497
|
+
def lit(s, *args) # core_sql ignore
|
|
498
|
+
if args.empty?
|
|
499
|
+
if s.is_a?(LiteralString)
|
|
500
|
+
s
|
|
501
|
+
else
|
|
502
|
+
LiteralString.new(s)
|
|
503
|
+
end
|
|
504
|
+
else
|
|
505
|
+
SQL::PlaceholderLiteralString.new(s, args)
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# Return a <tt>Sequel::SQL::BooleanExpression</tt> created from the condition
|
|
510
|
+
# specifier, matching none of the conditions.
|
|
511
|
+
#
|
|
512
|
+
# Sequel.negate(:a=>true) # SQL: a IS NOT TRUE
|
|
513
|
+
# Sequel.negate([[:a, true]]) # SQL: a IS NOT TRUE
|
|
514
|
+
# Sequel.negate([[:a, 1], [:b, 2]]) # SQL: ((a != 1) AND (b != 2))
|
|
515
|
+
def negate(arg)
|
|
516
|
+
if condition_specifier?(arg)
|
|
517
|
+
SQL::BooleanExpression.from_value_pairs(arg, :AND, true)
|
|
518
|
+
else
|
|
519
|
+
raise Error, 'must pass a conditions specifier to Sequel.negate'
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
# Return a <tt>Sequel::SQL::BooleanExpression</tt> created from the condition
|
|
524
|
+
# specifier, matching any of the conditions.
|
|
525
|
+
#
|
|
526
|
+
# Sequel.or(:a=>true) # SQL: a IS TRUE
|
|
527
|
+
# Sequel.or([[:a, true]]) # SQL: a IS TRUE
|
|
528
|
+
# Sequel.or([[:a, 1], [:b, 2]]) # SQL: ((a = 1) OR (b = 2))
|
|
529
|
+
def or(arg)
|
|
530
|
+
if condition_specifier?(arg)
|
|
531
|
+
SQL::BooleanExpression.from_value_pairs(arg, :OR, false)
|
|
532
|
+
else
|
|
533
|
+
raise Error, 'must pass a conditions specifier to Sequel.or'
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
# Create a qualified identifier with the given qualifier and identifier
|
|
538
|
+
#
|
|
539
|
+
# Sequel.qualify(:table, :column) # "table"."column"
|
|
540
|
+
# Sequel.qualify(:schema, :table) # "schema"."table"
|
|
541
|
+
# Sequel.qualify(:table, :column).qualify(:schema) # "schema"."table"."column"
|
|
542
|
+
def qualify(qualifier, identifier)
|
|
543
|
+
SQL::QualifiedIdentifier.new(qualifier, identifier)
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# Return an <tt>SQL::Subscript</tt> with the given arguments, representing an
|
|
547
|
+
# SQL array access.
|
|
548
|
+
#
|
|
549
|
+
# Sequel.subscript(:array, 1) # array[1]
|
|
550
|
+
# Sequel.subscript(:array, 1, 2) # array[1, 2]
|
|
551
|
+
# Sequel.subscript(:array, [1, 2]) # array[1, 2]
|
|
552
|
+
def subscript(exp, *subs)
|
|
553
|
+
SQL::Subscript.new(exp, subs.flatten)
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# Return a <tt>SQL::ValueList</tt> created from the given array. Used if the array contains
|
|
557
|
+
# all two element arrays and you want it treated as an SQL value list (IN predicate)
|
|
558
|
+
# instead of as a conditions specifier (similar to a hash). This is not necessary if you are using
|
|
559
|
+
# this array as a value in a filter, but may be necessary if you are using it as a
|
|
560
|
+
# value with placeholder SQL:
|
|
561
|
+
#
|
|
562
|
+
# DB[:a].filter([:a, :b]=>[[1, 2], [3, 4]]) # SQL: (a, b) IN ((1, 2), (3, 4))
|
|
563
|
+
# DB[:a].filter('(a, b) IN ?', [[1, 2], [3, 4]]) # SQL: (a, b) IN ((1 = 2) AND (3 = 4))
|
|
564
|
+
# DB[:a].filter('(a, b) IN ?', Sequel.value_list([[1, 2], [3, 4]])) # SQL: (a, b) IN ((1, 2), (3, 4))
|
|
565
|
+
def value_list(arg)
|
|
566
|
+
raise Error, 'argument to Sequel.value_list must be an array' unless arg.is_a?(Array)
|
|
567
|
+
SQL::ValueList.new(arg)
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
284
571
|
# Holds methods that are used to cast objects to different SQL types.
|
|
285
572
|
module CastMethods
|
|
286
573
|
# Cast the reciever to the given SQL type. You can specify a ruby class as a type,
|
|
@@ -333,10 +620,6 @@ module Sequel
|
|
|
333
620
|
#
|
|
334
621
|
# Also has the benefit of returning the result as a
|
|
335
622
|
# NumericExpression instead of a generic ComplexExpression.
|
|
336
|
-
#
|
|
337
|
-
# The extract function is in the SQL standard, but it doesn't
|
|
338
|
-
# doesn't use the standard function calling convention, and it
|
|
339
|
-
# doesn't work on all databases.
|
|
340
623
|
def extract(datetime_part)
|
|
341
624
|
NumericExpression.new(:extract, datetime_part, self)
|
|
342
625
|
end
|
|
@@ -424,6 +707,46 @@ module Sequel
|
|
|
424
707
|
end
|
|
425
708
|
end
|
|
426
709
|
|
|
710
|
+
# These methods are designed as replacements for the core extension operator
|
|
711
|
+
# methods, so that Sequel is still easy to use if the core extensions are not
|
|
712
|
+
# enabled.
|
|
713
|
+
#
|
|
714
|
+
# The following methods are defined via metaprogramming: +, -, *, /, &, |.
|
|
715
|
+
# The +, -, *, and / operators return numeric expressions combining all the
|
|
716
|
+
# arguments with the appropriate operator, and the & and | operators return
|
|
717
|
+
# boolean expressions combining all of the arguments with either AND or OR.
|
|
718
|
+
module OperatorBuilders
|
|
719
|
+
%w'+ - * /'.each do |op|
|
|
720
|
+
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
|
721
|
+
def #{op}(*args)
|
|
722
|
+
SQL::NumericExpression.new(:#{op}, *args)
|
|
723
|
+
end
|
|
724
|
+
END
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
{'&'=>'AND', '|'=>'OR'}.each do |m, op|
|
|
728
|
+
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
|
729
|
+
def #{m}(*args)
|
|
730
|
+
SQL::BooleanExpression.new(:#{op}, *args)
|
|
731
|
+
end
|
|
732
|
+
END
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
# Invert the given expression. Returns a <tt>Sequel::SQL::BooleanExpression</tt>
|
|
736
|
+
# created from this argument, not matching all of the conditions.
|
|
737
|
+
#
|
|
738
|
+
# Sequel.~(nil) # SQL: NOT NULL
|
|
739
|
+
# Sequel.~([[:a, true]]) # SQL: a IS NOT TRUE
|
|
740
|
+
# Sequel.~([[:a, 1], [:b, [2, 3]]]) # SQL: a != 1 OR b NOT IN (2, 3)
|
|
741
|
+
def ~(arg)
|
|
742
|
+
if condition_specifier?(arg)
|
|
743
|
+
SQL::BooleanExpression.from_value_pairs(arg, :OR, true)
|
|
744
|
+
else
|
|
745
|
+
SQL::BooleanExpression.invert(arg)
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
end
|
|
749
|
+
|
|
427
750
|
# Methods that create +OrderedExpressions+, used for sorting by columns
|
|
428
751
|
# or more complex expressions.
|
|
429
752
|
module OrderMethods
|
|
@@ -856,7 +1179,8 @@ module Sequel
|
|
|
856
1179
|
# substituted for :key phrases).
|
|
857
1180
|
attr_reader :args
|
|
858
1181
|
|
|
859
|
-
# The literal string containing placeholders
|
|
1182
|
+
# The literal string containing placeholders. This can also be an array
|
|
1183
|
+
# of strings, where each arg in args goes between the string elements.
|
|
860
1184
|
attr_reader :str
|
|
861
1185
|
|
|
862
1186
|
# Whether to surround the expression with parantheses
|
|
@@ -944,7 +1268,7 @@ module Sequel
|
|
|
944
1268
|
@table, @column = table, column
|
|
945
1269
|
end
|
|
946
1270
|
|
|
947
|
-
to_s_method :qualified_identifier_sql
|
|
1271
|
+
to_s_method :qualified_identifier_sql, "@table, @column"
|
|
948
1272
|
end
|
|
949
1273
|
|
|
950
1274
|
# Subclass of +ComplexExpression+ where the expression results
|
|
@@ -1023,9 +1347,21 @@ module Sequel
|
|
|
1023
1347
|
|
|
1024
1348
|
# Create a new +Subscript+ appending the given subscript(s)
|
|
1025
1349
|
# the the current array of subscripts.
|
|
1350
|
+
#
|
|
1351
|
+
# :a.sql_subscript(2) # a[2]
|
|
1352
|
+
# :a.sql_subscript(2) | 1 # a[2, 1]
|
|
1026
1353
|
def |(sub)
|
|
1027
1354
|
Subscript.new(@f, @sub + Array(sub))
|
|
1028
1355
|
end
|
|
1356
|
+
|
|
1357
|
+
# Create a new +Subscript+ by accessing a subarray of a multidimensional
|
|
1358
|
+
# array.
|
|
1359
|
+
#
|
|
1360
|
+
# :a.sql_subscript(2) # a[2]
|
|
1361
|
+
# :a.sql_subscript(2)[1] # a[2][1]
|
|
1362
|
+
def [](sub)
|
|
1363
|
+
Subscript.new(self, Array(sub))
|
|
1364
|
+
end
|
|
1029
1365
|
|
|
1030
1366
|
to_s_method :subscript_sql
|
|
1031
1367
|
end
|
|
@@ -1076,28 +1412,71 @@ module Sequel
|
|
|
1076
1412
|
# Examples:
|
|
1077
1413
|
#
|
|
1078
1414
|
# ds = DB[:t]
|
|
1415
|
+
#
|
|
1079
1416
|
# # Argument yielded to block
|
|
1080
1417
|
# ds.filter{|r| r.name < 2} # SELECT * FROM t WHERE (name < 2)
|
|
1418
|
+
#
|
|
1081
1419
|
# # Block without argument (instance_eval)
|
|
1082
1420
|
# ds.filter{name < 2} # SELECT * FROM t WHERE (name < 2)
|
|
1421
|
+
#
|
|
1083
1422
|
# # Qualified identifiers
|
|
1084
1423
|
# ds.filter{table__column + 1 < 2} # SELECT * FROM t WHERE ((table.column + 1) < 2)
|
|
1424
|
+
#
|
|
1085
1425
|
# # Functions
|
|
1086
1426
|
# ds.filter{is_active(1, 'arg2')} # SELECT * FROM t WHERE is_active(1, 'arg2')
|
|
1087
1427
|
# ds.select{version{}} # SELECT version() FROM t
|
|
1088
1428
|
# ds.select{count(:*){}} # SELECT count(*) FROM t
|
|
1089
1429
|
# ds.select{count(:distinct, col1){}} # SELECT count(DISTINCT col1) FROM t
|
|
1430
|
+
#
|
|
1090
1431
|
# # Window Functions
|
|
1091
1432
|
# ds.select{rank(:over){}} # SELECT rank() OVER () FROM t
|
|
1092
1433
|
# ds.select{count(:over, :*=>true){}} # SELECT count(*) OVER () FROM t
|
|
1093
1434
|
# ds.select{sum(:over, :args=>col1, :partition=>col2, :order=>col3){}} # SELECT sum(col1) OVER (PARTITION BY col2 ORDER BY col3) FROM t
|
|
1094
1435
|
#
|
|
1436
|
+
# # Math Operators
|
|
1437
|
+
# ds.select{|o| o.+(1, :a).as(:b)} # SELECT (1 + a) AS b FROM t
|
|
1438
|
+
# ds.select{|o| o.-(2, :a).as(:b)} # SELECT (2 - a) AS b FROM t
|
|
1439
|
+
# ds.select{|o| o.*(3, :a).as(:b)} # SELECT (3 * a) AS b FROM t
|
|
1440
|
+
# ds.select{|o| o./(4, :a).as(:b)} # SELECT (4 / a) AS b FROM t
|
|
1441
|
+
#
|
|
1442
|
+
# # Boolean Operators
|
|
1443
|
+
# ds.filter{|o| o.&({:a=>1}, :b)} # SELECT * FROM t WHERE ((a = 1) AND b)
|
|
1444
|
+
# ds.filter{|o| o.|({:a=>1}, :b)} # SELECT * FROM t WHERE ((a = 1) OR b)
|
|
1445
|
+
# ds.filter{|o| o.~({:a=>1})} # SELECT * FROM t WHERE (a != 1)
|
|
1446
|
+
# ds.filter{|o| o.~({:a=>1, :b=>2})} # SELECT * FROM t WHERE ((a != 1) OR (b != 2))
|
|
1447
|
+
#
|
|
1448
|
+
# # Inequality Operators
|
|
1449
|
+
# ds.filter{|o| o.>(1, :a)} # SELECT * FROM t WHERE (1 > a)
|
|
1450
|
+
# ds.filter{|o| o.<(2, :a)} # SELECT * FROM t WHERE (2 < a)
|
|
1451
|
+
# ds.filter{|o| o.>=(3, :a)} # SELECT * FROM t WHERE (3 >= a)
|
|
1452
|
+
# ds.filter{|o| o.<=(4, :a)} # SELECT * FROM t WHERE (4 <= a)
|
|
1453
|
+
#
|
|
1454
|
+
# # Literal Strings
|
|
1455
|
+
# ds.filter{{a=>`some SQL`}} # SELECT * FROM t WHERE (a = some SQL)
|
|
1456
|
+
#
|
|
1095
1457
|
# For a more detailed explanation, see the {Virtual Rows guide}[link:files/doc/virtual_rows_rdoc.html].
|
|
1096
1458
|
class VirtualRow < BasicObject
|
|
1097
1459
|
WILDCARD = LiteralString.new('*').freeze
|
|
1098
1460
|
QUESTION_MARK = LiteralString.new('?').freeze
|
|
1099
1461
|
COMMA_SEPARATOR = LiteralString.new(', ').freeze
|
|
1100
1462
|
DOUBLE_UNDERSCORE = '__'.freeze
|
|
1463
|
+
DISTINCT = ["DISTINCT ".freeze].freeze
|
|
1464
|
+
COMMA_ARRAY = [COMMA_SEPARATOR].freeze
|
|
1465
|
+
|
|
1466
|
+
include OperatorBuilders
|
|
1467
|
+
|
|
1468
|
+
%w'> < >= <='.each do |op|
|
|
1469
|
+
class_eval(<<-END, __FILE__, __LINE__ + 1)
|
|
1470
|
+
def #{op}(*args)
|
|
1471
|
+
SQL::BooleanExpression.new(:#{op}, *args)
|
|
1472
|
+
end
|
|
1473
|
+
END
|
|
1474
|
+
end
|
|
1475
|
+
|
|
1476
|
+
# Return a literal string created with the given string.
|
|
1477
|
+
def `(s)
|
|
1478
|
+
Sequel::LiteralString.new(s)
|
|
1479
|
+
end
|
|
1101
1480
|
|
|
1102
1481
|
# Return an +Identifier+, +QualifiedIdentifier+, +Function+, or +WindowFunction+, depending
|
|
1103
1482
|
# on arguments and whether a block is provided. Does not currently call the block.
|
|
@@ -1111,7 +1490,7 @@ module Sequel
|
|
|
1111
1490
|
when :*
|
|
1112
1491
|
Function.new(m, WILDCARD)
|
|
1113
1492
|
when :distinct
|
|
1114
|
-
Function.new(m, PlaceholderLiteralString.new(
|
|
1493
|
+
Function.new(m, PlaceholderLiteralString.new(DISTINCT + COMMA_ARRAY * (args.length-1), args))
|
|
1115
1494
|
when :over
|
|
1116
1495
|
opts = args.shift || {}
|
|
1117
1496
|
fun_args = ::Kernel.Array(opts[:*] ? WILDCARD : opts[:args])
|
|
@@ -1165,6 +1544,20 @@ module Sequel
|
|
|
1165
1544
|
|
|
1166
1545
|
to_s_method :window_function_sql, '@function, @window'
|
|
1167
1546
|
end
|
|
1547
|
+
|
|
1548
|
+
# A +Wrapper+ is a simple way to wrap an existing object so that it supports
|
|
1549
|
+
# the Sequel DSL.
|
|
1550
|
+
class Wrapper < GenericExpression
|
|
1551
|
+
# The underlying value wrapped by this object.
|
|
1552
|
+
attr_reader :value
|
|
1553
|
+
|
|
1554
|
+
# Set the value wrapped by the object.
|
|
1555
|
+
def initialize(value)
|
|
1556
|
+
@value = value
|
|
1557
|
+
end
|
|
1558
|
+
|
|
1559
|
+
to_s_method :literal, '@value'
|
|
1560
|
+
end
|
|
1168
1561
|
end
|
|
1169
1562
|
|
|
1170
1563
|
# +LiteralString+ is used to represent literal SQL expressions. A
|
|
@@ -1177,7 +1570,24 @@ module Sequel
|
|
|
1177
1570
|
include SQL::NumericMethods
|
|
1178
1571
|
include SQL::StringMethods
|
|
1179
1572
|
include SQL::InequalityMethods
|
|
1573
|
+
|
|
1574
|
+
# If the core extensions are enabled, these will already be included
|
|
1575
|
+
# in String, so we don't need to include/define them here.
|
|
1576
|
+
unless Sequel.core_extensions?
|
|
1577
|
+
include Sequel::SQL::AliasMethods
|
|
1578
|
+
include Sequel::SQL::CastMethods
|
|
1579
|
+
|
|
1580
|
+
def lit(*args)
|
|
1581
|
+
args.empty? ? self : SQL::PlaceholderLiteralString.new(self, args)
|
|
1582
|
+
end
|
|
1583
|
+
|
|
1584
|
+
def to_sequel_blob
|
|
1585
|
+
SQL::Blob.new(self)
|
|
1586
|
+
end
|
|
1587
|
+
end
|
|
1180
1588
|
end
|
|
1181
1589
|
|
|
1182
1590
|
include SQL::Constants
|
|
1591
|
+
extend SQL::Builders
|
|
1592
|
+
extend SQL::OperatorBuilders
|
|
1183
1593
|
end
|