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.
Files changed (152) hide show
  1. data/CHANGELOG +140 -0
  2. data/Rakefile +7 -0
  3. data/bin/sequel +22 -2
  4. data/doc/dataset_basics.rdoc +1 -1
  5. data/doc/mass_assignment.rdoc +3 -1
  6. data/doc/querying.rdoc +28 -4
  7. data/doc/reflection.rdoc +23 -3
  8. data/doc/release_notes/3.34.0.txt +671 -0
  9. data/doc/schema_modification.rdoc +18 -2
  10. data/doc/virtual_rows.rdoc +49 -0
  11. data/lib/sequel/adapters/do/mysql.rb +0 -5
  12. data/lib/sequel/adapters/ibmdb.rb +9 -4
  13. data/lib/sequel/adapters/jdbc.rb +9 -4
  14. data/lib/sequel/adapters/jdbc/h2.rb +8 -2
  15. data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
  18. data/lib/sequel/adapters/mock.rb +24 -3
  19. data/lib/sequel/adapters/mysql.rb +29 -50
  20. data/lib/sequel/adapters/mysql2.rb +13 -28
  21. data/lib/sequel/adapters/oracle.rb +8 -2
  22. data/lib/sequel/adapters/postgres.rb +115 -20
  23. data/lib/sequel/adapters/shared/db2.rb +1 -1
  24. data/lib/sequel/adapters/shared/mssql.rb +14 -3
  25. data/lib/sequel/adapters/shared/mysql.rb +59 -11
  26. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  28. data/lib/sequel/adapters/shared/postgres.rb +127 -30
  29. data/lib/sequel/adapters/shared/sqlite.rb +55 -38
  30. data/lib/sequel/adapters/sqlite.rb +9 -3
  31. data/lib/sequel/adapters/swift.rb +2 -2
  32. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  33. data/lib/sequel/adapters/swift/postgres.rb +10 -0
  34. data/lib/sequel/ast_transformer.rb +4 -0
  35. data/lib/sequel/connection_pool.rb +8 -0
  36. data/lib/sequel/connection_pool/sharded_single.rb +5 -0
  37. data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
  38. data/lib/sequel/connection_pool/single.rb +5 -0
  39. data/lib/sequel/connection_pool/threaded.rb +14 -0
  40. data/lib/sequel/core.rb +24 -3
  41. data/lib/sequel/database/connecting.rb +24 -14
  42. data/lib/sequel/database/dataset_defaults.rb +1 -0
  43. data/lib/sequel/database/misc.rb +16 -25
  44. data/lib/sequel/database/query.rb +20 -2
  45. data/lib/sequel/database/schema_generator.rb +2 -2
  46. data/lib/sequel/database/schema_methods.rb +120 -23
  47. data/lib/sequel/dataset/actions.rb +91 -18
  48. data/lib/sequel/dataset/features.rb +5 -0
  49. data/lib/sequel/dataset/prepared_statements.rb +6 -2
  50. data/lib/sequel/dataset/sql.rb +68 -51
  51. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  52. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
  53. data/lib/sequel/extensions/migration.rb +4 -0
  54. data/lib/sequel/extensions/null_dataset.rb +90 -0
  55. data/lib/sequel/extensions/pg_array.rb +460 -0
  56. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  57. data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
  58. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  59. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  60. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  61. data/lib/sequel/extensions/pretty_table.rb +5 -71
  62. data/lib/sequel/extensions/query_literals.rb +79 -0
  63. data/lib/sequel/extensions/schema_caching.rb +76 -0
  64. data/lib/sequel/extensions/schema_dumper.rb +227 -31
  65. data/lib/sequel/extensions/select_remove.rb +35 -0
  66. data/lib/sequel/extensions/sql_expr.rb +4 -110
  67. data/lib/sequel/extensions/to_dot.rb +1 -1
  68. data/lib/sequel/model.rb +11 -2
  69. data/lib/sequel/model/associations.rb +35 -7
  70. data/lib/sequel/model/base.rb +159 -36
  71. data/lib/sequel/no_core_ext.rb +2 -0
  72. data/lib/sequel/plugins/caching.rb +25 -18
  73. data/lib/sequel/plugins/composition.rb +1 -1
  74. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  75. data/lib/sequel/plugins/identity_map.rb +11 -3
  76. data/lib/sequel/plugins/instance_filters.rb +10 -0
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  78. data/lib/sequel/plugins/nested_attributes.rb +4 -3
  79. data/lib/sequel/plugins/prepared_statements.rb +3 -1
  80. data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
  81. data/lib/sequel/plugins/schema.rb +7 -2
  82. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  83. data/lib/sequel/plugins/static_cache.rb +99 -0
  84. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  85. data/lib/sequel/sql.rb +417 -7
  86. data/lib/sequel/version.rb +1 -1
  87. data/spec/adapters/firebird_spec.rb +1 -1
  88. data/spec/adapters/mssql_spec.rb +12 -15
  89. data/spec/adapters/mysql_spec.rb +81 -23
  90. data/spec/adapters/postgres_spec.rb +444 -77
  91. data/spec/adapters/spec_helper.rb +2 -0
  92. data/spec/adapters/sqlite_spec.rb +8 -8
  93. data/spec/core/connection_pool_spec.rb +85 -0
  94. data/spec/core/database_spec.rb +29 -5
  95. data/spec/core/dataset_spec.rb +171 -3
  96. data/spec/core/expression_filters_spec.rb +364 -0
  97. data/spec/core/mock_adapter_spec.rb +17 -3
  98. data/spec/core/schema_spec.rb +133 -0
  99. data/spec/extensions/association_dependencies_spec.rb +13 -13
  100. data/spec/extensions/caching_spec.rb +26 -3
  101. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  102. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
  103. data/spec/extensions/force_encoding_spec.rb +4 -2
  104. data/spec/extensions/hook_class_methods_spec.rb +5 -2
  105. data/spec/extensions/identity_map_spec.rb +17 -0
  106. data/spec/extensions/instance_filters_spec.rb +1 -1
  107. data/spec/extensions/lazy_attributes_spec.rb +2 -2
  108. data/spec/extensions/list_spec.rb +4 -4
  109. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  110. data/spec/extensions/migration_spec.rb +6 -2
  111. data/spec/extensions/nested_attributes_spec.rb +20 -0
  112. data/spec/extensions/null_dataset_spec.rb +85 -0
  113. data/spec/extensions/optimistic_locking_spec.rb +2 -2
  114. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  115. data/spec/extensions/pg_array_spec.rb +196 -0
  116. data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
  117. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  118. data/spec/extensions/pg_hstore_spec.rb +195 -0
  119. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  120. data/spec/extensions/prepared_statements_spec.rb +4 -0
  121. data/spec/extensions/pretty_table_spec.rb +6 -0
  122. data/spec/extensions/query_literals_spec.rb +168 -0
  123. data/spec/extensions/schema_caching_spec.rb +41 -0
  124. data/spec/extensions/schema_dumper_spec.rb +231 -11
  125. data/spec/extensions/schema_spec.rb +14 -2
  126. data/spec/extensions/select_remove_spec.rb +38 -0
  127. data/spec/extensions/sharding_spec.rb +6 -6
  128. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  129. data/spec/extensions/spec_helper.rb +2 -1
  130. data/spec/extensions/sql_expr_spec.rb +28 -19
  131. data/spec/extensions/static_cache_spec.rb +145 -0
  132. data/spec/extensions/touch_spec.rb +1 -1
  133. data/spec/extensions/typecast_on_load_spec.rb +9 -1
  134. data/spec/integration/associations_test.rb +6 -6
  135. data/spec/integration/database_test.rb +1 -1
  136. data/spec/integration/dataset_test.rb +89 -26
  137. data/spec/integration/migrator_test.rb +2 -3
  138. data/spec/integration/model_test.rb +3 -3
  139. data/spec/integration/plugin_test.rb +85 -22
  140. data/spec/integration/prepared_statement_test.rb +28 -8
  141. data/spec/integration/schema_test.rb +78 -7
  142. data/spec/integration/spec_helper.rb +1 -0
  143. data/spec/integration/timezone_test.rb +1 -1
  144. data/spec/integration/transaction_test.rb +4 -6
  145. data/spec/integration/type_test.rb +2 -2
  146. data/spec/model/associations_spec.rb +94 -8
  147. data/spec/model/base_spec.rb +4 -4
  148. data/spec/model/hooks_spec.rb +2 -2
  149. data/spec/model/model_spec.rb +19 -7
  150. data/spec/model/record_spec.rb +135 -58
  151. data/spec/model/spec_helper.rb +1 -0
  152. metadata +35 -7
