sequel 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,19 @@
1
+ === 0.3.3 (2007-11-04)
2
+
3
+ * Revised code to generate SQL statements without trailing semicolons.
4
+
5
+ * Added Sequel::Worker implementation of a simple worker thread for asynchronous execution.
6
+
7
+ * Added spec for Oracle adapter.
8
+
9
+ * Fixed Oracle adapter to format INSERT statements without semicolons (thanks Liming Lian).
10
+
11
+ * Renamed alias to Array#keys as Array#columns instead of Array#fields.
12
+
13
+ * Renamed FieldCompositionMethods as ColumnCompositionMethods.
14
+
15
+ * Implemented Sequel::NumericExtensions to provide stuff like 30.days.ago.
16
+
1
17
  === 0.3.2 (2007-11-01)
2
18
 
3
19
  * Added #to_column_name as alias to #to_field_name, #column_title as alias to #field_title.
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'fileutils'
6
6
  include FileUtils
7
7
 
8
8
  NAME = "sequel"
9
- VERS = "0.3.2"
9
+ VERS = "0.3.3"
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",
data/lib/sequel.rb CHANGED
@@ -2,7 +2,7 @@ require 'metaid'
2
2
 
3
3
  files = %w[
4
4
  core_ext array_keys error connection_pool pretty_table
5
- dataset migration model schema database
5
+ dataset migration model schema database worker
6
6
  ]
7
7
  dir = File.join(File.dirname(__FILE__), 'sequel')
8
8
  files.each {|f| require(File.join(dir, f))}
@@ -183,8 +183,8 @@ module ArrayKeys
183
183
  end
184
184
  end
185
185
 
186
- alias_method :fields, :keys
187
- alias_method :fields=, :keys=
186
+ alias_method :columns, :keys
187
+ alias_method :columns=, :keys=
188
188
  end
189
189
 
190
190
  # The DatasetExtensions module provides extensions that modify
@@ -43,7 +43,7 @@ module Sequel
43
43
  # Assigns a connection to the current thread, yielding the connection
44
44
  # to the supplied block.
45
45
  #
46
- # pool.hold {|conn| conn.execute('DROP TABLE posts;')}
46
+ # pool.hold {|conn| conn.execute('DROP TABLE posts')}
47
47
  #
48
48
  # Pool#hold is re-entrant, meaning it can be called recursively in
49
49
  # the same thread without blocking.
@@ -17,6 +17,14 @@ class Array
17
17
  end
18
18
  end
19
19
 
20
+ # Range extensions
21
+ class Range
22
+ # Returns the interval between the beginning and end of the range.
23
+ def interval
24
+ last - first
25
+ end
26
+ end
27
+
20
28
  module Sequel
21
29
  # LiteralString is used to represent literal SQL expressions. An
22
30
  # LiteralString is copied verbatim into an SQL statement. Instances of
@@ -68,7 +76,7 @@ end
68
76
 
69
77
  # Methods to format column names and associated constructs. This module is
70
78
  # included in String and Symbol.
71
- module FieldCompositionMethods
79
+ module ColumnCompositionMethods
72
80
  # Constructs a DESC clause for use in an ORDER BY clause.
73
81
  def DESC
74
82
  "#{to_column_name} DESC".lit
@@ -84,14 +92,14 @@ module FieldCompositionMethods
84
92
  "#{to_s}.*".lit
85
93
  end
86
94
 
87
- FIELD_TITLE_RE1 = /^(.*)\sAS\s(.+)$/i.freeze
88
- FIELD_TITLE_RE2 = /^([^\.]+)\.([^\.]+)$/.freeze
95
+ COLUMN_TITLE_RE1 = /^(.*)\sAS\s(.+)$/i.freeze
96
+ COLUMN_TITLE_RE2 = /^([^\.]+)\.([^\.]+)$/.freeze
89
97
 
90
98
  # Returns the column name. If the column name is aliased, the alias is
91
99
  # returned.
92
100
  def field_title
93
101
  case s = to_column_name
94
- when FIELD_TITLE_RE1, FIELD_TITLE_RE2: $2
102
+ when COLUMN_TITLE_RE1, COLUMN_TITLE_RE2: $2
95
103
  else
96
104
  s
97
105
  end
@@ -101,17 +109,17 @@ end
101
109
 
102
110
  # String extensions
103
111
  class String
104
- include FieldCompositionMethods
112
+ include ColumnCompositionMethods
105
113
  end
106
114
 
107
115
  # Symbol extensions
108
116
  class Symbol
