sequel 2.9.0 → 2.10.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.
Files changed (78) hide show
  1. data/CHANGELOG +56 -0
  2. data/{README → README.rdoc} +85 -57
  3. data/Rakefile +10 -5
  4. data/bin/sequel +7 -16
  5. data/doc/advanced_associations.rdoc +5 -17
  6. data/doc/cheat_sheet.rdoc +18 -20
  7. data/doc/dataset_filtering.rdoc +8 -32
  8. data/doc/schema.rdoc +20 -0
  9. data/lib/sequel_core.rb +35 -1
  10. data/lib/sequel_core/adapters/ado.rb +1 -1
  11. data/lib/sequel_core/adapters/db2.rb +2 -2
  12. data/lib/sequel_core/adapters/dbi.rb +2 -11
  13. data/lib/sequel_core/adapters/do.rb +205 -0
  14. data/lib/sequel_core/adapters/do/mysql.rb +38 -0
  15. data/lib/sequel_core/adapters/do/postgres.rb +92 -0
  16. data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
  17. data/lib/sequel_core/adapters/firebird.rb +298 -0
  18. data/lib/sequel_core/adapters/informix.rb +10 -1
  19. data/lib/sequel_core/adapters/jdbc.rb +78 -19
  20. data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
  21. data/lib/sequel_core/adapters/jdbc/mysql.rb +10 -0
  22. data/lib/sequel_core/adapters/jdbc/postgresql.rb +7 -3
  23. data/lib/sequel_core/adapters/mysql.rb +53 -77
  24. data/lib/sequel_core/adapters/odbc.rb +1 -1
  25. data/lib/sequel_core/adapters/openbase.rb +1 -1
  26. data/lib/sequel_core/adapters/oracle.rb +2 -2
  27. data/lib/sequel_core/adapters/postgres.rb +16 -14
  28. data/lib/sequel_core/adapters/shared/mysql.rb +44 -9
  29. data/lib/sequel_core/adapters/shared/oracle.rb +4 -5
  30. data/lib/sequel_core/adapters/shared/postgres.rb +86 -82
  31. data/lib/sequel_core/adapters/shared/sqlite.rb +39 -24
  32. data/lib/sequel_core/adapters/sqlite.rb +11 -1
  33. data/lib/sequel_core/connection_pool.rb +10 -2
  34. data/lib/sequel_core/core_sql.rb +13 -3
  35. data/lib/sequel_core/database.rb +131 -30
  36. data/lib/sequel_core/database/schema.rb +5 -5
  37. data/lib/sequel_core/dataset.rb +31 -6
  38. data/lib/sequel_core/dataset/convenience.rb +11 -11
  39. data/lib/sequel_core/dataset/query.rb +2 -2
  40. data/lib/sequel_core/dataset/sql.rb +6 -6
  41. data/lib/sequel_core/exceptions.rb +4 -0
  42. data/lib/sequel_core/migration.rb +4 -4
  43. data/lib/sequel_core/schema/generator.rb +19 -3
  44. data/lib/sequel_core/schema/sql.rb +24 -20
  45. data/lib/sequel_core/sql.rb +13 -16
  46. data/lib/sequel_core/version.rb +11 -0
  47. data/lib/sequel_model.rb +2 -0
  48. data/lib/sequel_model/base.rb +2 -2
  49. data/lib/sequel_model/hooks.rb +46 -7
  50. data/lib/sequel_model/record.rb +11 -9
  51. data/lib/sequel_model/schema.rb +1 -1
  52. data/lib/sequel_model/validations.rb +72 -61
  53. data/spec/adapters/firebird_spec.rb +371 -0
  54. data/spec/adapters/mysql_spec.rb +118 -62
  55. data/spec/adapters/oracle_spec.rb +5 -5
  56. data/spec/adapters/postgres_spec.rb +33 -18
  57. data/spec/adapters/sqlite_spec.rb +2 -2
  58. data/spec/integration/dataset_test.rb +3 -3
  59. data/spec/integration/schema_test.rb +55 -5
  60. data/spec/integration/spec_helper.rb +11 -0
  61. data/spec/integration/type_test.rb +59 -16
  62. data/spec/sequel_core/connection_pool_spec.rb +14 -0
  63. data/spec/sequel_core/core_sql_spec.rb +24 -14
  64. data/spec/sequel_core/database_spec.rb +96 -11
  65. data/spec/sequel_core/dataset_spec.rb +97 -37
  66. data/spec/sequel_core/expression_filters_spec.rb +51 -40
  67. data/spec/sequel_core/object_graph_spec.rb +2 -2
  68. data/spec/sequel_core/schema_generator_spec.rb +31 -6
  69. data/spec/sequel_core/schema_spec.rb +25 -9
  70. data/spec/sequel_core/spec_helper.rb +4 -1
  71. data/spec/sequel_core/version_spec.rb +7 -0
  72. data/spec/sequel_model/associations_spec.rb +1 -1
  73. data/spec/sequel_model/hooks_spec.rb +68 -13
  74. data/spec/sequel_model/model_spec.rb +4 -4
  75. data/spec/sequel_model/record_spec.rb +22 -0
  76. data/spec/sequel_model/spec_helper.rb +2 -1
  77. data/spec/sequel_model/validations_spec.rb +107 -7
  78. metadata +15 -5
