sequel_core 1.4.0 → 1.5.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 (51) hide show
  1. data/CHANGELOG +74 -0
  2. data/COPYING +1 -0
  3. data/README +17 -6
  4. data/Rakefile +16 -21
  5. data/lib/sequel_core.rb +18 -28
  6. data/lib/sequel_core/adapters/ado.rb +3 -15
  7. data/lib/sequel_core/adapters/dbi.rb +1 -14
  8. data/lib/sequel_core/adapters/informix.rb +3 -3
  9. data/lib/sequel_core/adapters/jdbc.rb +2 -2
  10. data/lib/sequel_core/adapters/mysql.rb +39 -59
  11. data/lib/sequel_core/adapters/odbc.rb +18 -38
  12. data/lib/sequel_core/adapters/openbase.rb +1 -17
  13. data/lib/sequel_core/adapters/oracle.rb +1 -19
  14. data/lib/sequel_core/adapters/postgres.rb +20 -60
  15. data/lib/sequel_core/adapters/sqlite.rb +4 -8
  16. data/lib/sequel_core/connection_pool.rb +150 -0
  17. data/lib/sequel_core/core_ext.rb +41 -0
  18. data/lib/sequel_core/core_sql.rb +35 -38
  19. data/lib/sequel_core/database.rb +20 -17
  20. data/lib/sequel_core/dataset.rb +49 -80
  21. data/lib/sequel_core/dataset/callback.rb +11 -13
  22. data/lib/sequel_core/dataset/convenience.rb +18 -136
  23. data/lib/sequel_core/dataset/pagination.rb +81 -0
  24. data/lib/sequel_core/dataset/sequelizer.rb +5 -4
  25. data/lib/sequel_core/dataset/sql.rb +43 -33
  26. data/lib/sequel_core/deprecated.rb +200 -0
  27. data/lib/sequel_core/exceptions.rb +0 -14
  28. data/lib/sequel_core/object_graph.rb +199 -0
  29. data/lib/sequel_core/pretty_table.rb +27 -24
  30. data/lib/sequel_core/schema/generator.rb +16 -4
  31. data/lib/sequel_core/schema/sql.rb +5 -3
  32. data/lib/sequel_core/worker.rb +1 -1
  33. data/spec/adapters/informix_spec.rb +1 -47
  34. data/spec/adapters/mysql_spec.rb +85 -54
  35. data/spec/adapters/oracle_spec.rb +1 -57
  36. data/spec/adapters/postgres_spec.rb +66 -49
  37. data/spec/adapters/sqlite_spec.rb +4 -29
  38. data/spec/connection_pool_spec.rb +358 -0
  39. data/spec/core_sql_spec.rb +24 -19
  40. data/spec/database_spec.rb +13 -9
  41. data/spec/dataset_spec.rb +59 -78
  42. data/spec/object_graph_spec.rb +202 -0
  43. data/spec/pretty_table_spec.rb +1 -9
  44. data/spec/schema_generator_spec.rb +7 -1
  45. data/spec/schema_spec.rb +27 -0
  46. data/spec/sequelizer_spec.rb +2 -2
  47. data/spec/spec_helper.rb +4 -2
  48. metadata +16 -57
  49. data/lib/sequel_core/array_keys.rb +0 -322
  50. data/lib/sequel_core/model.rb +0 -8
  51. data/spec/array_keys_spec.rb +0 -682
@@ -17,9 +17,50 @@ end
17
17
 
18
18
  # Object extensions
19
19
  class Object
20
+ # Returns true if the object is a object of one of the classes
20
21
  def is_one_of?(*classes)
21
22
  classes.each {|c| return c if is_a?(c)}
22
23
  nil
23
24
  end
25
+
26
+ # Objects are blank if they respond true to empty?
27
+ def blank?
28
+ nil? || (respond_to?(:empty?) && empty?)
29
+ end
30
+ end
31
+
32
+ class Numeric
33
+ # Numerics are never blank (not even 0)
34
+ def blank?
35
+ false
36
+ end
37
+ end
38
+
39
+ class NilClass
40
+ # nil is always blank
41
+ def blank?
42
+ true
43
+ end
24
44
  end
25
45
 
