sequel 0.2.1.1 → 0.3.0

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