sequel 0.2.1.1 → 0.3.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.
@@ -1,9 +1,12 @@
1
+ # TODO: refactoring...
1
2
  module Sequel
2
3
  class Model
4
+
3
5
  ONE_TO_ONE_PROC = "proc {i = @values[:%s]; %s[i] if i}".freeze
4
6
  ID_POSTFIX = "_id".freeze
5
7
  FROM_DATASET = "db[%s]".freeze
6
8
 
9
+ # Comprehensive description goes here!
7
10
  def self.one_to_one(name, opts)
8
11
  klass = opts[:class] ? opts[:class] : (FROM_DATASET % name.inspect)
9
12
  key = opts[:key] || (name.to_s + ID_POSTFIX)
@@ -12,6 +15,8 @@ module Sequel
12
15
 
13
16
  ONE_TO_MANY_PROC = "proc {%s.filter(:%s => pkey)}".freeze
14
17
  ONE_TO_MANY_ORDER_PROC = "proc {%s.filter(:%s => pkey).order(%s)}".freeze
18
+
19
+ # Comprehensive description goes here!
15
20
  def self.one_to_many(name, opts)
16
21
  klass = opts[:class] ? opts[:class] :
17
22
  (FROM_DATASET % (opts[:table] || name.inspect))
@@ -1,5 +1,9 @@
1
1
  module Sequel
2
2
  class Model
3
+ # Defines a table schema (see Schema::Generator for more information).
4
+ #
5
+ # This is only needed if you want to use the create_table or drop_table
6
+ # methods.
3
7
  def self.set_schema(name = nil, &block)
4
8
  name ? set_dataset(db[name]) : name = table_name
5
9
  @schema = Schema::Generator.new(db, name, &block)
@@ -8,32 +12,38 @@ module Sequel
8
12
  end
9
13
  end
10
14
 
15
+ # Returns table schema for direct descendant of Model.
11
16
  def self.schema
12
17
  @schema || ((superclass != Model) && (superclass.schema))
13
18
  end
14
19
 
20
+ # Returns name of table.
15
21
  def self.table_name
16
22
  dataset.opts[:from].first
17
23
  end
18
24
 
25
+ # Returns true if table exists, false otherwise.
19
26
  def self.table_exists?
20
27
  db.table_exists?(table_name)
21
28
  end
22
29
 
30
+ # Creates table.
23
31
  def self.create_table
24
32
  db.create_table_sql_list(*schema.create_info).each {|s| db << s}
25
33
  end
26
34
 
35
+ # Drops table.
27
36
  def self.drop_table
28
37
  db.execute db.drop_table_sql(table_name)
29
38
  end
30
39
 
40
+ # Like create_table but invokes drop_table when table_exists? is true.
31
41
  def self.create_table!
32
42
  drop_table if table_exists?
33
43
  create_table
34
-
35
44
  end
36
45
 
46
+ # Deprecated, use create_table! instead.
37
47
  def self.recreate_table
38
48
  warn "Model.recreate_table is deprecated. Please use Model.create_table! instead."
39
49
  create_table!
@@ -2,27 +2,40 @@ if !Object.const_defined?('Sequel')
2
2
  require File.join(File.dirname(__FILE__), '../sequel')
3
3
  end
4
4
 
5
+ require "bigdecimal"
6
+ require "bigdecimal/util"
5
7
  require 'mysql'
6
8
 
7
9
  # Monkey patch Mysql::Result to yield hashes with symbol keys
8
10
  class Mysql::Result