109
- include FieldCompositionMethods
117
+ include ColumnCompositionMethods
110
118
 
111
119
 
112
- FIELD_REF_RE1 = /^(\w+)__(\w+)___(\w+)/.freeze
113
- FIELD_REF_RE2 = /^(\w+)___(\w+)$/.freeze
114
- FIELD_REF_RE3 = /^(\w+)__(\w+)$/.freeze
120
+ COLUMN_REF_RE1 = /^(\w+)__(\w+)___(\w+)/.freeze
121
+ COLUMN_REF_RE2 = /^(\w+)___(\w+)$/.freeze
122
+ COLUMN_REF_RE3 = /^(\w+)__(\w+)$/.freeze
115
123
 
116
124
  # Converts a symbol into a column name. This method supports underscore
117
125
  # notation in order to express qualified (two underscores) and aliased
@@ -125,9 +133,9 @@ class Symbol
125
133
  def to_field_name
126
134
  s = to_s
127
135
  case s
128
- when FIELD_REF_RE1: "#{$1}.#{$2} AS #{$3}"
129
- when FIELD_REF_RE2: "#{$1} AS #{$2}"
130
- when FIELD_REF_RE3: "#{$1}.#{$2}"
136
+ when COLUMN_REF_RE1: "#{$1}.#{$2} AS #{$3}"
137
+ when COLUMN_REF_RE2: "#{$1} AS #{$2}"
138
+ when COLUMN_REF_RE3: "#{$1}.#{$2}"
131
139
  else
132
140
  s
133
141
  end
@@ -147,4 +155,37 @@ class Symbol
147
155
  end
148
156
  end
149
157
 
158
+ module Sequel
159
+ # Facilitates time calculations by providing methods to convert from larger
160
+ # time units to seconds, and to convert relative time intervals to absolute
161
+ # ones. This module duplicates some of the functionality provided by Rails'
162
+ # ActiveSupport::CoreExtensions::Numeric::Time module.
163
+ module NumericExtensions
164
+ MINUTE = 60
165
+ HOUR = 3600
166
+ DAY = 86400
167
+ WEEK = DAY * 7
168
+
169
+ # Converts self from minutes to seconds
170
+ def minutes; self * MINUTE; end; alias_method :minute, :minutes
171
+ # Converts self from hours to seconds
172
+ def hours; self * HOUR; end; alias_method :hour, :hours
173
+ # Converts self from days to seconds
174
+ def days; self * DAY; end; alias_method :day, :days
175
+ # Converts self from weeks to seconds
176
+ def weeks; self * WEEK; end; alias_method :week, :weeks
177
+
178
+ # Returns the time at now - self.
179
+ def ago(t = Time.now); t - self; end
180
+ alias_method :before, :ago
150
181
 
182
+ # Returns the time at now + self.
183
+ def from_now(t = Time.now); t + self; end
184
+ alias_method :since, :from_now
185
+
186
+ # Extends the Numeric class with numeric extensions.
187
+ def self.enable
188
+ Numeric.send(:include, self)
189
+ end
190
+ end
191
+ end
@@ -180,7 +180,7 @@ module Sequel
180
180
 
181
181
  # Drops one or more tables corresponding to the given table names.
182
182
  def drop_table(*names)
183
- execute(names.map {|n| drop_table_sql(n)}.join)
183
+ names.each {|n| execute(drop_table_sql(n))}
184
184
  end
185
185
 
186
186
  # Returns true if the given table exists.
@@ -438,7 +438,7 @@ module Sequel
438
438
  # 'INSERT INTO items (a, b) VALUES (1, 2)'
439
439
  def insert_sql(*values)
440
440
  if values.empty?
441
- "INSERT INTO #{@opts[:from]} DEFAULT VALUES;"
441
+ "INSERT INTO #{@opts[:from]} DEFAULT VALUES"
442
442
  else
443
443
  values = values[0] if values.size == 1
444
444
  case values
@@ -446,27 +446,27 @@ module Sequel
446
446
  insert_sql(values.values)
447
447
  when Array
448
448
  if values.empty?
449
- "INSERT INTO #{@opts[:from]} DEFAULT VALUES;"
450
- elsif values.fields
451
- fl = values.fields
449
+ "INSERT INTO #{@opts[:from]} DEFAULT VALUES"
450
+ elsif values.keys
451
+ fl = values.keys
452
452
  vl = transform_save(values.values).map {|v| literal(v)}
453
- "INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)});"
453
+ "INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
454
454
  else
