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