46
+ class TrueClass
47
+ # true is never blank
48
+ def blank?
49
+ false
50
+ end
51
+ end
52
+
53
+ class FalseClass
54
+ # false is always blank
55
+ def blank?
56
+ true
57
+ end
58
+ end
59
+
60
+ class String
61
+ BLANK_STRING_REGEXP = /\A\s*\z/
62
+ # Strings are blank if they are empty or include only whitespace
63
+ def blank?
64
+ empty? || BLANK_STRING_REGEXP.match(self)
65
+ end
66
+ end
@@ -11,7 +11,7 @@ end
11
11
  module Sequel
12
12
  # LiteralString is used to represent literal SQL expressions. An
13
13
  # LiteralString is copied verbatim into an SQL statement. Instances of
14
- # LiteralString can be created by calling String#expr.
14
+ # LiteralString can be created by calling String#lit.
15
15
  class LiteralString < ::String
16
16
  end
17
17
  end
@@ -21,7 +21,7 @@ class String
21
21
  # Converts a string into an SQL string by removing comments.
22
22
  # See also Array#to_sql.
23
23
  def to_sql
24
- split($/).to_sql
24
+ split("\n").to_sql
25
25
  end
26
26
 
27
27
  # Splits a string into separate SQL statements, removing comments
@@ -67,7 +67,27 @@ end
67
67
 
68
68
  module Sequel
69
69
  module SQL
70
+ module ColumnMethods
71
+ AS = 'AS'.freeze
72
+ DESC = 'DESC'.freeze
73
+ ASC = 'ASC'.freeze
74
+
75
+ def as(a); ColumnExpr.new(self, AS, a); end
76
+
77
+ def desc; ColumnExpr.new(self, DESC); end
78
+
79
+ def asc; ColumnExpr.new(self, ASC); end
80
+
81
+ def cast_as(t)
82
+ if t.is_a?(Symbol)
83
+ t = t.to_s.lit
84
+ end
85
+ Sequel::SQL::Function.new(:cast, self.as(t))
86
+ end
87
+ end
88
+
70
89
  class Expression
90
+ include ColumnMethods
71
91
  def lit; self; end
72
92
  end
73
93
 
@@ -91,8 +111,15 @@ module Sequel
91
111
  end
92
112
 
93
113
  class Function < Expression
114
+ attr_reader :f, :args
94
115
  def initialize(f, *args); @f, @args = f, args; end
95
116
 
117
+ # Functions are considered equivalent if they
118
+ # have the same class, function, and arguments.
119
+ def ==(x)
120
+ x.class == self.class && @f == x.f && @args == x.args
121
+ end
122
+
96
123
  def to_s(ds)
97
124
  args = @args.empty? ? '' : ds.literal(@args)
98
125
  "#{@f}(#{args})"
@@ -119,39 +146,19 @@ module Sequel
119
146
  def initialize(t); @t = t; end
120
147
  def to_s(ds); "#{@t}.*"; end
121
148
  end
122
-
123
- module ColumnMethods
124
- AS = 'AS'.freeze
125
- DESC = 'DESC'.freeze
126
- ASC = 'ASC'.freeze
127
-
128
- def as(a); ColumnExpr.new(self, AS, a); end
129
- alias_method :AS, :as
130
-
131
- def desc; ColumnExpr.new(self, DESC); end
132
- alias_method :DESC, :desc
133
-
134
- def asc; ColumnExpr.new(self, ASC); end
135
- alias_method :ASC, :asc
136
-
137
- def all; Sequel::SQL::ColumnAll.new(self); end
138
- alias_method :ALL, :all
139
-
140
- def cast_as(t)
141
- if t.is_a?(Symbol)
142
- t = t.to_s.lit
143
- end
144
- Sequel::SQL::Function.new(:cast, self.as(t))
145
- end
146
- end
147
149
  end
148
150
  end
149
151
 
150
- class Object
152
+ class String
151
153
  include Sequel::SQL::ColumnMethods
152
154
  end
153
155
 
154
156
  class Symbol
157
+ include Sequel::SQL::ColumnMethods
158
+ def *
159
+ Sequel::SQL::ColumnAll.new(self);
160
+ end
161
+
155
162
  def [](*args); Sequel::SQL::Function.new(self, *args); end
156
163
  def |(sub)
157
164
  unless Array === sub
@@ -186,14 +193,4 @@ class Symbol
186
193
  ds.quote_column_ref(s)
187
194
  end
188
195
  end