9
11
  MYSQL_TYPES = {
10
- 0 => :to_i,
11
- 1 => :to_i,
12
- 2 => :to_i,
13
- 3 => :to_i,
14
- 4 => :to_f,
15
- 5 => :to_f,
16
- 7 => :to_time,
17
- 8 => :to_i,
18
- 9 => :to_i,
19
- 10 => :to_time,
20
- 11 => :to_time,
21
- 12 => :to_time,
22
- 13 => :to_i,
23
- 14 => :to_time,
24
- 247 => :to_i,
25
- 248 => :to_i
12
+ 0 => :to_d, # MYSQL_TYPE_DECIMAL
13
+ 1 => :to_i, # MYSQL_TYPE_TINY
14
+ 2 => :to_i, # MYSQL_TYPE_SHORT
15
+ 3 => :to_i, # MYSQL_TYPE_LONG
16
+ 4 => :to_f, # MYSQL_TYPE_FLOAT
17
+ 5 => :to_f, # MYSQL_TYPE_DOUBLE
18
+ # 6 => ??, # MYSQL_TYPE_NULL
19
+ 7 => :to_time, # MYSQL_TYPE_TIMESTAMP
20
+ 8 => :to_i, # MYSQL_TYPE_LONGLONG
21
+ 9 => :to_i, # MYSQL_TYPE_INT24
22
+ 10 => :to_time, # MYSQL_TYPE_DATE
23
+ 11 => :to_time, # MYSQL_TYPE_TIME
24
+ 12 => :to_time, # MYSQL_TYPE_DATETIME
25
+ 13 => :to_i, # MYSQL_TYPE_YEAR
26
+ 14 => :to_time, # MYSQL_TYPE_NEWDATE
27
+ # 15 => :to_s # MYSQL_TYPE_VARCHAR
28
+ # 16 => :to_s, # MYSQL_TYPE_BIT
29
+ 246 => :to_d, # MYSQL_TYPE_NEWDECIMAL
30
+ 247 => :to_i, # MYSQL_TYPE_ENUM
31
+ 248 => :to_i # MYSQL_TYPE_SET
32
+ # 249 => :to_s, # MYSQL_TYPE_TINY_BLOB
33
+ # 250 => :to_s, # MYSQL_TYPE_MEDIUM_BLOB
34
+ # 251 => :to_s, # MYSQL_TYPE_LONG_BLOB
35
+ # 252 => :to_s, # MYSQL_TYPE_BLOB
36
+ # 253 => :to_s, # MYSQL_TYPE_VAR_STRING
37
+ # 254 => :to_s, # MYSQL_TYPE_STRING
38
+ # 255 => :to_s # MYSQL_TYPE_GEOMETRY
26
39
  }
27
40
 
28
41
  def convert_type(v, type)
@@ -40,7 +53,20 @@ class Mysql::Result
40
53
  @columns
41
54
  end
42
55
 
43
- def each_hash(with_table=nil)
56
+ def each_array(with_table = nil)
57
+ c = columns
58
+ while row = fetch_row
59
+ c.each_with_index do |f, i|
60
+ if (t = MYSQL_TYPES[@column_types[i]]) && (v = row[i])
61
+ row[i] = v.send(t)
62
+ end
63
+ end
64
+ row.fields = c
65
+ yield row
66
+ end
67
+ end
68
+
69
+ def each_hash(with_table = nil)
44
70
  c = columns
45
71
  while row = fetch_row
46
72
  h = {}
@@ -78,6 +104,10 @@ module Sequel
78
104
  conn
79
105
  end
80
106
 
107
+ def disconnect
108
+ @pool.disconnect {|c| c.close}
109
+ end
110
+
81
111
  def tables
82
112
  @pool.hold do |conn|
83
113
  conn.list_tables.map {|t| t.to_sym}
@@ -166,6 +196,7 @@ module Sequel
166
196
 
167
197
  def literal(v)
168
198
  case v
199
+ when LiteralString: quoted_field_name(v)
169
200
  when true: TRUE
170
201
  when false: FALSE
171
202
  else
@@ -225,6 +256,19 @@ module Sequel
225
256
  end
226
257
  self
227
258
  end
259
+
260
+ def array_tuples_fetch_rows(sql, &block)
261
+ @db.synchronize do
262
+ r = @db.execute_select(sql)
263
+ begin
264
+ @columns = r.columns
265
+ r.each_array(&block)
266
+ ensure
267
+ r.free
268
+ end
269
+ end
270
+ self
271
+ end
228
272
  end
229
273
  end
230
274
  end
@@ -14,6 +14,10 @@ module Sequel
14
14
  conn.autocommit = true
15
15
  conn
16
16
  end
17
+
18
+ def disconnect
19
+ @pool.disconnect {|c| c.disconnect}
20
+ end
17
21
 
18
22
  def dataset(opts = nil)
19
23
  ODBC::Dataset.new(self, opts)
@@ -46,7 +50,7 @@ module Sequel
46
50
 
47
51
  def fetch_rows(sql, &block)
48
52
  @db.synchronize do
49
- s = @db.execute sql
53
+ s = @db.execute select_sql(sql)
50
54
  begin
51
55
  @columns = s.columns(true).map {|c| c.name.to_sym}
52
56
  rows = s.fetch_all
@@ -86,6 +90,43 @@ module Sequel
86
90
  end
87
91
  end
