sequel_core 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/CHANGELOG +74 -0
  2. data/COPYING +1 -0
  3. data/README +17 -6
  4. data/Rakefile +16 -21
  5. data/lib/sequel_core.rb +18 -28
  6. data/lib/sequel_core/adapters/ado.rb +3 -15
  7. data/lib/sequel_core/adapters/dbi.rb +1 -14
  8. data/lib/sequel_core/adapters/informix.rb +3 -3
  9. data/lib/sequel_core/adapters/jdbc.rb +2 -2
  10. data/lib/sequel_core/adapters/mysql.rb +39 -59
  11. data/lib/sequel_core/adapters/odbc.rb +18 -38
  12. data/lib/sequel_core/adapters/openbase.rb +1 -17
  13. data/lib/sequel_core/adapters/oracle.rb +1 -19
  14. data/lib/sequel_core/adapters/postgres.rb +20 -60
  15. data/lib/sequel_core/adapters/sqlite.rb +4 -8
  16. data/lib/sequel_core/connection_pool.rb +150 -0
  17. data/lib/sequel_core/core_ext.rb +41 -0
  18. data/lib/sequel_core/core_sql.rb +35 -38
  19. data/lib/sequel_core/database.rb +20 -17
  20. data/lib/sequel_core/dataset.rb +49 -80
  21. data/lib/sequel_core/dataset/callback.rb +11 -13
  22. data/lib/sequel_core/dataset/convenience.rb +18 -136
  23. data/lib/sequel_core/dataset/pagination.rb +81 -0
  24. data/lib/sequel_core/dataset/sequelizer.rb +5 -4
  25. data/lib/sequel_core/dataset/sql.rb +43 -33
  26. data/lib/sequel_core/deprecated.rb +200 -0
  27. data/lib/sequel_core/exceptions.rb +0 -14
  28. data/lib/sequel_core/object_graph.rb +199 -0
  29. data/lib/sequel_core/pretty_table.rb +27 -24
  30. data/lib/sequel_core/schema/generator.rb +16 -4
  31. data/lib/sequel_core/schema/sql.rb +5 -3
  32. data/lib/sequel_core/worker.rb +1 -1
  33. data/spec/adapters/informix_spec.rb +1 -47
  34. data/spec/adapters/mysql_spec.rb +85 -54
  35. data/spec/adapters/oracle_spec.rb +1 -57
  36. data/spec/adapters/postgres_spec.rb +66 -49
  37. data/spec/adapters/sqlite_spec.rb +4 -29
  38. data/spec/connection_pool_spec.rb +358 -0
  39. data/spec/core_sql_spec.rb +24 -19
  40. data/spec/database_spec.rb +13 -9
  41. data/spec/dataset_spec.rb +59 -78
  42. data/spec/object_graph_spec.rb +202 -0
  43. data/spec/pretty_table_spec.rb +1 -9
  44. data/spec/schema_generator_spec.rb +7 -1
  45. data/spec/schema_spec.rb +27 -0
  46. data/spec/sequelizer_spec.rb +2 -2
  47. data/spec/spec_helper.rb +4 -2
  48. metadata +16 -57
  49. data/lib/sequel_core/array_keys.rb +0 -322
  50. data/lib/sequel_core/model.rb +0 -8
  51. data/spec/array_keys_spec.rb +0 -682
@@ -41,6 +41,10 @@ module Sequel
41
41
  ODBC::Dataset.new(self, opts)
42
42
  end
43
43
 
44
+ # ODBC returns native statement objects, which must be dropped if
45
+ # you call execute manually, or you will get warnings. See the
46
+ # fetch_rows method source code for an example of how to drop
47
+ # the statements.
44
48
  def execute(sql)
45
49
  @logger.info(sql) if @logger
46
50
  @pool.hold do |conn|
@@ -59,6 +63,10 @@ module Sequel
59
63
  class Dataset < Sequel::Dataset
60
64
  BOOL_TRUE = '1'.freeze
61
65
  BOOL_FALSE = '0'.freeze