189
-
190
- # Converts missing method calls into functions on columns, if the
191
- # method name is made of all upper case letters.
192
- def method_missing(sym, *args)
193
- if ((s = sym.to_s) =~ /^([A-Z]+)$/)
194
- Sequel::SQL::Function.new(s.downcase, self)
195
- else
196
- super
197
- end
198
- end
199
196
  end
@@ -1,19 +1,20 @@
1
1
  require 'uri'
2
2
 
3
3
  module Sequel
4
+ DATABASES = []
4
5
  # A Database object represents a virtual connection to a database.
5
6
  # The Database class is meant to be subclassed by database adapters in order
6
7
  # to provide the functionality needed for executing queries.
7
8
  class Database
9
+ ADAPTERS = %w'ado db2 dbi informix jdbc mysql odbc odbc_mssql openbase oracle postgres sqlite'.collect{|x| x.to_sym}
8
10
  attr_reader :opts, :pool
9
11
  attr_accessor :logger
10
-
12
+
11
13
  # Constructs a new instance of a database connection with the specified
12
14
  # options hash.
13
15
  #
14
16
  # Sequel::Database is an abstract class that is not useful by itself.
15
17
  def initialize(opts = {}, &block)
16
- Model.database_opened(self)
17
18
  @opts = opts
18
19
 
19
20
  # Determine if the DB is single threaded or multi threaded
@@ -27,6 +28,7 @@ module Sequel
27
28
  @pool.connection_proc = block || proc {connect}
28
29
 
29
30
  @logger = opts[:logger]
31
+ ::Sequel::DATABASES.push(self)
30
32
  end
31
33
 
32
34
  # Connects to the database. This method should be overriden by descendants.
@@ -167,7 +169,6 @@ module Sequel
167
169
  true
168
170
  end
169
171
 
170
- # include Dataset::SQL
171
172
  include Schema::SQL
172
173
 
173
174
  # default serial primary key definition. this should be overriden for each adapter.
@@ -352,23 +353,26 @@ module Sequel
352
353
  # Returns a string representation of the database object including the
353
354
  # class name and the connection URI.
354
355
  def inspect
355
- '#<%s: %s>' % [self.class.to_s, uri.inspect]
356
+ "#<#{self.class}: #{(uri rescue opts).inspect}>"
356
357
  end
357
358
 
358
359
  @@adapters = Hash.new
359
360
 
360
- # Sets the adapter scheme for the Database class. Call this method in
361
- # descendnants of Database to allow connection using a URL. For example the
362
- # following:
363
- # class DB2::Database < Sequel::Database
364
- # set_adapter_scheme :db2
365
- # ...
366
- # end
367
- # would allow connection using:
368
- # Sequel.open('db2://user:password@dbserver/mydb')
369
- def self.set_adapter_scheme(scheme)
370
- @scheme = scheme
371
- @@adapters[scheme.to_sym] = self
361
+ class << self
362
+ private
363
+ # Sets the adapter scheme for the Database class. Call this method in
364
+ # descendnants of Database to allow connection using a URL. For example the
365
+ # following:
366
+ # class DB2::Database < Sequel::Database
367
+ # set_adapter_scheme :db2
368
+ # ...
369
+ # end
370
+ # would allow connection using:
371
+ # Sequel.open('db2://user:password@dbserver/mydb')
372
+ def set_adapter_scheme(scheme)
373
+ @scheme = scheme
374
+ @@adapters[scheme.to_sym] = self
375
+ end
372
376
  end
373
377
 
374
378
  # Returns the scheme for the Database class.
@@ -392,7 +396,6 @@ module Sequel
392
396
  end
393
397
 
394
398
  def self.adapter_class(scheme)
395
- adapter_name = scheme.to_s =~ /\-/ ? scheme.to_s.gsub('-', '_').to_sym : scheme.to_sym
396
399
  scheme = scheme.to_sym
397
400
 
398
401
  if (klass = @@adapters[scheme]).nil?
@@ -7,6 +7,7 @@ require File.join(File.dirname(__FILE__), 'dataset/sql')
7
7
  require File.join(File.dirname(__FILE__), 'dataset/sequelizer')
8
8
  require File.join(File.dirname(__FILE__), 'dataset/convenience')
9
9
  require File.join(File.dirname(__FILE__), 'dataset/callback')
10
+ require File.join(File.dirname(__FILE__), 'dataset/pagination')
10
11
 
