sequel 3.33.0 → 3.34.0

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