@@ -53,7 +53,16 @@ module Sequel
53
53
  def fetch_rows(sql, &block)
54
54
  execute(sql) do |cursor|
55
55
  begin
56
- cursor.open.each_hash(&block)
56
+ col_map = nil
57
+ cursor.open.each_hash do |h|
58
+ unless col_map
59
+ col_map = {}
60
+ @columns = h.keys.map{|k| col_map[k] = output_identifier(k)}
61
+ end
62
+ h2 = {}
63
+ h.each{|k,v| h2[col_map[k]||k] = v}
64
+ yield h2
65
+ end
57
66
  ensure
58
67
  cursor.drop
59
68
  end
@@ -61,6 +61,12 @@ module Sequel
61
61
  require 'sequel_core/adapters/shared/mssql'
62
62
  db.extend(Sequel::MSSQL::DatabaseMethods)
63
63
  com.microsoft.sqlserver.jdbc.SQLServerDriver
64
+ end,
65
+ :h2=>proc do |db|
66
+ require 'sequel_core/adapters/jdbc/h2'
67
+ db.extend(Sequel::JDBC::H2::DatabaseMethods)
68
+ JDBC.load_gem('h2')
69
+ org.h2.Driver
64
70
  end
65
71
  }
66
72
 
@@ -87,11 +93,12 @@ module Sequel
87
93
  # raise an error immediately if the connection doesn't have a
88
94
  # uri, since JDBC requires one.
89
95
  def initialize(opts)
90
- super(opts)
96
+ @opts = opts
91
97
  raise(Error, "No connection string specified") unless uri
92
98
  if match = /\Ajdbc:([^:]+)/.match(uri) and prok = DATABASE_SETUP[match[1].to_sym]
93
99
  prok.call(self)
94
100
  end
101
+ super(opts)
95
102
  end
96
103
 
97
104
  # Execute the given stored procedure with the give name. If a block is
@@ -178,6 +185,14 @@ module Sequel
178
185
  execute(sql, {:type=>:insert}.merge(opts))
179
186
  end
180
187
 
188
+ # All tables in this database
189
+ def tables
190
+ ts = []
191
+ ds = dataset
192
+ metadata(:getTables, nil, nil, nil, ['TABLE'].to_java(:string)){|h| ts << ds.send(:output_identifier, h[:table_name])}
193
+ ts
194
+ end
195
+
181
196
  # Default transaction method that should work on most JDBC
182
197
  # databases. Does not use the JDBC transaction methods, uses
183
198
  # SQL BEGIN/ROLLBACK/COMMIT statements instead.
@@ -214,10 +229,14 @@ module Sequel
214
229
  ur = opts[:uri] || opts[:url] || opts[:database]
215
230
  ur =~ /^\Ajdbc:/ ? ur : "jdbc:#{ur}"
216
231
  end
217
- alias url uri
218
232
 
219
233
  private
220
234
 