455
- "INSERT INTO #{@opts[:from]} VALUES (#{literal(values)});"
455
+ "INSERT INTO #{@opts[:from]} VALUES (#{literal(values)})"
456
456
  end
457
457
  when Hash
458
458
  values = transform_save(values) if @transform
459
459
  if values.empty?
460
- "INSERT INTO #{@opts[:from]} DEFAULT VALUES;"
460
+ "INSERT INTO #{@opts[:from]} DEFAULT VALUES"
461
461
  else
462
462
  fl, vl = [], []
463
463
  values.each {|k, v| fl << column_name(k); vl << literal(v)}
464
- "INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)});"
464
+ "INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
465
465
  end
466
466
  when Dataset
467
- "INSERT INTO #{@opts[:from]} #{literal(values)};"
467
+ "INSERT INTO #{@opts[:from]} #{literal(values)}"
468
468
  else
469
- "INSERT INTO #{@opts[:from]} VALUES (#{literal(values)});"
469
+ "INSERT INTO #{@opts[:from]} VALUES (#{literal(values)})"
470
470
  end
471
471
  end
472
472
  end
@@ -484,7 +484,7 @@ module Sequel
484
484
  raise SequelError, "Can't update a joined dataset"
485
485
  end
486
486
 
487
- if values.is_a?(Array) && values.fields
487
+ if values.is_a?(Array) && values.keys
488
488
  values = values.to_hash
489
489
  end
490
490
  values = transform_save(values) if @transform
data/lib/sequel/mysql.rb CHANGED
@@ -96,9 +96,9 @@ module Sequel
96
96
  @opts[:database], @opts[:port])
97
97
  conn.query_with_result = false
98
98
  if encoding = @opts[:encoding] || @opts[:charset]
99
- conn.query("set character_set_connection = '#{encoding}';")
100
- conn.query("set character_set_client = '#{encoding}';")
101
- conn.query("set character_set_results = '#{encoding}';")
99
+ conn.query("set character_set_connection = '#{encoding}'")
100
+ conn.query("set character_set_client = '#{encoding}'")
101
+ conn.query("set character_set_results = '#{encoding}'")
102
102
  end
103
103
  conn.reconnect = true
104
104
  conn
@@ -172,20 +172,20 @@ module Sequel
172
172
  end
173
173
 
174
174
  class Dataset < Sequel::Dataset
175
- UNQUOTABLE_FIELD_RE = /^(`(.+)`)|\*$/.freeze
175
+ UNQUOTABLE_COLUMN_RE = /^(`(.+)`)|\*$/.freeze
176
176
  def quote_column(f)
177
- (f.nil? || f.empty? || f =~ UNQUOTABLE_FIELD_RE) ? f : "`#{f}`"
177
+ (f.nil? || f.empty? || f =~ UNQUOTABLE_COLUMN_RE) ? f : "`#{f}`"
178
178
  end
179
179
 
180
- FIELD_EXPR_RE = /^([^\(]+\()?([^\.]+\.)?([^\s\)]+)?(\))?(\sAS\s(.+))?$/i.freeze
181
- FIELD_ORDER_RE = /^(.*) (DESC|ASC)$/i.freeze
180
+ COLUMN_EXPR_RE = /^([^\(]+\()?([^\.]+\.)?([^\s\)]+)?(\))?(\sAS\s(.+))?$/i.freeze
181
+ COLUMN_ORDER_RE = /^(.*) (DESC|ASC)$/i.freeze
182
182
  def quoted_column_name(name)
183
183
  case name
184
- when FIELD_EXPR_RE:
184
+ when COLUMN_EXPR_RE:
185
185
  $6 ? \
186
186
  "#{$1}#{$2}#{quote_column($3)}#{$4} AS #{quote_column($6)}" : \
187
187
  "#{$1}#{$2}#{quote_column($3)}#{$4}"
188
- when FIELD_ORDER_RE: "#{quote_column($1)} #{$2}"
188
+ when COLUMN_ORDER_RE: "#{quote_column($1)} #{$2}"
189
189
  else
190
190
  quote_column(name)
191
191
  end
@@ -284,7 +284,7 @@ module Sequel
284
284
  end
285
285
 
286
286
  def drop_table_sql(name)
287
- "DROP TABLE #{name} CASCADE;"
287
+ "DROP TABLE #{name} CASCADE"
288
288
  end
289
289
  end
290
290
 
@@ -350,7 +350,7 @@ module Sequel
350
350
  analysis.join("\r\n")
351
351
  end
352
352
 
353
- LOCK = 'LOCK TABLE %s IN %s MODE;'.freeze
353
+ LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
354
354
 