@@ -0,0 +1,2 @@
1
+ ::SEQUEL_NO_CORE_EXTENSIONS = true
2
+ require 'sequel'
@@ -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
- # Set the time to live for the cache store, in seconds (default is 3600, # so 1 hour).
57
- def set_cache_ttl(ttl)
58
- @cache_ttl = ttl
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
- raise(Error, "No primary key is associated with this model") unless key = primary_key
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.send(:cache_delete, cache_key)
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, :date,
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 _load_associated_objects(opts, dynamic_opts={})
231
+ def _load_associated_object(opts, dynamic_opts)
232
232
  klass = opts.associated_class
233
- if !dynamic_opts[:callback] && klass.respond_to?(:identity_map) && idm = klass.identity_map and opts[:type] == :many_to_one and opts.primary_key == klass.primary_key and
234
- opts[:key] and pk = _associated_object_pk(opts[:key]) and o = idm[klass.identity_map_key(pk)]
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(associated_objects = send(reflection[:name])).find{|x| x.pk.to_s == pk}
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
- if klass.db.send(:typecast_value_boolean, attributes[:_delete] || attributes['_delete']) && reflection[:nested_attributes][:destroy]
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[:_remove] || attributes['_remove']) && reflection[:nested_attributes][:remove]
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] ||= _associated_dataset(opts, {}).unbind.first.prepare(opts.returns_array? ? :select : :first, :"smpsap_#{NEXT.call}")
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 rescue nil
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.
@@ -117,7 +117,7 @@ module Sequel
117
117
  @sti_dataset = sd
118
118
  @sti_key_map = skm
119
119
  @sti_model_map = smm
120
- @simple_table = nil
120
+ self.simple_table = nil
121
121
  end
122
122
  end
123
123
 
@@ -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("DISTINCT #{args.map{QUESTION_MARK}.join(COMMA_SEPARATOR)}", args))
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