235
+ # The JDBC adapter should not need the pool to convert exceptions.
236
+ def connection_pool_default_options
237
+ super.merge(:pool_convert_exceptions=>false)
238
+ end
239
+
221
240
  # Close given adapter connections
222
241
  def disconnect_connection(c)
223
242
  c.close
@@ -283,6 +302,13 @@ module Sequel
283
302
  nil
284
303
  end
285
304
 
305
+ # Yield the metadata for this database
306
+ def metadata(*args, &block)
307
+ ds = dataset
308
+ ds.identifier_output_method = :downcase
309
+ synchronize{|c| ds.send(:process_result_set, c.getMetaData.send(*args), &block)}
310
+ end
311
+
286
312
  # Java being java, you need to specify the type of each argument
287
313
  # for the prepared statement, and bind it individually. This
288
314
  # guesses which JDBC method to use, and hopefully JRuby will convert
@@ -313,9 +339,20 @@ module Sequel
313
339
  conn
314
340
  end
315
341
 
316
- # The JDBC adapter should not need the pool to convert exceptions.
317
- def connection_pool_default_options
318
- super.merge(:pool_convert_exceptions=>false)
342
+ # All tables in this database
343
+ def schema_parse_table(table, opts={})
344
+ ds = dataset
345
+ schema, table = schema_and_table(table)
346
+ schema = ds.send(:input_identifier, schema) if schema
347
+ table = ds.send(:input_identifier, table)
348
+ pks, ts = [], []
349
+ metadata(:getPrimaryKeys, nil, schema, table) do |h|
350
+ pks << h[:column_name]
351
+ end
352
+ metadata(:getColumns, nil, schema, table, nil) do |h|
353
+ ts << [ds.send(:output_identifier, h[:column_name]), {:type=>schema_column_type(h[:type_name]), :db_type=>h[:type_name], :default=>(h[:column_def] == '' ? nil : h[:column_def]), :allow_null=>(h[:nullable] != 0), :primary_key=>pks.include?(h[:column_name])}]
354
+ end
355
+ ts
319
356
  end
320
357
  end
321
358
 
@@ -373,20 +410,7 @@ module Sequel
373
410
 
374
411
  # Correctly return rows from the database and return them as hashes.
375
412
  def fetch_rows(sql, &block)
376
- execute(sql) do |result|
377
- # get column names
378
- meta = result.getMetaData
379
- column_count = meta.getColumnCount
380
- @columns = []
381
- column_count.times {|i| @columns << meta.getColumnName(i+1).to_sym}
382
-
383
- # get rows
384
- while result.next
385
- row = {}
386
- @columns.each_with_index {|v, i| row[v] = result.getObject(i+1)}
387
- yield row
388
- end
389
- end
413
+ execute(sql){|result| process_result_set(result, &block)}
390
414
  self
391
415
  end
392
416
 
@@ -416,10 +440,45 @@ module Sequel
416
440
 
417
441
  private
418
442
 
443
+ # Convert the type. Used for converting Java types to ruby types.
444
+ def convert_type(v)
445
+ case v
446
+ when Java::JavaSQL::Timestamp, Java::JavaSQL::Time
447
+ v.to_string.to_sequel_time
448
+ when Java::JavaSQL::Date
449
+ v.to_string.to_date
450
+ when Java::JavaIo::BufferedReader
451
+ lines = []
452
+ while(line = v.read_line) do lines << line end
453
+ lines.join("\n")
454
+ when Java::JavaMath::BigDecimal
455
+ BigDecimal.new(v.to_string)
456
+ else
457
+ v
458
+ end
459
+ end
460
+
419
461
  # Extend the dataset with the JDBC stored procedure methods.
420
462
  def prepare_extend_sproc(ds)
421
463
  ds.extend(StoredProcedureMethods)
422
464
  end