88
92
 
93
+ def array_tuples_fetch_rows(sql, &block)
94
+ @db.synchronize do
95
+ s = @db.execute sql
96
+ begin
97
+ @columns = s.columns(true).map {|c| c.name.to_sym}
98
+ rows = s.fetch_all
99
+ rows.each {|r| yield array_tuples_make_row(r)}
100
+ ensure
101
+ s.drop unless s.nil? rescue nil
102
+ end
103
+ end
104
+ self
105
+ end
106
+
107
+ def array_tuples_make_row(row)
108
+ row.fields = @columns
109
+ row.each_with_index do |v, idx|
110
+ # When fetching a result set, the Ruby ODBC driver converts all ODBC
111
+ # SQL types to an equivalent Ruby type; with the exception of
112
+ # SQL_TYPE_DATE, SQL_TYPE_TIME and SQL_TYPE_TIMESTAMP.
113
+ #
114
+ # The conversions below are consistent with the mappings in
115
+ # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
116
+ case v
117
+ when ::ODBC::TimeStamp
118
+ row[idx] = DateTime.new(v.year, v.month, v.day, v.hour, v.minute, v.second)
119
+ when ::ODBC::Time
120
+ now = DateTime.now
121
+ row[idx] = Time.gm(now.year, now.month, now.day, v.hour, v.minute, v.second)
122
+ when ::ODBC::Date
123
+ row[idx] = Date.new(v.year, v.month, v.day)
124
+ end
125
+ end
126
+ row
127
+ end
128
+
129
+
89
130
  def insert(*values)
90
131
  @db.do insert_sql(*values)
91
132
  end
@@ -154,6 +154,10 @@ module Sequel
154
154
  end
155
155
  conn
156
156
  end
157
+
158
+ def disconnect
159
+ @pool.disconnect {|c| c.close}
160
+ end
157
161
 
158
162
  def dataset(opts = nil)
159
163
  Postgres::Dataset.new(self, opts)
@@ -280,6 +284,7 @@ module Sequel
280
284
  class Dataset < Sequel::Dataset
281
285
  def literal(v)
282
286
  case v
287
+ when LiteralString: v
283
288
  when String, Fixnum, Float, TrueClass, FalseClass: PGconn.quote(v)
284
289
  else
285
290
  super
@@ -436,6 +441,46 @@ module Sequel
436
441
  end
437
442
  eval("lambda {|r| {#{kvs.join(COMMA_SEPARATOR)}}}")
438
443
  end
444
+
445
+ def array_tuples_fetch_rows(sql, &block)
446
+ @db.synchronize do
447
+ result = @db.execute(sql)
448
+ begin
449
+ conv = array_tuples_row_converter(result)
450
+ result.each {|r| yield conv[r]}
451
+ ensure
452
+ result.clear
453
+ end
454
+ end
455
+ end
456
+
457
+ @@array_tuples_converters_mutex = Mutex.new
458
+ @@array_tuples_converters = {}
459
+
460
+ def array_tuples_row_converter(result)
461
+ fields = []; translators = []
462
+ result.fields.each_with_index do |f, idx|
463
+ fields << f.to_sym
464
+ translators << PG_TYPES[result.type(idx)]
465
+ end
466
+ @columns = fields
467
+
468
+ # create result signature and memoize the converter
469
+ sig = [fields, translators].hash
470
+ @@array_tuples_converters_mutex.synchronize do
471
+ @@array_tuples_converters[sig] ||= array_tuples_compile_converter(fields, translators)
472
+ end
473
+ end
474
+
475
+ def array_tuples_compile_converter(fields, translators)
476
+ tr = []
477
+ fields.each_with_index do |field, idx|
478
+ if t = translators[idx]
479
+ tr << "if (v = r[#{idx}]); r[#{idx}] = v.#{t}; end"
480
+ end
481
+ end
482
+ eval("lambda {|r| r.fields = fields; #{tr.join(';')}; r}")
483
+ end
439
484
  end
440
485
  end
441
486
  end
@@ -1,17 +1,20 @@
1
- # Print nice-looking plain-text tables
2
- # +--+-------+
3
- # |id|name |
4
- # |--+-------|
5
- # |1 |fasdfas|
6
- # |2 |test |
7
- # +--+-------+
8
-
9
1
  module Sequel