355
355
  ACCESS_SHARE = 'ACCESS SHARE'.freeze
356
356
  ROW_SHARE = 'ROW SHARE'.freeze
@@ -63,9 +63,9 @@ module Sequel
63
63
  columns = index[:columns].join(COMMA_SEPARATOR)
64
64
  index_name = index[:name] || default_index_name(table_name, index[:columns])
65
65
  if index[:unique]
66
- "CREATE UNIQUE INDEX #{index_name} ON #{table_name} (#{columns});"
66
+ "CREATE UNIQUE INDEX #{index_name} ON #{table_name} (#{columns})"
67
67
  else
68
- "CREATE INDEX #{index_name} ON #{table_name} (#{columns});"
68
+ "CREATE INDEX #{index_name} ON #{table_name} (#{columns})"
69
69
  end
70
70
  end
71
71
 
@@ -74,7 +74,7 @@ module Sequel
74
74
  end
75
75
 
76
76
  def create_table_sql_list(name, columns, indexes = nil)
77
- sql = ["CREATE TABLE #{name} (#{column_list_sql(columns)});"]
77
+ sql = ["CREATE TABLE #{name} (#{column_list_sql(columns)})"]
78
78
  if indexes && !indexes.empty?
79
79
  sql.concat(index_list_sql_list(name, indexes))
80
80
  end
@@ -82,7 +82,7 @@ module Sequel
82
82
  end
83
83
 
84
84
  def drop_table_sql(name)
85
- "DROP TABLE #{name};"
85
+ "DROP TABLE #{name}"
86
86
  end
87
87
  end
88
88
  end
data/lib/sequel/sqlite.rb CHANGED
@@ -61,11 +61,11 @@ module Sequel
61
61
  end
62
62
 
63
63
  def pragma_get(name)
64
- single_value("PRAGMA #{name};")
64
+ single_value("PRAGMA #{name}")
65
65
  end
66
66
 
67
67
  def pragma_set(name, value)
68
- execute("PRAGMA #{name} = #{value};")
68
+ execute("PRAGMA #{name} = #{value}")
69
69
  end
70
70
 
71
71
  AUTO_VACUUM = {'0' => :none, '1' => :full, '2' => :incremental}.freeze
@@ -149,7 +149,7 @@ module Sequel
149
149
  def array_tuples_fetch_rows(sql, &block)
150
150
  @db.execute_select(sql) do |result|
151
151
  @columns = result.columns.map {|c| c.to_sym}
152
- result.each(&block)
152
+ result.each {|r| r.keys = @columns; block[r]}
153
153
  end
154
154
  end
155
155
 
@@ -0,0 +1,44 @@
1
+ require 'thread'
2
+
3
+ module Sequel
4
+ class Worker < Thread
5
+ class WorkerStopError < RuntimeError; end
6
+
7
+ attr_reader :queue
8
+
9
+ def initialize(db = nil)
10
+ @queue = Queue.new
11
+ t = self
12
+ if db
13
+ super {db.transaction {t.work}}
14
+ else
15
+ super {t.work}
16
+ end
17
+ end
18
+
19
+ def work
20
+ begin
21
+ loop {@cur = @queue.pop; @cur.call; @cur = nil}
22
+ rescue WorkerStopError # do nothing
23
+ end
24
+ end
25
+
26
+ def busy?
27
+ @cur || !@queue.empty?
28
+ end
29
+
30
+ def async(proc = nil, &block)
31
+ @queue << (proc || block)
32
+ end
33
+ alias_method :add, :async
34
+ alias_method :<<, :async
35
+
36
+ def join
37
+ while busy?
38
+ sleep 0.1
39
+ end
40
+ self.raise WorkerStopError
41
+ super
42
+ end
43
+ end
44
+ end
@@ -107,7 +107,7 @@ context "A MySQL dataset" do
107
107
  'SELECT max(items.`name`) AS `max_name` FROM items'
108
108
 
109
109
  @d.insert_sql(:value => 333).should == \
110
- 'INSERT INTO items (`value`) VALUES (333);'
110
+ 'INSERT INTO items (`value`) VALUES (333)'
111
111
  end
112
112
 
113
113
  specify "should support ORDER clause in UPDATE statements" do
