sequel 0.1.1 → 0.1.2
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.
- data/CHANGELOG +22 -0
- data/Rakefile +1 -1
- data/lib/sequel/connection_pool.rb +2 -5
- data/lib/sequel/database.rb +27 -1
- data/lib/sequel/dataset.rb +26 -2
- data/lib/sequel/error.rb +1 -19
- data/lib/sequel/model.rb +27 -8
- data/lib/sequel/mysql.rb +3 -3
- data/lib/sequel/postgres.rb +21 -54
- data/lib/sequel/sqlite.rb +2 -0
- metadata +2 -3
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.
|
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
|
-
|
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
|
data/lib/sequel/database.rb
CHANGED
@@ -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
|
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)
|
data/lib/sequel/dataset.rb
CHANGED
@@ -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
|
-
|
347
|
-
|
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.
|
138
|
-
get_hooks(:
|
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
|
-
|
274
|
+
run_hooks(:before_update)
|
256
275
|
model.dataset.filter(primary_key => @pkey).update(@values)
|
257
|
-
|
276
|
+
run_hooks(:after_update)
|
258
277
|
else
|
259
|
-
|
278
|
+
run_hooks(:before_create)
|
260
279
|
@pkey = model.dataset.insert(@values)
|
261
280
|
refresh
|
262
|
-
|
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
|
data/lib/sequel/postgres.rb
CHANGED
@@ -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]
|
165
|
-
@opts[:user]
|
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 =
|
420
|
-
|
421
|
-
|
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
|
393
|
+
sig = [fields, translators, klass].hash
|
425
394
|
@@converters_mutex.synchronize do
|
426
|
-
@@converters[sig] ||= compile_converter(fields,
|
395
|
+
@@converters[sig] ||= compile_converter(fields, translators, klass)
|
427
396
|
end
|
428
397
|
end
|
429
398
|
|
430
|
-
|
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
|
-
|
444
|
-
|
445
|
-
|
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.
|
7
|
-
date: 2007-
|
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
|