2
+ # Prints nice-looking plain-text tables
3
+ # +--+-------+
4
+ # |id|name |
5
+ # |--+-------|
6
+ # |1 |fasdfas|
7
+ # |2 |test |
8
+ # +--+-------+
10
9
  module PrettyTable
11
- def self.hash_columns(records)
10
+ def self.records_columns(records)
12
11
  columns = []
13
12
  records.each do |r|
14
- r.keys.each {|k| columns << k unless columns.include?(k)}
13
+ if Array === r && (f = r.fields)
14
+ return r.fields
15
+ elsif Hash === r
16
+ r.keys.each {|k| columns << k unless columns.include?(k)}
17
+ end
15
18
  end
16
19
  columns
17
20
  end
@@ -53,7 +56,7 @@ module Sequel
53
56
  end
54
57
 
55
58
  def self.print(records, columns = nil) # records is an array of hashes
56
- columns ||= hash_columns(records)
59
+ columns ||= records_columns(records)
57
60
  sizes = column_sizes(records, columns)
58
61
 
59
62
  puts separator_line(columns, sizes)
@@ -18,12 +18,18 @@ module Sequel
18
18
  @primary_key ? @primary_key[:name] : nil
19
19
  end
20
20
 
21
- def primary_key(name, type = nil, opts = nil)
21
+ def primary_key(name, *args)
22
22
  @primary_key = @db.serial_primary_key_options.merge({
23
23
  :name => name
24
24
  })
25
- @primary_key.merge!({:type => type}) if type
26
- @primary_key.merge!(opts) if opts
25
+
26
+ if opts = args.pop
27
+ opts = {:type => opts} unless opts.is_a?(Hash)
28
+ if type = args.pop
29
+ opts.merge!(:type => type)
30
+ end
31
+ @primary_key.merge!(opts)
32
+ end
27
33
  @primary_key
28
34
  end
29
35
 
@@ -21,6 +21,10 @@ module Sequel
21
21
  db.type_translation = true
22
22
  db
23
23
  end
24
+
25
+ def disconnect
26
+ @pool.disconnect {|c| c.close}
27
+ end
24
28
 
25
29
  def dataset(opts = nil)
26
30
  SQLite::Dataset.new(self, opts)
@@ -93,18 +97,39 @@ module Sequel
93
97
  pragma_set(:temp_store, value)
94
98
  end
95
99
 
100
+ def transaction(&block)
101
+ @pool.hold do |conn|
102
+ if conn.transaction_active?
103
+ return yield(conn)
104
+ end
105
+ begin
106
+ result = nil
107
+ conn.transaction {result = yield(conn)}
108
+ result
109
+ rescue => e
110
+ raise e unless SequelRollbackError === e
111
+ end
112
+ end
113
+ end
96
114
  end
97
115
 
98
116
  class Dataset < Sequel::Dataset
99
117
  def literal(v)
100
118
  case v
101
119
  when Time: literal(v.iso8601)
102
- when Date: literal(v.to_s)
103
120
  else
104
121
  super
105
122
  end
106
123
  end
107
124
 
125
+ def insert_sql(*values)
126
+ if (values.size == 1) && values.first.is_a?(Sequel::Dataset)
127
+ "INSERT INTO #{@opts[:from]} #{values.first.sql};"
128
+ else
129
+ super(*values)
130
+ end
131
+ end
132
+
108
133
  def fetch_rows(sql, &block)
109
134
  @db.execute_select(sql) do |result|
110
135
  @columns = result.columns.map {|c| c.to_sym}
@@ -116,6 +141,13 @@ module Sequel
116
141
  end
117
142
  end
118
143
  end
144
+
145
+ def array_tuples_fetch_rows(sql, &block)
146
+ @db.execute_select(sql) do |result|
147
+ @columns = result.columns.map {|c| c.to_sym}
148
+ result.each(&block)
149
+ end
150
+ end
119
151
 
120
152
  def insert(*values)
121
153
  @db.execute_insert insert_sql(*values)
@@ -11,6 +11,19 @@ MYSQL_DB.create_table :items do
11
11
  index :value
12
12
  end
13
13
 
14
+ context "A MySQL database" do
15
+ setup do
16
+ @db = MYSQL_DB
17
+ end
18
+
19
+ specify "should provide disconnect functionality" do
20
+ @db.tables
21
+ @db.pool.size.should == 1
22
+ @db.disconnect
23
+ @db.pool.size.should == 0
24
+ end
25
+ end
26
+
14
27
  context "A MySQL dataset" do
15
28
  setup do