465
+
466
+ # Split out from fetch rows to allow processing of JDBC result sets
467
+ # that don't come from issuing an SQL string.
468
+ def process_result_set(result)
469
+ # get column names
470
+ meta = result.getMetaData
471
+ column_count = meta.getColumnCount
472
+ @columns = []
473
+ column_count.times{|i| @columns << output_identifier(meta.getColumnName(i+1))}
474
+
475
+ # get rows
476
+ while result.next
477
+ row = {}
478
+ @columns.each_with_index{|v, i| row[v] = convert_type(result.getObject(i+1))}
479
+ yield row
480
+ end
481
+ end
423
482
  end
424
483
  end
425
484
  end
@@ -0,0 +1,69 @@
1
+ module Sequel
2
+ module JDBC
3
+ # Database and Dataset support for H2 databases accessed via JDBC.
4
+ module H2
5
+ # Instance methods for H2 Database objects accessed via JDBC.
6
+ module DatabaseMethods
7
+ # H2 needs to add a primary key column as a constraint
8
+ def alter_table_sql(table, op)
9
+ case op[:op]
10
+ when :add_column
11
+ if op.delete(:primary_key)
12
+ sql = super(table, op)
13
+ [sql, "ALTER TABLE #{quote_schema_table(table)} ADD PRIMARY KEY (#{quote_identifier(op[:name])})"]
14
+ else
15
+ super(table, op)
16
+ end
17
+ else
18
+ super(table, op)
19
+ end
20
+ end
21
+
22
+ # Return Sequel::JDBC::H2::Dataset object with the given opts.
23
+ def dataset(opts=nil)
24
+ Sequel::JDBC::H2::Dataset.new(self, opts)
25
+ end
26
+
27
+ # H2 uses an IDENTITY type
28
+ def serial_primary_key_options
29
+ {:primary_key => true, :type => :identity}
30
+ end
31
+
32
+ private
33
+
34
+ # Use IDENTITY() to get the last inserted id.
35
+ def last_insert_id(conn, opts={})
36
+ stmt = conn.createStatement
37
+ begin
38
+ rs = stmt.executeQuery('SELECT IDENTITY();')
39
+ rs.next
40
+ rs.getInt(1)
41
+ ensure
42
+ stmt.close
43
+ end
44
+ end
45
+
46
+ # Default to a single connection for a memory database.
47
+ def connection_pool_default_options
48
+ o = super
49
+ uri == 'jdbc:h2:mem:' ? o.merge(:max_connections=>1) : o
50
+ end
51
+ end
52
+
53
+ # Dataset class for H2 datasets accessed via JDBC.
54
+ class Dataset < JDBC::Dataset
55
+ # Use H2 syntax for Date, DateTime, and Time types.
56
+ def literal(v)
57
+ case v
58
+ when Date
59
+ v.strftime("DATE '%Y-%m-%d'")
60
+ when DateTime, Time
61
+ v.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S'")
62
+ else
63
+ super
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -46,6 +46,16 @@ module Sequel
46
46
  execute_insert(insert_sql(*values))
47
47
  end
48
48
 
49
+ # Handle time types correctly
50
+ def literal(v)
51
+ case v
52
+ when Time, DateTime
53
+ v.strftime("'%Y-%m-%d %H:%M:%S'")
54
+ else
55
+ super
56
+ end
57
+ end
58
+
49
59
  # Use execute_insert to execute the replace_sql.
50
60
  def replace(*args)
51
61
  execute_insert(replace_sql(*args))
@@ -91,12 +91,16 @@ module Sequel
91
91
  ps
92
92
  end
93
93
 
94
- # Convert Java::JavaSql::Timestamps correctly, and handle SQL::Blobs
95
- # correctly.
94
+ # Convert Java::JavaSql::Timestamps correctly, and handle Strings
95
+ # similar to the native postgres adapter.
96
96
  def literal(v)
97
97
  case v
98
+ when LiteralString
99
+ v
98
100
  when SQL::Blob
99
- "'#{v.gsub(/[\000-\037\047\134\177-\377]/){|b| "\\#{ b[0].to_s(8).rjust(3, '0') }"}}'"
101
+ super
102
+ when String
103
+ db.synchronize{|c| "'#{c.escape_string(v)}'"}
100
104
  when Java::JavaSql::Timestamp
101
105
  "TIMESTAMP #{literal(v.to_s)}"
102
106
  else