66
+ ODBC_TIMESTAMP_FORMAT = "{ts '%Y-%m-%d %H:%M:%S'}".freeze
67
+ ODBC_TIMESTAMP_AFTER_SECONDS =
68
+ ODBC_TIMESTAMP_FORMAT.index( '%S' ).succ - ODBC_TIMESTAMP_FORMAT.length
69
+ ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
62
70
 
63
71
  def literal(v)
64
72
  case v
@@ -66,6 +74,15 @@ module Sequel
66
74
  BOOL_TRUE
67
75
  when false
68
76
  BOOL_FALSE
77
+ when Time
78
+ formatted = v.strftime(ODBC_TIMESTAMP_FORMAT)
79
+ if v.usec >= 1000
80
+ msec = ( v.usec.to_f / 1000 ).round
81
+ formatted.insert ODBC_TIMESTAMP_AFTER_SECONDS, ".#{msec}"
82
+ end
83
+ formatted
84
+ when Date
85
+ v.strftime(ODBC_DATE_FORMAT)
69
86
  else
70
87
  super
71
88
  end
@@ -135,43 +152,6 @@ module Sequel
135
152
  end
136
153
  end
137
154
 
138
- def array_tuples_fetch_rows(sql, &block)
139
- @db.synchronize do
140
- s = @db.execute sql
141
- begin
142
- @columns = s.columns(true).map {|c| c.name.to_sym}
143
- rows = s.fetch_all
144
- rows.each {|r| yield array_tuples_make_row(r)}
145
- ensure
146
- s.drop unless s.nil? rescue nil
147
- end
148
- end
149
- self
150
- end
151
-
152
- def array_tuples_make_row(row)
153
- row.keys = @columns
154
- row.each_with_index do |v, idx|
155
- # When fetching a result set, the Ruby ODBC driver converts all ODBC
156
- # SQL types to an equivalent Ruby type; with the exception of
157
- # SQL_TYPE_DATE, SQL_TYPE_TIME and SQL_TYPE_TIMESTAMP.
158
- #
159
- # The conversions below are consistent with the mappings in
160
- # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
161
- case v
162
- when ::ODBC::TimeStamp
163
- row[idx] = DateTime.new(v.year, v.month, v.day, v.hour, v.minute, v.second)
164
- when ::ODBC::Time
165
- now = DateTime.now
166
- row[idx] = Time.gm(now.year, now.month, now.day, v.hour, v.minute, v.second)
167
- when ::ODBC::Date
168
- row[idx] = Date.new(v.year, v.month, v.day)
169
- end
170
- end
171
- row
172
- end
173
-
174
-
175
155
  def insert(*values)
176
156
  @db.do insert_sql(*values)
177
157
  end
@@ -186,4 +166,4 @@ module Sequel
186
166
  end
187
167
  end
188
168
  end
189
- end
169
+ end
@@ -58,22 +58,6 @@ module Sequel
58
58
  self
59
59
  end
60
60
 
61
- def array_tuples_fetch_rows(sql, &block)
62
- @db.synchronize do
63
- result = @db.execute sql
64
- begin
65
- @columns = result.column_infos.map {|c| c.name.to_sym}
66
- result.each do |r|
67
- r.keys = @columns
68
- yield r
69
- end
70
- ensure
71
- # cursor.close
72
- end
73
- end
74
- self
75
- end
76
-
77
61
  def insert(*values)
78
62
  @db.do insert_sql(*values)
79
63
  end
@@ -87,4 +71,4 @@ module Sequel
87
71
  end
88
72
  end
89
73
  end
90
- end
74
+ end
@@ -100,24 +100,6 @@ module Sequel
100
100
  self
101
101
  end
102
102
 
103
- def array_tuples_fetch_rows(sql, &block)
104
- @db.synchronize do
105
- cursor = @db.execute sql
106
- begin
107
- @columns = cursor.get_col_names.map {|c| c.downcase.to_sym}
108
- raw_rnum_index = columns.index(:raw_rnum_)
109
- while r = cursor.fetch
110
- r.delete_at(raw_rnum_index) if raw_rnum_index
111
- r.keys = columns.delete_if{|x| x == :raw_rnum_}
112
- yield r
113
- end
114
- ensure
115
- cursor.close
116
- end
117
- end
118
- self
119
- end
120
-
121
103
  def insert(*values)