11
12
  module Sequel
12
13
  # A Dataset represents a view of a the data in a database, constrained by
@@ -25,7 +26,7 @@ module Sequel
25
26
  # use different datasets to access data:
26
27
  # posts = DB[:posts]
27
28
  # davids_posts = posts.filter(:author => 'david')
28
- # old_posts = posts.filter('stamp < ?', 1.week.ago)
29
+ # old_posts = posts.filter('stamp < ?', Date.today - 7)
29
30
  #
30
31
  # Datasets are Enumerable objects, so they can be manipulated using any
31
32
  # of the Enumerable methods, such as map, inject, etc.
@@ -72,8 +73,7 @@ module Sequel
72
73
  include Convenience
73
74
  include Callback
74
75
 
75
- attr_accessor :db
76
- attr_accessor :opts
76
+ attr_accessor :db, :opts, :row_proc
77
77
 
78
78
  alias_method :size, :count
79
79
 
@@ -82,7 +82,7 @@ module Sequel
82
82
  def all(opts = nil, &block)
83
83
  a = []
84
84
  each(opts) {|r| a << r}
85
- run_callback(:post_load, a)
85
+ post_load(a)
86
86
  a.each(&block) if block
87
87
  a
88
88
  end
@@ -106,15 +106,10 @@ module Sequel
106
106
  # Returns a new clone of the dataset with with the given options merged.
107
107
  def clone(opts = {})
108
108
  c = super()
109
- c.set_options @opts.merge(opts)
109
+ c.opts = @opts.merge(opts)
110
+ c.instance_variable_set(:@columns, nil)
110
111
  c
111
112
  end
112
- alias_method :clone_merge, :clone # should be deprecated in the next major release.
113
-
114
- def set_options(opts) #:nodoc:
115
- @opts = opts
116
- @columns = nil
117
- end
118
113
 
119
114
  NOTIMPL_MSG = "This method must be overriden in Sequel adapters".freeze
120
115
 
@@ -179,7 +174,17 @@ module Sequel
179
174
 
180
175
  # Iterates over the records in the dataset
181
176
  def each(opts = nil, &block)
182
- fetch_rows(select_sql(opts), &block)
177
+ if graph = @opts[:graph]
178
+ graph_each(opts, &block)
179
+ else
180
+ row_proc = @row_proc unless opts && opts[:naked]
181
+ transform = @transform
182
+ fetch_rows(select_sql(opts)) do |r|
183
+ r = transform_load(r) if transform
184
+ r = row_proc[r] if row_proc
185
+ yield r
186
+ end
187
+ end
183
188
  self
184
189
  end
185
190
 
@@ -248,17 +253,17 @@ module Sequel
248
253
  when nil # set_model(nil) => no
249
254
  # no argument provided, so the dataset is denuded
250
255
  @opts.merge!(:naked => true, :models => nil, :polymorphic_key => nil)
251
- remove_row_proc
256
+ self.row_proc = nil
252
257
  # extend_with_stock_each
253
258
  when Class
254
259
  # isomorphic model
255
260
  @opts.merge!(:naked => nil, :models => {nil => key}, :polymorphic_key => nil)
256
261
  if key.respond_to?(:load)
257
262
  # the class has a values setter method, so we use it
258
- set_row_proc {|h| key.load(h, *args)}
263
+ self.row_proc = proc{|h| key.load(h, *args)}
259
264
  else
260
265
  # otherwise we just pass the hash to the constructor
261
- set_row_proc {|h| key.new(h, *args)}
266
+ self.row_proc = proc{|h| key.new(h, *args)}
262
267
  end
263
268
  extend_with_destroy
264
269
  when Symbol
@@ -267,14 +272,14 @@ module Sequel
267
272
  @opts.merge!(:naked => true, :models => hash, :polymorphic_key => key)
268
273
  if hash.values.first.respond_to?(:load)
269
274
  # the class has a values setter method, so we use it
270
- set_row_proc do |h|
275
+ self.row_proc = proc do |h|
271
276
  c = hash[h[key]] || hash[nil] || \
272
277
  raise(Error, "No matching model class for record (#{polymorphic_key} => #{h[polymorphic_key].inspect})")
273
278
  c.load(h, *args)
274
279
  end
275
280
  else
276
281
  # otherwise we just pass the hash to the constructor