@@ -2,84 +2,40 @@ require 'mysql'
2
2
  require 'sequel_core/adapters/shared/mysql'
3
3
  require 'sequel_core/dataset/stored_procedures'
4
4
 
5
- # Add methods to get columns, yield hashes with symbol keys, and do
6
- # type conversion.
7
- class Mysql::Result
8
- # Mapping of type numbers to conversion methods.
9
- MYSQL_TYPES = {
10
- 0 => :to_d, # MYSQL_TYPE_DECIMAL
11
- 1 => :to_i, # MYSQL_TYPE_TINY
12
- 2 => :to_i, # MYSQL_TYPE_SHORT
13
- 3 => :to_i, # MYSQL_TYPE_LONG
14
- 4 => :to_f, # MYSQL_TYPE_FLOAT
15
- 5 => :to_f, # MYSQL_TYPE_DOUBLE
16
- # 6 => ??, # MYSQL_TYPE_NULL
17
- 7 => :to_sequel_time, # MYSQL_TYPE_TIMESTAMP
18
- 8 => :to_i, # MYSQL_TYPE_LONGLONG
19
- 9 => :to_i, # MYSQL_TYPE_INT24
20
- 10 => :to_date, # MYSQL_TYPE_DATE
21
- 11 => :to_time, # MYSQL_TYPE_TIME
22
- 12 => :to_sequel_time, # MYSQL_TYPE_DATETIME
23
- 13 => :to_i, # MYSQL_TYPE_YEAR
24
- 14 => :to_date, # MYSQL_TYPE_NEWDATE
25
- # 15 => :to_s # MYSQL_TYPE_VARCHAR
26
- # 16 => :to_s, # MYSQL_TYPE_BIT
27
- 246 => :to_d, # MYSQL_TYPE_NEWDECIMAL
28
- 247 => :to_i, # MYSQL_TYPE_ENUM
29
- 248 => :to_i, # MYSQL_TYPE_SET
30
- 249 => :to_blob, # MYSQL_TYPE_TINY_BLOB
31
- 250 => :to_blob, # MYSQL_TYPE_MEDIUM_BLOB
32
- 251 => :to_blob, # MYSQL_TYPE_LONG_BLOB
33
- 252 => :to_blob, # MYSQL_TYPE_BLOB
34
- # 253 => :to_s, # MYSQL_TYPE_VAR_STRING
35
- # 254 => :to_s, # MYSQL_TYPE_STRING
36
- # 255 => :to_s # MYSQL_TYPE_GEOMETRY
37
- }
38
-
39
- # Return an array of column name symbols for this result set.
40
- def columns(with_table = nil)
41
- unless @columns
42
- @column_types = []
43
- @columns = fetch_fields.map do |f|
44
- @column_types << f.type
45
- (with_table ? "#{f.table}.#{f.name}" : f.name).to_sym
46
- end
47
- end
48
- @columns
49
- end
50
-
51
- # yield a hash with symbol keys and type converted values.
52
- def sequel_each_hash(with_table = nil)
53
- c = columns
54
- while row = fetch_row
55
- h = {}
56
- c.each_with_index {|f, i| h[f] = convert_type(row[i], @column_types[i])}
57
- yield h
58
- end
59
- end
60
-
61
- private
62
-
63
- # Convert the type of v using the method in MYSQL_TYPES[type].
64
- def convert_type(v, type)
65
- if v
66
- if type == 1 && Sequel.convert_tinyint_to_bool
67
- # We special case tinyint here to avoid adding
68
- # a method to an ancestor of Fixnum
69
- v.to_i == 0 ? false : true
70
- else
71
- (t = MYSQL_TYPES[type]) ? v.send(t) : v
72
- end
73
- else
74
- nil
75
- end
76
- end
77
-
78
- end
79
-
80
5
  module Sequel
81
6
  # Module for holding all MySQL-related classes and modules for Sequel.
82
7
  module MySQL