16
29
  @d = MYSQL_DB[:items]
@@ -25,24 +38,19 @@ context "A MySQL dataset" do
25
38
  @d.count.should == 3
26
39
  end
27
40
 
28
- # specify "should return the last inserted id when inserting records" do
29
- # id = @d << {:name => 'abc', :value => 1.23}
30
- # id.should == @d.first[:id]
31
- # end
32
- #
33
-
34
- specify "should return all records" do
41
+ specify "should return the correct records" do
42
+ @d.to_a.should == []
35
43
  @d << {:name => 'abc', :value => 123}
36
44
  @d << {:name => 'abc', :value => 456}
37
45
  @d << {:name => 'def', :value => 789}
38
-
39
- @d.order(:value).all.should == [
46
+
47
+ @d.order(:value).to_a.should == [
40
48
  {:name => 'abc', :value => 123},
41
49
  {:name => 'abc', :value => 456},
42
50
  {:name => 'def', :value => 789}
43
51
  ]
44
52
  end
45
-
53
+
46
54
  specify "should update records correctly" do
47
55
  @d << {:name => 'abc', :value => 123}
48
56
  @d << {:name => 'abc', :value => 456}
@@ -74,7 +82,7 @@ context "A MySQL dataset" do
74
82
  @d.select(:name).sql.should == \
75
83
  'SELECT `name` FROM items'
76
84
 
77
- @d.select('COUNT(*)').sql.should == \
85
+ @d.select('COUNT(*)'.lit).sql.should == \
78
86
  'SELECT COUNT(*) FROM items'
79
87
 
80
88
  @d.select(:value.MAX).sql.should == \
@@ -89,13 +97,13 @@ context "A MySQL dataset" do
89
97
  @d.order(:name.DESC).sql.should == \
90
98
  'SELECT * FROM items ORDER BY `name` DESC'
91
99
 
92
- @d.select('items.name AS item_name').sql.should == \
100
+ @d.select('items.name AS item_name'.to_sym).sql.should == \
93
101
  'SELECT items.`name` AS `item_name` FROM items'
94
102
 
95
- @d.select('`name`').sql.should == \
103
+ @d.select('`name`'.lit).sql.should == \
96
104
  'SELECT `name` FROM items'
97
105
 
98
- @d.select('max(items.`name`) as `max_name`').sql.should == \
106
+ @d.select('max(items.`name`) AS `max_name`'.lit).sql.should == \
99
107
  'SELECT max(items.`name`) AS `max_name` FROM items'
100
108
 
101
109
  @d.insert_sql(:value => 333).should == \
@@ -126,4 +134,50 @@ context "A MySQL dataset" do
126
134
  @d.filter(:name => /bc/).count.should == 2
127
135
  @d.filter(:name => /^bc/).count.should == 1
128
136
  end
129
- end
137
+ end
138
+
139
+ context "A MySQL dataset in array tuples mode" do
140
+ setup do
141
+ @d = MYSQL_DB[:items]
142
+ @d.delete # remove all records
143
+ Sequel.use_array_tuples
144
+ end
145
+
146
+ teardown do
147
+ Sequel.use_hash_tuples
148
+ end
149
+
150
+ specify "should return the correct records" do
151
+ @d.to_a.should == []
152
+ @d << {:name => 'abc', :value => 123}
153
+ @d << {:name => 'abc', :value => 456}
154
+ @d << {:name => 'def', :value => 789}
155
+
156
+ @d.order(:value).select(:name, :value).to_a.should == [
157
+ ['abc', 123],
158
+ ['abc', 456],
159
+ ['def', 789]
160
+ ]
161
+ end
162
+
163
+ specify "should work correctly with transforms" do
164
+ @d.transform(:value => [proc {|v| v.to_s}, proc {|v| v.to_i}])
165
+
166
+ @d.to_a.should == []
167
+ @d << {:name => 'abc', :value => 123}
168
+ @d << {:name => 'abc', :value => 456}
169
+ @d << {:name => 'def', :value => 789}
170
+
171
+ @d.order(:value).select(:name, :value).to_a.should == [
172
+ ['abc', '123'],
173
+ ['abc', '456'],
174
+ ['def', '789']
175
+ ]
176
+
177
+ a = @d.order(:value).first
178
+ a.values.should == ['abc', '123']
179
+ a.keys.should == [:name, :value]
180
+ a[:name].should == 'abc'
181
+ a[:value].should == '123'
182
+ end
183
+ end