sequel 2.9.0 → 2.10.0

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