277
- set_row_proc do |h|
282
+ self.row_proc = proc do |h|
278
283
  c = hash[h[key]] || hash[nil] || \
279
284
  raise(Error, "No matching model class for record (#{polymorphic_key} => #{h[polymorphic_key].inspect})")
280
285
  c.new(h, *args)
@@ -287,25 +292,6 @@ module Sequel
287
292
  self
288
293
  end
289
294
 
290
- # Overrides the each method to pass the values through a filter. The filter
291
- # receives as argument a hash containing the column values for the current
292
- # record. The filter should return a value which is then passed to the
293
- # iterating block. In order to elucidate, here's a contrived example:
294
- #
295
- # dataset.set_row_proc {|h| h.merge(:xxx => 'yyy')}
296
- # dataset.first[:xxx] #=> "yyy" # always!
297
- #
298
- def set_row_proc(&filter)
299
- @row_proc = filter
300
- update_each_method
301
- end
302
-
303
- # Removes the row making proc.
304
- def remove_row_proc
305
- @row_proc = nil
306
- update_each_method
307
- end
308
-
309
295
  STOCK_TRANSFORMS = {
310
296
  :marshal => [
311
297
  # for backwards-compatibility we support also non-base64-encoded values.
@@ -354,7 +340,6 @@ module Sequel
354
340
  end
355
341
  end
356
342
  end
357
- update_each_method
358
343
  self
359
344
  end
360
345
 
@@ -376,49 +361,6 @@ module Sequel
376
361
  end
377
362
  end
378
363
 
379
- # Updates the each method according to whether @row_proc and @transform are
380
- # set or not.
381
- def update_each_method
382
- # warning: ugly code generation ahead
383
- if @row_proc && @transform
384
- class << self
385
- def each(opts = nil, &block)
386
- if opts && opts[:naked]
387
- fetch_rows(select_sql(opts)) {|r| block[transform_load(r)]}
388
- else
389
- fetch_rows(select_sql(opts)) {|r| block[@row_proc[transform_load(r)]]}
390
- end
391
- self
392
- end
393
- end
394
- elsif @row_proc
395
- class << self
396
- def each(opts = nil, &block)
397
- if opts && opts[:naked]
398
- fetch_rows(select_sql(opts), &block)
399
- else
400
- fetch_rows(select_sql(opts)) {|r| block[@row_proc[r]]}
401
- end
402
- self
403
- end
404
- end
405
- elsif @transform
406
- class << self
407
- def each(opts = nil, &block)
408
- fetch_rows(select_sql(opts)) {|r| block[transform_load(r)]}
409
- self
410
- end
411
- end
412
- else
413
- class << self
414
- def each(opts = nil, &block)
415
- fetch_rows(select_sql(opts), &block)
416
- self
417
- end
418
- end
419
- end
420
- end
421
-
422
364
  # Extends the dataset with a destroy method, that calls destroy for each
423
365
  # record in the dataset.
424
366
  def extend_with_destroy
@@ -449,6 +391,33 @@ module Sequel
449
391
  def inspect
450
392
  '#<%s: %s>' % [self.class.to_s, sql.inspect]
451
393
  end
394
+
395
+ # Setup mutation (e.g. filter!) methods
396
+ def self.def_mutation_method(*meths)
397
+ meths.each do |meth|
398
+ class_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end")
399
+ end
400
+ end
401
+ def def_mutation_method(*meths)
402
+ meths.each do |meth|
403
+ instance_eval("def #{meth}!(*args, &block); mutation_method(:#{meth}, *args, &block) end")
404
+ end
405
+ end
406
+
407
+ MUTATION_METHODS = %w'and distinct exclude exists filter from from_self full_outer_join graph
408
+ group group_and_count group_by having inner_join intersect invert_order join
409
+ left_outer_join limit naked or order order_by order_more paginate query reject
410
+ reverse reverse_order right_outer_join select select_all select_more
411
+ set_graph_aliases set_model sort sort_by union unordered where'.collect{|x| x.to_sym}
412
+
413
+ def_mutation_method(*MUTATION_METHODS)
414
+
415
+ private
416
+ def mutation_method(meth, *args, &block)
417
+ copy = send(meth, *args, &block)
418
+ @opts.merge!(copy.opts)
419
+ self
420
+ end
452
421
  end
453
422
  end
454
423