sequel 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,25 @@
1
+ *0.1.2*
2
+
3
+ * The first opened database is automatically assigned to to Model.db.
4
+
5
+ * Removed SequelConnectionError. Exception class errors are converted to RuntimeError.
6
+
7
+ * Added support for UNION, INTERSECT and EXCEPT set operations.
8
+
9
+ * Fixed Dataset#single_record to return nil if no record is found.
10
+
11
+ * Updated specs to conform to RSpec 1.0.
12
+
13
+ * Added Model#find_or_create method.
14
+
15
+ * Fixed MySQL::Dataset#query_single (thanks Dries Harnie.)
16
+
17
+ * Added Model.subset method. Fixed Model.filter and Model.exclude to accept blocks.
18
+
19
+ * Added Database#uri method.
20
+
21
+ * Refactored and removed deprecated code in postgres adapter.
22
+
1
23
  *0.1.1*
2
24
 
3
25
  * More documentation for Dataset.
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
6
6
  include FileUtils
7
7
 
8
8
  NAME = "sequel"
9
- VERS = "0.1.1"
9
+ VERS = "0.1.2"
10
10
  CLEAN.include ['**/.*.sw?', 'pkg/*', '.config', 'doc/*', 'coverage/*']
11
11
  RDOC_OPTS = ['--quiet', '--title', "Sequel: Concise ORM for Ruby",
12
12
  "--opname", "index.html",
@@ -50,8 +50,6 @@ module Sequel
50
50
  #
51
51
  # If no connection is available, Pool#hold will block until a connection
52
52
  # is available.
53
- #
54
- # Errors raised inside a hold call are wrapped in a SequelConnectionError.
55
53
  def hold
56
54
  t = Thread.current
57
55
  if (conn = owned_connection(t))
@@ -65,10 +63,9 @@ module Sequel
65
63
  ensure
66
64
  release(t)
67
65
  end
68
- rescue SequelConnectionError => e
69
- raise e
70
66
  rescue Exception => e
71
- raise SequelConnectionError.new(e)
67
+ # if the error is not a StandardError it is converted into RuntimeError.
68
+ raise e.is_a?(StandardError) ? e : e.message
72
69
  end
73
70
 
74
71
  private
@@ -2,6 +2,7 @@ require 'uri'
2
2
 
3
3
  require File.join(File.dirname(__FILE__), 'schema')
4
4
  require File.join(File.dirname(__FILE__), 'dataset')
5
+ require File.join(File.dirname(__FILE__), 'model')
5
6
 
6
7
  module Sequel
7
8
  # A Database object represents a virtual connection to a database.
@@ -15,11 +16,30 @@ module Sequel
15
16
  #
16
17
  # Sequel::Database is an abstract class that is not useful by itself.
17
18
  def initialize(opts = {}, &block)
19
+ Model.database_opened(self)
18
20
  @opts = opts
19
21
  @pool = ConnectionPool.new(@opts[:max_connections] || 4, &block)
20
22
  @logger = opts[:logger]
21
23
  end
22
24
 
25
+ def uri
26
+ uri = URI::Generic.new(
27
+ self.class.adapter_scheme.to_s,
28
+ nil,
29
+ @opts[:host],
30
+ @opts[:port],
31
+ nil,
32
+ "/#{@opts[:database]}",
33
+ nil,
34
+ nil,
35
+ nil
36
+ )
37
+ uri.user = @opts[:user]
38
+ uri.password = @opts[:password]
39
+ uri.to_s
40
+ end
41
+ alias url uri # Because I don't care much for the semantic difference.
42
+
23
43
  # Returns a blank dataset
24
44
  def dataset
25
45
  Dataset.new(self)
@@ -114,7 +134,7 @@ module Sequel
114
134
 
115
135
  @@adapters = Hash.new
116
136
 
117
- # Sets the adapter scheme for the database class. Call this method in
137
+ # Sets the adapter scheme for the Database class. Call this method in
118
138
  # descendnants of Database to allow connection using a URL. For example the
119
139
  # following:
120
140
  # class DB2::Database < Sequel::Database
@@ -124,9 +144,15 @@ module Sequel
124
144
  # would allow connection using:
125
145
  # Sequel.open('db2://user:password@dbserver/mydb')
126
146
  def self.set_adapter_scheme(scheme)
147
+ @scheme = scheme
127
148
  @@adapters[scheme.to_sym] = self
128
149
  end
129
150
 
151
+ # Returns the scheme for the Database class.
152
+ def self.adapter_scheme
153
+ @scheme
154
+ end
155
+
130
156
  # Converts a uri to an options hash. These options are then passed
131
157
  # to a newly created database object.
132
158
  def self.uri_to_options(uri)
@@ -343,8 +343,20 @@ module Sequel
343
343
  raise SequelError, "Can only specify a HAVING clause on a grouped dataset"
344
344
  else
345
345
  filter(*cond, &block)
346
- end
347
- end
346
+ end
347
+ end
348
+
349
+ def union(dataset, all = false)
350
+ dup_merge(:union => dataset, :union_all => all)
351
+ end
352
+
353
+ def intersect(dataset, all = false)
354
+ dup_merge(:intersect => dataset, :intersect_all => all)
355
+ end
356
+
357
+ def except(dataset, all = false)
358
+ dup_merge(:except => dataset, :except_all => all)
359
+ end
348
360
 
349
361
  LEFT_OUTER_JOIN = 'LEFT OUTER JOIN'.freeze
350
362
  INNER_JOIN = 'INNER JOIN'.freeze
@@ -437,6 +449,17 @@ module Sequel
437
449
  end
438
450
  end
439
451
 
452
+ if union = opts[:union]
453
+ sql << (opts[:union_all] ? \
454
+ " UNION ALL #{union.sql}" : " UNION #{union.sql}")
455
+ elsif intersect = opts[:intersect]
456
+ sql << (opts[:intersect_all] ? \
457
+ " INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
458
+ elsif except = opts[:except]
459
+ sql << (opts[:except_all] ? \
460
+ " EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
461
+ end
462
+
440
463
  sql
441
464
  end
442
465
  alias sql select_sql
@@ -516,6 +539,7 @@ module Sequel
516
539
  # Returns the first record in the dataset.
517
540
  def single_record(opts = nil)
518
541
  each(opts) {|r| return r}
542
+ nil
519
543
  end
520
544
 
521
545
  # Returns the first value of the first reecord in the dataset.
data/lib/sequel/error.rb CHANGED
@@ -1,20 +1,2 @@
1
1
  class SequelError < StandardError
2
- end
3
-
4
- # This error class is used to wrap exceptions occuring inside calls to
5
- # ConnectionPool#hold. Sequel wraps any exception raised by the database
6
- # connection and provides it as a SequelConnectionError. The original
7
- # exception is provided through SequelConnectionError#original_exception.
8
- class SequelConnectionError < SequelError
9
- attr_reader :original_error
10
-
11
- def initialize(original_error)
12
- @original_error = original_error
13
- end
14
-
15
- def message
16
- "#{@original_error.class}: #{@original_error.message}"
17
- end
18
-
19
- alias_method :to_s, :message
20
- end
2
+ end
data/lib/sequel/model.rb CHANGED
@@ -9,6 +9,9 @@ module Sequel
9
9
  @db ||= ((superclass != Object) && (superclass.db)) || nil
10
10
  end
11
11
  def self.db=(db); @db = db; end
12
+ def self.database_opened(db)
13
+ @db = db if self == Model && !@db
14
+ end
12
15
 
13
16
  def self.table_name
14
17
  @table_name ||= ((superclass != Model) && (superclass.table_name)) || nil
@@ -90,6 +93,10 @@ module Sequel
90
93
  create_table
91
94
  end
92
95
 
96
+ def self.subset(name, *args, &block)
97
+ meta_def(name) {filter(*args, &block)}
98
+ end
99
+
93
100
  ONE_TO_ONE_PROC = "proc {i = @values[:%s]; %s[i] if i}".freeze
94
101
  ID_POSTFIX = "_id".freeze
95
102
  FROM_DATASET = "db[%s]".freeze
@@ -126,6 +133,10 @@ module Sequel
126
133
  self.class.get_hooks(key).each {|h| instance_eval(&h)}
127
134
  end
128
135
 
136
+ def self.before_save(&block)
137
+ get_hooks(:before_save).unshift(block)
138
+ end
139
+
129
140
  def self.before_create(&block)
130
141
  get_hooks(:before_create).unshift(block)
131
142
  end
@@ -134,17 +145,25 @@ module Sequel
134
145
  get_hooks(:before_destroy).unshift(block)
135
146
  end
136
147
 
137
- def self.after_destroy(&block)
138
- get_hooks(:after_destroy).unshift(block)
148
+ def self.after_save(&block)
149
+ get_hooks(:after_save) << block
139
150
  end
140
151
 
141
152
  def self.after_create(&block)
142
153
  get_hooks(:after_create) << block
143
154
  end
144
155
 
156
+ def self.after_destroy(&block)
157
+ get_hooks(:after_destroy).unshift(block)
158
+ end
159
+
145
160
  def self.find(cond)
146
161
  dataset[cond.is_a?(Hash) ? cond : {primary_key => cond}]
147
162
  end
163
+
164
+ def self.find_or_create(cond)
165
+ find(cond) || create(cond)
166
+ end
148
167
 
149
168
  class << self; alias_method :[], :find; end
150
169
 
@@ -177,8 +196,8 @@ module Sequel
177
196
 
178
197
  def self.each(&block); dataset.each(&block); end
179
198
  def self.all; dataset.all; end
180
- def self.filter(*arg); dataset.filter(*arg); end
181
- def self.exclude(*arg); dataset.exclude(*arg); end
199
+ def self.filter(*arg, &block); dataset.filter(*arg, &block); end
200
+ def self.exclude(*arg, &block); dataset.exclude(*arg, &block); end
182
201
  def self.order(*arg); dataset.order(*arg); end
183
202
  def self.first(*arg); dataset.first(*arg); end
184
203
  def self.count; dataset.count; end
@@ -252,14 +271,14 @@ module Sequel
252
271
  def save
253
272
  run_hooks(:before_save)
254
273
  if @pkey
255
- # run_hooks(:before_update)
274
+ run_hooks(:before_update)
256
275
  model.dataset.filter(primary_key => @pkey).update(@values)
257
- # run_hooks(:after_update)
276
+ run_hooks(:after_update)
258
277
  else
259
- # run_hooks(:before_create)
278
+ run_hooks(:before_create)
260
279
  @pkey = model.dataset.insert(@values)
261
280
  refresh
262
- # run_hooks(:after_create)
281
+ run_hooks(:after_create)
263
282
  end
264
283
  run_hooks(:after_save)
265
284
  end
data/lib/sequel/mysql.rb CHANGED
@@ -2,7 +2,7 @@ if !Object.const_defined?('Sequel')
2
2
  require File.join(File.dirname(__FILE__), '../sequel')
3
3
  end
4
4
 
5
- require 'mysql'
5
+ #require 'mysql'
6
6
 
7
7
  module Sequel
8
8
  module MySQL
@@ -101,9 +101,9 @@ module Sequel
101
101
  result = @db.execute(sql)
102
102
  begin
103
103
  if use_model_class && @model_class
104
- @model_class.new(result.fetch_hash)
104
+ row = @model_class.new(result.fetch_hash)
105
105
  else
106
- result.fetch_hash
106
+ row = result.fetch_hash
107
107
  end
108
108
  ensure
109
109
  result.free
@@ -24,16 +24,12 @@ class PGconn
24
24
 
25
25
  def execute(sql)
26
26
  begin
27
- # ServerSide.info(sql)
28
27
  async_exec(sql)
29
28
  rescue PGError => e
30
29
  unless connected?
31
- # ServerSide.warn('Reconnecting to Postgres server')
32
30
  reset
33
31
  async_exec(sql)
34
32
  else
35
- p sql
36
- p e
37
33
  raise e
38
34
  end
39
35
  end
@@ -49,16 +45,13 @@ class PGconn
49
45
  if @transaction_in_progress
50
46
  return yield
51
47
  end
52
- # ServerSide.info('BEGIN')
53
48
  async_exec(SQL_BEGIN)
54
49
  begin
55
50
  @transaction_in_progress = true
56
51
  result = yield
57
- # ServerSide.info('COMMIT')
58
52
  async_exec(SQL_COMMIT)
59
53
  result
60
54
  rescue => e
61
- # ServerSide.info('ROLLBACK')
62
55
  async_exec(SQL_ROLLBACK)
63
56
  raise e
64
57
  ensure
@@ -126,15 +119,8 @@ class String
126
119
  end
127
120
  end
128
121
 
129
- TIME_REGEXP = /(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})/
130
-
131
122
  def postgres_to_time
132
123
  Time.parse(self)
133
- # if self =~ TIME_REGEXP
134
- # Time.local($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i)
135
- # else
136
- # nil
137
- # end
138
124
  end
139
125
  end
140
126
 
@@ -161,8 +147,8 @@ module Sequel
161
147
  @opts[:host] || 'localhost',
162
148
  @opts[:port] || 5432,
163
149
  '', '',
164
- @opts[:database] || 'reality_development',
165
- @opts[:user] || 'postgres',
150
+ @opts[:database],
151
+ @opts[:user],
166
152
  @opts[:password]
167
153
  )
168
154
  end
@@ -368,7 +354,6 @@ module Sequel
368
354
  result = @db.execute(sql)
369
355
  begin
370
356
  row = nil
371
- # each_row(result, use_model_class) {|r| row = r}
372
357
  conv = row_converter(result, use_model_class)
373
358
  result.each {|r| row = conv.call(r)}
374
359
  ensure
@@ -393,60 +378,42 @@ module Sequel
393
378
  end
394
379
  end
395
380
 
396
- def each_row(result, use_model_class)
397
- fields = result.fields.map {|s| s.to_sym}
398
- types = (0..(result.num_fields - 1)).map {|idx| PG_TYPES[result.type(idx)]}
399
- m_klass = use_model_class && @model_class
400
- result.each do |row|
401
- hashed_row = {}
402
- row.each_index do |cel_index|
403
- column = row[cel_index]
404
- if column && types[cel_index]
405
- column = column.send(types[cel_index])
406
- end
407
- hashed_row[fields[cel_index]] = column
408
- end
409
- yield m_klass ? m_klass.new(hashed_row) : hashed_row
410
- end
411
- end
412
-
413
- COMMA = ','.freeze
414
-
415
381
  @@converters_mutex = Mutex.new
416
382
  @@converters = {}
417
383
 
418
384
  def row_converter(result, use_model_class)
419
- fields = result.fields.map {|s| s.to_sym}
420
- types = (0..(result.num_fields - 1)).map {|idx| result.type(idx)}
421
- klass = use_model_class ? @model_class : nil
385
+ fields = []; translators = []
386
+ result.fields.each_with_index do |f, idx|
387
+ fields << f.to_sym
388
+ translators << PG_TYPES[result.type(idx)]
389
+ end
390
+ klass = use_model_class && @model_class
422
391
 
423
392
  # create result signature and memoize the converter
424
- sig = fields.join(COMMA) + types.join(COMMA) + klass.to_s
393
+ sig = [fields, translators, klass].hash
425
394
  @@converters_mutex.synchronize do
426
- @@converters[sig] ||= compile_converter(fields, types, klass)
395
+ @@converters[sig] ||= compile_converter(fields, translators, klass)
427
396
  end
428
397
  end
429
398
 
430
- CONVERT = "lambda {|r| {%s}}".freeze
431
- CONVERT_MODEL_CLASS = "lambda {|r| %2$s.new(%1$s)}".freeze
432
-
433
- CONVERT_FIELD = '%s => r[%d]'.freeze
434
- CONVERT_FIELD_TRANSLATE = '%s => ((t = r[%d]) ? t.%s : nil)'.freeze
435
-
436
- def compile_converter(fields, types, klass)
399
+ def compile_converter(fields, translators, klass)
437
400
  used_fields = []
438
401
  kvs = []
439
402
  fields.each_with_index do |field, idx|
440
403
  next if used_fields.include?(field)
441
404
  used_fields << field
442
405
 
443
- translate_fn = PG_TYPES[types[idx]]
444
- kvs << (translate_fn ? CONVERT_FIELD_TRANSLATE : CONVERT_FIELD) %
445
- [field.inspect, idx, translate_fn]
406
+ if translator = translators[idx]
407
+ kvs << ":#{field} => ((t = r[#{idx}]) ? t.#{translator} : nil)"
408
+ else
409
+ kvs << ":#{field} => r[#{idx}]"
410
+ end
411
+ end
412
+ if klass
413
+ eval("lambda {|r| #{klass}.new(#{kvs.join(COMMA_SEPARATOR)})}")
414
+ else
415
+ eval("lambda {|r| {#{kvs.join(COMMA_SEPARATOR)}}}")
446
416
  end
447
- s = (klass ? CONVERT_MODEL_CLASS : CONVERT) %
448
- [kvs.join(COMMA), klass]
449
- eval(s)
450
417
  end
451
418
  end
452
419
  end
data/lib/sequel/sqlite.rb CHANGED
@@ -40,10 +40,12 @@ module Sequel
40
40
  end
41
41
 
42
42
  def single_value(sql)
43
+ @logger.info(sql) if @logger
43
44
  @pool.hold {|conn| conn.get_first_value(sql)}
44
45
  end
45
46
 
46
47
  def result_set(sql, model_class, &block)
48
+ @logger.info(sql) if @logger
47
49
  @pool.hold do |conn|
48
50
  conn.query(sql) do |result|
49
51
  columns = result.columns
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: sequel
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.1.1
7
- date: 2007-04-28 00:00:00 +03:00
6
+ version: 0.1.2
7
+ date: 2007-05-19 00:00:00 +03:00
8
8
  summary: Concise ORM for Ruby.
9
9
  require_paths:
10
10
  - lib
@@ -33,7 +33,6 @@ files:
33
33
  - README
34
34
  - Rakefile
35
35
  - bin/sequel
36
- - doc/rdoc
37
36
  - lib/sequel
38
37
  - lib/sequel.rb
39
38
  - lib/sequel/connection_pool.rb