122
104
  @db.do insert_sql(*values)
123
105
  end
@@ -195,4 +177,4 @@ module Sequel
195
177
  alias sql select_sql
196
178
  end
197
179
  end
198
- end
180
+ end
@@ -328,16 +328,21 @@ module Sequel
328
328
 
329
329
  def index_definition_sql(table_name, index)
330
330
  index_name = index[:name] || default_index_name(table_name, index[:columns])
331
- if index[:full_text]
331
+ expr = "(#{literal(index[:columns])})"
332
+ unique = "UNIQUE " if index[:unique]
333
+ index_type = index[:type]
334
+ filter = index[:where] || index[:filter]
335
+ filter = " WHERE #{expression_list(filter)}" if filter
336
+ case index_type
337
+ when :full_text
332
338
  lang = index[:language] ? "#{literal(index[:language])}, " : ""
333
339
  cols = index[:columns].map {|c| literal(c)}.join(" || ")
334
- expr = "gin(to_tsvector(#{lang}#{cols}))"
335
- "CREATE INDEX #{index_name} ON #{table_name} USING #{expr}"
336
- elsif index[:unique]
337
- "CREATE UNIQUE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
338
- else
339
- "CREATE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
340
+ expr = "(to_tsvector(#{lang}#{cols}))"
341
+ index_type = :gin
342
+ when :spatial
343
+ index_type = :gist
340
344
  end
345
+ "CREATE #{unique}INDEX #{index_name} ON #{table_name} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
341
346
  end
342
347
 
343
348
  def drop_table_sql(name)
@@ -346,6 +351,9 @@ module Sequel
346
351
  end
347
352
 
348
353
  class Dataset < Sequel::Dataset
354
+
355
+ PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
356
+
349
357
  def quote_column_ref(c); "\"#{c}\""; end
350
358
 
351
359
  def literal(v)
@@ -354,6 +362,8 @@ module Sequel
354
362
  v
355
363
  when String, Fixnum, Float, TrueClass, FalseClass
356
364
  PGconn.quote(v)
365
+ when Time
366
+ "#{v.strftime(PG_TIMESTAMP_FORMAT)}.#{sprintf("%06d",v.usec)}'"
357
367
  else
358
368
  super
359
369
  end
@@ -433,7 +443,7 @@ module Sequel
433
443
 
434
444
  # Locks the table with the specified mode.
435
445
  def lock(mode, &block)
436
- sql = LOCK % [@opts[:from], mode]
446
+ sql = LOCK % [source_list(@opts[:from]), mode]
437
447
  @db.synchronize do
438
448
  if block # perform locking inside a transaction and yield to block
439
449
  @db.transaction {@db.execute(sql); yield}
@@ -450,11 +460,11 @@ module Sequel
450
460
  # postgresql 8.2 introduces support for insert
451
461
  columns = literal(columns)
452
462
  values = values.map {|r| "(#{literal(r)})"}.join(COMMA_SEPARATOR)
453
- ["INSERT INTO #{@opts[:from]} (#{columns}) VALUES #{values}"]
463
+ ["INSERT INTO #{source_list(@opts[:from])} (#{columns}) VALUES #{values}"]
454
464
  end
455
465
 
456
466
  def insert(*values)
