sequel_core 1.4.0 → 1.5.0

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