8
+ # Mapping of type numbers to conversion methods.
9
+ MYSQL_TYPES = {
10
+ 0 => :to_d, # MYSQL_TYPE_DECIMAL
11
+ 1 => :to_i, # MYSQL_TYPE_TINY
12
+ 2 => :to_i, # MYSQL_TYPE_SHORT
13
+ 3 => :to_i, # MYSQL_TYPE_LONG
14
+ 4 => :to_f, # MYSQL_TYPE_FLOAT
15
+ 5 => :to_f, # MYSQL_TYPE_DOUBLE
16
+ # 6 => ??, # MYSQL_TYPE_NULL
17
+ 7 => :to_sequel_time, # MYSQL_TYPE_TIMESTAMP
18
+ 8 => :to_i, # MYSQL_TYPE_LONGLONG
19
+ 9 => :to_i, # MYSQL_TYPE_INT24
20
+ 10 => :to_date, # MYSQL_TYPE_DATE
21
+ 11 => :to_time, # MYSQL_TYPE_TIME
22
+ 12 => :to_sequel_time, # MYSQL_TYPE_DATETIME
23
+ 13 => :to_i, # MYSQL_TYPE_YEAR
24
+ 14 => :to_date, # MYSQL_TYPE_NEWDATE
25
+ # 15 => :to_s # MYSQL_TYPE_VARCHAR
26
+ # 16 => :to_s, # MYSQL_TYPE_BIT
27
+ 246 => :to_d, # MYSQL_TYPE_NEWDECIMAL
28
+ 247 => :to_i, # MYSQL_TYPE_ENUM
29
+ 248 => :to_i, # MYSQL_TYPE_SET
30
+ 249 => :to_blob, # MYSQL_TYPE_TINY_BLOB
31
+ 250 => :to_blob, # MYSQL_TYPE_MEDIUM_BLOB
32
+ 251 => :to_blob, # MYSQL_TYPE_LONG_BLOB
33
+ 252 => :to_blob, # MYSQL_TYPE_BLOB
34
+ # 253 => :to_s, # MYSQL_TYPE_VAR_STRING
35
+ # 254 => :to_s, # MYSQL_TYPE_STRING
36
+ # 255 => :to_s # MYSQL_TYPE_GEOMETRY
37
+ }
38
+
83
39
  # Database class for MySQL databases used with Sequel.
84
40
  class Database < Sequel::Database
85
41
  include Sequel::MySQL::DatabaseMethods
@@ -316,8 +272,13 @@ module Sequel
316
272
  # Yield all rows matching this dataset
317
273
  def fetch_rows(sql)
318
274
  execute(sql) do |r|
319
- @columns = r.columns
320
- r.sequel_each_hash {|row| yield row}
275
+ column_types = []
276
+ @columns = r.fetch_fields.map{|f| column_types << f.type; output_identifier(f.name)}
277
+ while row = r.fetch_row
278
+ h = {}
279
+ @columns.each_with_index {|f, i| h[f] = convert_type(row[i], column_types[i])}
280
+ yield h
281
+ end
321
282
  end
322
283
  self
323
284
  end
@@ -363,6 +324,21 @@ module Sequel
363
324
 
364
325
  private
365
326
 
327
+ # Convert the type of v using the method in MYSQL_TYPES[type].
328
+ def convert_type(v, type)
329
+ if v
330
+ if type == 1 && Sequel.convert_tinyint_to_bool
331
+ # We special case tinyint here to avoid adding
332
+ # a method to an ancestor of Fixnum
333
+ v.to_i == 0 ? false : true
334
+ else
335
+ (t = MYSQL_TYPES[type]) ? v.send(t) : v
336
+ end
337
+ else
338
+ nil
339
+ end
340
+ end
341
+
366
342
  # Set the :type option to :select if it hasn't been set.
367
343
  def execute(sql, opts={}, &block)
368
344
  super(sql, {:type=>:select}.merge(opts), &block)
@@ -129,7 +129,7 @@ module Sequel
129
129
  if (n = c.name).empty?
130
130
  n = UNTITLED_COLUMN % (untitled_count += 1)
131
131
  end
132
- n.to_sym
132
+ output_identifier(n)
133
133
  end
134
134
  rows = s.fetch_all
135
135
  rows.each {|row| yield hash_row(row)} if rows