457
- @db.execute_insert(insert_sql(*values), @opts[:from],
467
+ @db.execute_insert(insert_sql(*values), source_list(@opts[:from]),
458
468
  values.size == 1 ? values.first : values)
459
469
  end
460
470
 
@@ -506,56 +516,6 @@ module Sequel
506
516
  eval("lambda {|r| {#{kvs.join(COMMA_SEPARATOR)}}}")
507
517
  end
508
518
 
509
- def array_tuples_fetch_rows(sql, &block)
510
- @db.execute(sql) do |q|
511
- conv = array_tuples_row_converter(q)
512
- q.each {|r| yield conv[r]}
513
- end
514
- end
515
-
516
- @@array_tuples_converters_mutex = Mutex.new
517
- @@array_tuples_converters = {}
518
-
519
- def array_tuples_row_converter(result)
520
- @columns = []; translators = []
521
- result.fields.each_with_index do |f, idx|
522
- @columns << f.to_sym
523
- translators << PG_TYPES[result.type(idx)]
524
- end
525
-
526
- # create result signature and memoize the converter
527
- sig = [@columns, translators].hash
528
- @@array_tuples_converters_mutex.synchronize do
529
- @@array_tuples_converters[sig] ||= array_tuples_compile_converter(@columns, translators)
530
- end
531
- end
532
-
533
- def array_tuples_compile_converter(columns, translators)
534
- tr = []
535
- columns.each_with_index do |column, idx|
536
- if !AUTO_TRANSLATE and t = translators[idx]
537
- tr << "if (v = r[#{idx}]); r[#{idx}] = v.#{t}; end"
538
- end
539
- end
540
- eval("lambda {|r| r.keys = columns; #{tr.join(';')}; r}")
541
- end
542
-
543
- def array_tuples_transform_load(r)
544
- a = []; a.keys = []
545
- r.each_pair do |k, v|
546
- a[k] = (tt = @transform[k]) ? tt[0][v] : v
547
- end
548
- a
549
- end
550
-
551
- # Applies the value transform for data saved to the database.
552
- def array_tuples_transform_save(r)
553
- a = []; a.keys = []
554
- r.each_pair do |k, v|
555
- a[k] = (tt = @transform[k]) ? tt[1][v] : v
556
- end
557
- a
558
- end
559
519
  end
560
520
  end
561
521
  end
@@ -14,6 +14,7 @@ module Sequel
14
14
  @opts[:database] = ':memory:'
15
15
  end
16
16
  db = ::SQLite3::Database.new(@opts[:database])
17
+ db.busy_timeout(@opts.fetch(:timeout, 5000))
17
18
  db.type_translation = true
18
19
  # fix for timestamp translation
19
20
  db.translator.add_translator("timestamp") do |t, v|
@@ -131,6 +132,8 @@ module Sequel
131
132
  end
132
133
 
133
134
  class Dataset < Sequel::Dataset
135
+ def quote_column_ref(c); "`#{c}`"; end
136
+
134
137
  def literal(v)
135
138
  case v
136
139
  when Time
@@ -142,7 +145,7 @@ module Sequel
142
145
 
143
146
  def insert_sql(*values)
144
147
  if (values.size == 1) && values.first.is_a?(Sequel::Dataset)
145
- "INSERT INTO #{@opts[:from]} #{values.first.sql};"
148
+ "INSERT INTO #{source_list(@opts[:from])} #{values.first.sql};"
146
149
  else
147
150
  super(*values)
148
151
  end
@@ -160,13 +163,6 @@ module Sequel
160
163
  end
161
164
  end
162
165
 
163
- def array_tuples_fetch_rows(sql, &block)
164
- @db.execute_select(sql) do |result|
165
- @columns = result.columns.map {|c| c.to_sym}
166
- result.each {|r| r.keys = @columns; block[r]}
167
- end
168
- end
169
-
170
166
  def insert(*values)
171
167
  @db.execute_insert insert_sql(*values)
172
168
  end
@@ -0,0 +1,150 @@
1
+ require 'thread'
2
+
3
+ # A ConnectionPool manages access to database connections by keeping
4
+ # multiple connections and giving threads exclusive access to each
5
+ # connection.
6
+ class ConnectionPool
7
+ attr_reader :mutex
8
+
9
+ # The maximum number of connections.
10
+ attr_reader :max_size
11
+
12
+ # The proc used to create a new connection.
13
+ attr_accessor :connection_proc
14
+
15
+ attr_reader :available_connections, :allocated, :created_count
16
+
17
+ # Constructs a new pool with a maximum size. If a block is supplied, it
18
+ # is used to create new connections as they are needed.
19
+ #
20
+ # pool = ConnectionPool.new(10) {MyConnection.new(opts)}
21
+ #
22
+ # The connection creation proc can be changed at any time by assigning a
23
+ # Proc to pool#connection_proc.
24
+ #
25
+ # pool = ConnectionPool.new(10)
26
+ # pool.connection_proc = proc {MyConnection.new(opts)}
27
+ def initialize(max_size = 4, &block)
28
+ @max_size = max_size
29
+ @mutex = Mutex.new
30
+ @connection_proc = block
31
+
32
+ @available_connections = []
33
+ @allocated = {}
34
+ @created_count = 0
35
+ end
36
+
37
+ # Returns the number of created connections.
38
+ def size
39
+ @created_count
40
+ end
41
+
42
+ # Assigns a connection to the current thread, yielding the connection
43
+ # to the supplied block.
44
+ #
45
+ # pool.hold {|conn| conn.execute('DROP TABLE posts')}
46
+ #
47
+ # Pool#hold is re-entrant, meaning it can be called recursively in
48
+ # the same thread without blocking.
49
+ #
50
+ # If no connection is available, Pool#hold will block until a connection
51
+ # is available.
52
+ def hold
53
+ t = Thread.current
54
+ if (conn = owned_connection(t))
55
+ return yield(conn)
56
+ end
57
+ while !(conn = acquire(t))
58
+ sleep 0.001
59
+ end
60
+ begin
61
+ yield conn
62
+ ensure
63
+ release(t)
64
+ end
65
+ rescue Exception => e
66
+ # if the error is not a StandardError it is converted into RuntimeError.
67
+ raise e.is_a?(StandardError) ? e : e.message
68
+ end
69
+
70
+ # Removes all connection currently available, optionally yielding each
71
+ # connection to the given block. This method has the effect of
72
+ # disconnecting from the database. Once a connection is requested using
73
+ # #hold, the connection pool creates new connections to the database.
74
+ def disconnect(&block)
75
+ @mutex.synchronize do
76
+ @available_connections.each {|c| block[c]} if block
77
+ @available_connections = []
78
+ @created_count = @allocated.size
79
+ end
80
+ end
81
+
82
+ private
83
+ # Returns the connection owned by the supplied thread, if any.
84
+ def owned_connection(thread)
85
+ @mutex.synchronize {@allocated[thread]}
86
+ end
87
+
88
+ # Assigns a connection to the supplied thread, if one is available.
89
+ def acquire(thread)
90
+ @mutex.synchronize do
91
+ if conn = available
92
+ @allocated[thread] = conn
93
+ end
94
+ end
95
+ end
96
+
97
+ # Returns an available connection. If no connection is available,
98
+ # tries to create a new connection.
99
+ def available
100
+ @available_connections.pop || make_new
101
+ end
102
+
103
+ # Creates a new connection if the size of the pool is less than the
104
+ # maximum size.
105
+ def make_new
106
+ if @created_count < @max_size
107
+ @created_count += 1
108
+ @connection_proc ? @connection_proc.call : \
109
+ (raise Error, "No connection proc specified")
110
+ end
111
+ end
112
+
113
+ # Releases the connection assigned to the supplied thread.
114
+ def release(thread)
115
+ @mutex.synchronize do
116
+ @available_connections << @allocated[thread]
117
+ @allocated.delete(thread)
118
+ end
119
+ end
120
+ end
121
+
122
+ # A SingleThreadedPool acts as a replacement for a ConnectionPool for use
123
+ # in single-threaded applications. ConnectionPool imposes a substantial
124
+ # performance penalty, so SingleThreadedPool is used to gain some speed.
125
+ class SingleThreadedPool
126
+ attr_reader :conn
127
+ attr_writer :connection_proc
128
+
129
+ # Initializes the instance with the supplied block as the connection_proc.
130
+ def initialize(&block)
131
+ @connection_proc = block
132
+ end
133
+
134
+ # Yields the connection to the supplied block. This method simulates the
135
+ # ConnectionPool#hold API.
136
+ def hold
137
+ @conn ||= @connection_proc.call
138
+ yield @conn
139
+ rescue Exception => e
140
+ # if the error is not a StandardError it is converted into RuntimeError.
141
+ raise e.is_a?(StandardError) ? e : e.message
142
+ end
143
+
144
+ # Disconnects from the database. Once a connection is requested using
145
+ # #hold, the connection is reestablished.
146
+ def disconnect(&block)
147
+ block[@conn] if block && @conn
148
+ @conn = nil
149
+ end
150
+ end