@@ -0,0 +1,130 @@
1
+ require File.join(File.dirname(__FILE__), '../../lib/sequel/oracle')
2
+
3
+ ORACLE_DB = Sequel('oracle://hr:hr@loalhost/XE')
4
+ if ORACLE_DB.table_exists?(:test)
5
+ ORACLE_DB.drop_table :test
6
+ end
7
+ ORACLE_DB.create_table :test do
8
+ text :name
9
+ integer :value
10
+
11
+ index :value
12
+ end
13
+
14
+ context "A Oracle database" do
15
+ specify "should provide disconnect functionality" do
16
+ ORACLE_DB.execute("select user from dual")
17
+ ORACLE_DB.pool.size.should == 1
18
+ ORACLE_DB.disconnect
19
+ ORACLE_DB.pool.size.should == 0
20
+ end
21
+ end
22
+
23
+ context "A Oracle dataset" do
24
+ setup do
25
+ @d = ORACLE_DB[:test]
26
+ @d.delete # remove all records
27
+ end
28
+
29
+ specify "should return the correct record count" do
30
+ @d.count.should == 0
31
+ @d << {:name => 'abc', :value => 123}
32
+ @d << {:name => 'abc', :value => 456}
33
+ @d << {:name => 'def', :value => 789}
34
+ @d.count.should == 3
35
+ end
36
+
37
+ specify "should return the correct records" do
38
+ @d.to_a.should == []
39
+ @d << {:name => 'abc', :value => 123}
40
+ @d << {:name => 'abc', :value => 456}
41
+ @d << {:name => 'def', :value => 789}
42
+
43
+ @d.order(:value).to_a.should == [
44
+ {:name => 'abc', :value => 123},
45
+ {:name => 'abc', :value => 456},
46
+ {:name => 'def', :value => 789}
47
+ ]
48
+ end
49
+
50
+ specify "should update records correctly" do
51
+ @d << {:name => 'abc', :value => 123}
52
+ @d << {:name => 'abc', :value => 456}
53
+ @d << {:name => 'def', :value => 789}
54
+ @d.filter(:name => 'abc').update(:value => 530)
55
+
56
+ # the third record should stay the same
57
+ # floating-point precision bullshit
58
+ @d[:name => 'def'][:value].should == 789
59
+ @d.filter(:value => 530).count.should == 2
60
+ end
61
+
62
+ specify "should delete records correctly" do
63
+ @d << {:name => 'abc', :value => 123}
64
+ @d << {:name => 'abc', :value => 456}
65
+ @d << {:name => 'def', :value => 789}
66
+ @d.filter(:name => 'abc').delete
67
+
68
+ @d.count.should == 1
69
+ @d.first[:name].should == 'def'
70
+ end
71
+
72
+ specify "should be able to literalize booleans" do
73
+ proc {@d.literal(true)}.should_not raise_error
74
+ proc {@d.literal(false)}.should_not raise_error
75
+ end
76
+
77
+ specify "should support transactions" do
78
+ ORACLE_DB.transaction do
79
+ @d << {:name => 'abc', :value => 1}
80
+ end
81
+
82
+ @d.count.should == 1
83
+ end
84
+ end
85
+
86
+ context "A Oracle dataset in array tuples mode" do
87
+ setup do
88
+ @d = ORACLE_DB[:test]
89
+ @d.delete # remove all records
90
+ Sequel.use_array_tuples
91
+ end
92
+
93
+ teardown do
94
+ Sequel.use_hash_tuples
95
+ end
96
+
97
+ specify "should return the correct records" do
98
+ @d.to_a.should == []
99
+ @d << {:name => 'abc', :value => 123}
100
+ @d << {:name => 'abc', :value => 456}
101
+ @d << {:name => 'def', :value => 789}
102
+
103
+ @d.order(:value).select(:name, :value).to_a.should == [
104
+ ['abc', 123],
105
+ ['abc', 456],
106
+ ['def', 789]
107
+ ]
108
+ end
109
+
110
+ specify "should work correctly with transforms" do
111
+ @d.transform(:value => [proc {|v| v.to_s}, proc {|v| v.to_i}])
112
+
113
+ @d.to_a.should == []
114
+ @d << {:name => 'abc', :value => 123}
115
+ @d << {:name => 'abc', :value => 456}
116
+ @d << {:name => 'def', :value => 789}
117
+
118
+ @d.order(:value).select(:name, :value).to_a.should == [
119
+ ['abc', '123'],
120
+ ['abc', '456'],
121
+ ['def', '789']
122
+ ]
123
+
124
+ a = @d.order(:value).first
125
+ a.values.should == ['abc', '123']
126
+ a.keys.should == [:name, :value]
127
+ a[:name].should == 'abc'
128
+ a[:value].should == '123'
129
+ end
130
+ end