sequel 3.18.0 → 3.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/CHANGELOG +18 -0
  2. data/{COPYING → MIT-LICENSE} +0 -0
  3. data/Rakefile +2 -2
  4. data/doc/release_notes/3.19.0.txt +67 -0
  5. data/doc/sharding.rdoc +25 -0
  6. data/lib/sequel/adapters/amalgalite.rb +1 -1
  7. data/lib/sequel/adapters/dbi.rb +1 -1
  8. data/lib/sequel/adapters/firebird.rb +1 -1
  9. data/lib/sequel/adapters/informix.rb +1 -1
  10. data/lib/sequel/adapters/jdbc.rb +27 -12
  11. data/lib/sequel/adapters/mysql.rb +42 -17
  12. data/lib/sequel/adapters/odbc.rb +1 -1
  13. data/lib/sequel/adapters/oracle.rb +1 -1
  14. data/lib/sequel/adapters/postgres.rb +30 -17
  15. data/lib/sequel/adapters/shared/mssql.rb +2 -2
  16. data/lib/sequel/adapters/shared/sqlite.rb +1 -1
  17. data/lib/sequel/adapters/sqlite.rb +39 -16
  18. data/lib/sequel/adapters/swift/postgres.rb +1 -1
  19. data/lib/sequel/dataset/actions.rb +2 -2
  20. data/lib/sequel/dataset/query.rb +3 -3
  21. data/lib/sequel/model/associations.rb +2 -0
  22. data/lib/sequel/model/base.rb +2 -2
  23. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  24. data/lib/sequel/plugins/schema.rb +1 -1
  25. data/lib/sequel/plugins/subclasses.rb +1 -1
  26. data/lib/sequel/plugins/typecast_on_load.rb +1 -1
  27. data/lib/sequel/plugins/validation_class_methods.rb +32 -3
  28. data/lib/sequel/version.rb +1 -1
  29. data/spec/extensions/validation_class_methods_spec.rb +20 -1
  30. data/spec/integration/associations_test.rb +20 -0
  31. data/spec/integration/database_test.rb +6 -0
  32. data/spec/integration/prepared_statement_test.rb +58 -1
  33. data/spec/integration/schema_test.rb +1 -1
  34. data/spec/integration/type_test.rb +1 -1
  35. data/spec/model/associations_spec.rb +46 -2
  36. data/spec/model/model_spec.rb +11 -0
  37. metadata +8 -6
data/CHANGELOG CHANGED
@@ -1,3 +1,21 @@
1
+ === 3.19.0 (2011-01-03)
2
+
3
+ * Handle Date and DateTime types in prepared statements when using the jdbc adapter (jeremyevans)
4
+
5
+ * Handle Date, DateTime, Time, SQL::Blob, true, and false in prepared statements when using the SQLite adapter (jeremyevans)
6
+
7
+ * Use varbinary(max) instead of image for the generic blob type on MSSQL (jeremyevans)
8
+
9
+ * Close prepared statements when disconnecting when using SQLite (jeremyevans)
10
+
11
+ * Allow reflecting on validations in the validation_class_methods plugin (jeremyevans)
12
+
13
+ * Allow passing a primary key value to the add_* association method (gucki)
14
+
15
+ * When typecasting model column values, check the classes of the new and existing values (jeremyevans)
16
+
17
+ * Improve type translation performance in the postgres, mysql, and sqlite adapters by using methods instead of procs (jeremyevans)
18
+
1
19
  === 3.18.0 (2010-12-01)
2
20
 
3
21
  * Allow the user to control how the connection pool deals with attempts to access shards that aren't configured (jeremyevans)
File without changes
data/Rakefile CHANGED
@@ -43,7 +43,7 @@ end
43
43
  Rake::RDocTask.new do |rdoc|
44
44
  rdoc.rdoc_dir = "rdoc"
45
45
  rdoc.options += RDOC_OPTS
46
- rdoc.rdoc_files.add %w"README.rdoc CHANGELOG COPYING lib/**/*.rb doc/*.rdoc doc/release_notes/*.txt"
46
+ rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb doc/*.rdoc doc/release_notes/*.txt"
47
47
  end
48
48
 
49
49
  ### Website
@@ -59,7 +59,7 @@ task :website_rdoc=>[:website_rdoc_main, :website_rdoc_adapters, :website_rdoc_p
59
59
  Rake::RDocTask.new(:website_rdoc_main) do |rdoc|
60
60
  rdoc.rdoc_dir = "www/public/rdoc"
61
61
  rdoc.options += RDOC_OPTS
62
- rdoc.rdoc_files.add %w"README.rdoc CHANGELOG COPYING lib/*.rb lib/sequel/*.rb lib/sequel/{connection_pool,dataset,database,model}/*.rb doc/*.rdoc doc/release_notes/*.txt lib/sequel/extensions/migration.rb"
62
+ rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/*.rb lib/sequel/*.rb lib/sequel/{connection_pool,dataset,database,model}/*.rb doc/*.rdoc doc/release_notes/*.txt lib/sequel/extensions/migration.rb"
63
63
  end
64
64
 
65
65
  Rake::RDocTask.new(:website_rdoc_adapters) do |rdoc|
@@ -0,0 +1,67 @@
1
+ = New Features
2
+
3
+ * The add_* association methods now accept a primary key, and
4
+ associates the receiver to the associated model object with that
5
+ primary key:
6
+
7
+ artist.add_album(42)
8
+ # equivalent to: artist.add_album(Album[42])
9
+
10
+ * The validation_class_methods plugin now has the ability to
11
+ reflect on validations:
12
+
13
+ Album.plugin :validation_class_methods
14
+ Album.validates_acceptance_of(:a)
15
+ Album.validation_reflections
16
+ # => {:a=>[[:acceptance, {:tag=>:acceptance, :allow_nil=>true,
17
+ :message=>"is not accepted", :accept=>"1"}]]}
18
+
19
+ = Other Improvements
20
+
21
+ * In the postgres, mysql, and sqlite adapters, typecasting now uses
22
+ methods instead of procs. Since methods aren't closures (while
23
+ procs are), this makes typecasting faster (up to 15%).
24
+
25
+ * When typecasting model column values, the classes of the new and
26
+ existing values are checked in addition to the values themselves.
27
+ Previously, if the new and existing values were equal (i.e. 1.0
28
+ and 1), it wouldn't update the value. Now, if the classes are
29
+ different, it always updates the value.
30
+
31
+ * Date and DateTime objects are now handled correctly when using
32
+ prepared statements/bound variables in the jdbc adapter.
33
+
34
+ * Date, DateTime, Time, true, false, and SQL::Blob objects are now
35
+ handled correctly when using prepared statements/bound variables
36
+ in the sqlite adapter.
37
+
38
+ * Sequel now uses varbinary(max) instead of image for the generic
39
+ File (blob) type on Microsoft SQL Server. This makes it possible
40
+ to use an SQL::Blob object as a prepared statement argument.
41
+
42
+ * Sequel now handles blobs better in the Amalgalite adapter.
43
+
44
+ * When disconnecting a connection using the sqlite adapter, all
45
+ open prepared statements are now closed first. Previously,
46
+ attempting to disconnect a connection with open prepared statements
47
+ resulted in an error.
48
+
49
+ * The license file has been renamed from COPYING to MIT-LICENSE, to
50
+ make it easier to determine at a glance which license is used.
51
+
52
+ = Backwards Compatibility
53
+
54
+ * Because Sequel switched the generic File type from image to
55
+ varbinary(max) on Microsoft SQL Server, any migrations/schema
56
+ modification methods that used the File type will now result in a
57
+ different column type than before.
58
+
59
+ * The MYSQL_TYPE_PROCS, PG_TYPE_PROCS, and SQLITE_TYPE_PROCS
60
+ constants have been removed from the mysql, postgres, and sqlite
61
+ adapters, respectively. The UNIX_EPOCH_TIME_FORMAT and
62
+ FALSE_VALUES constants have also been removed from the sqlite
63
+ adapter.
64
+
65
+ * Typecasting in the sqlite adapters now uses to_i and to_f instead
66
+ of Integer() and Float() with rescues. If you put non-numeric
67
+ data in numeric columns on SQLite, this could cause problems.
data/doc/sharding.rdoc CHANGED
@@ -112,6 +112,31 @@ the shard to use. This is fairly easy using a Sequel::Model:
112
112
 
113
113
  Rainbow.plaintext_for_hash("e580726d31f6e1ad216ffd87279e536d1f74e606")
114
114
 
115
+ The connection pool can be further controlled to change how it handles attempts
116
+ to access shards that haven't been configured. The default is
117
+ to assume the :default shard. However, you can specify a
118
+ different shard using the :servers_hash option when connecting
119
+ to the database:
120
+
121
+ DB = Sequel.connect(..., :servers_hash=>Hash.new(:some_shard))
122
+
123
+ You can also use this feature to raise an exception if an
124
+ unconfigured shard is used:
125
+
126
+ DB = Sequel.connect(..., :servers_hash=>Hash.new{raise ...})
127
+
128
+ If you specify a :servers_hash option to raise an exception for non configured
129
+ shards you should also explicitly specify a :read_only entry in your :servers option
130
+ for the case where a shard is not specified. In most cases it is sufficient
131
+ to make the :read_only entry the same as the :default shard:
132
+
133
+ servers = {:read_only => {}}
134
+ (('0'..'9').to_a + ('a'..'f').to_a).each do |hex|
135
+ servers[hex.to_sym] = {:host=>"hash_host_#{hex}"}
136
+ end
137
+ DB=Sequel.connect('postgres://hash_host/hashes', :servers=>servers,
138
+ :servers_hash=>Hash.new{raise Exception.new("Invalid Server")})
139
+
115
140
  === Sharding Plugin
116
141
 
117
142
  Sequel comes with a sharding plugin that makes it easy to use sharding with model objects.
@@ -36,7 +36,7 @@ module Sequel
36
36
  # type doesn't match a known type, just return the value.
37
37
  def result_value_of(declared_type, value)
38
38
  if value.is_a?(::Amalgalite::Blob)
39
- SQL::Blob.new(value.source)
39
+ SQL::Blob.new(value.to_s)
40
40
  elsif value.is_a?(String) && declared_type
41
41
  (meth = self.class.sql_to_method(declared_type.downcase)) ? send(meth, value) : value
42
42
  else
@@ -88,7 +88,7 @@ module Sequel
88
88
  end
89
89
 
90
90
  class Dataset < Sequel::Dataset
91
- def fetch_rows(sql, &block)
91
+ def fetch_rows(sql)
92
92
  execute(sql) do |s|
93
93
  begin
94
94
  @columns = s.column_names.map{|c| output_identifier(c)}
@@ -201,7 +201,7 @@ module Sequel
201
201
 
202
202
  # Yield all rows returned by executing the given SQL and converting
203
203
  # the types.
204
- def fetch_rows(sql, &block)
204
+ def fetch_rows(sql)
205
205
  execute(sql) do |s|
206
206
  begin
207
207
  @columns = s.fields.map{|c| output_identifier(c.name)}
@@ -37,7 +37,7 @@ module Sequel
37
37
  class Dataset < Sequel::Dataset
38
38
  SELECT_CLAUSE_METHODS = clause_methods(:select, %w'limit distinct columns from join where having group compounds order')
39
39
 
40
- def fetch_rows(sql, &block)
40
+ def fetch_rows(sql)
41
41
  execute(sql) do |cursor|
42
42
  begin
43
43
  col_map = nil
@@ -191,7 +191,7 @@ module Sequel
191
191
  return execute_prepared_statement(sql, opts, &block) if [Symbol, Dataset].any?{|c| sql.is_a?(c)}
192
192
  synchronize(opts[:server]) do |conn|
193
193
  statement(conn) do |stmt|
194
- if block_given?
194
+ if block
195
195
  yield log_yield(sql){stmt.executeQuery(sql)}
196
196
  else
197
197
  case opts[:type]
@@ -338,14 +338,25 @@ module Sequel
338
338
  JavaxNaming::InitialContext.new.lookup(jndi_name).connection
339
339
  end
340
340
 
341
- # Support fractional seconds for Time objects used in bound variables
342
- def java_sql_timestamp(time)
343
- millis = time.to_i * 1000
344
- ts = java.sql.Timestamp.new(millis)
345
- ts.setNanos(time.usec * 1000)
341
+ # Support Date objects used in bound variables
342
+ def java_sql_date(date)
343
+ java.sql.Date.new(Time.local(date.year, date.month, date.day).to_i * 1000)
344
+ end
345
+
346
+ # Support DateTime objects used in bound variables
347
+ def java_sql_datetime(datetime)
348
+ ts = java.sql.Timestamp.new(Time.local(datetime.year, datetime.month, datetime.day, datetime.hour, datetime.min, datetime.sec).to_i * 1000)
349
+ ts.setNanos((datetime.sec_fraction * (RUBY_VERSION >= '1.9.0' ? 1000000000 : 86400000000000)).to_i)
346
350
  ts
347
351
  end
348
352
 
353
+ # Support fractional seconds for Time objects used in bound variables
354
+ def java_sql_timestamp(time)
355
+ ts = java.sql.Timestamp.new(time.to_i * 1000)
356
+ ts.setNanos(RUBY_VERSION >= '1.9.0' ? time.nsec : time.usec * 1000)
357
+ ts
358
+ end
359
+
349
360
  # Log the given SQL and then execute it on the connection, used by
350
361
  # the transaction code.
351
362
  def log_connection_execute(conn, sql)
@@ -389,18 +400,22 @@ module Sequel
389
400
  cps.setBytes(i, arg.to_java_bytes)
390
401
  when String
391
402
  cps.setString(i, arg)
392
- when Date, Java::JavaSql::Date
393
- cps.setDate(i, arg)
394
- when DateTime, Java::JavaSql::Timestamp
395
- cps.setTimestamp(i, arg)
396
- when Time
397
- cps.setTimestamp(i, java_sql_timestamp(arg))
398
403
  when Float
399
404
  cps.setDouble(i, arg)
400
405
  when TrueClass, FalseClass
401
406
  cps.setBoolean(i, arg)
402
407
  when nil
403
408
  cps.setNull(i, JavaSQL::Types::NULL)
409
+ when DateTime
410
+ cps.setTimestamp(i, java_sql_datetime(arg))
411
+ when Date
412
+ cps.setDate(i, java_sql_date(arg))
413
+ when Time
414
+ cps.setTimestamp(i, java_sql_timestamp(arg))
415
+ when Java::JavaSql::Timestamp
416
+ cps.setTimestamp(i, arg)
417
+ when Java::JavaSql::Date
418
+ cps.setDate(i, arg)
404
419
  else
405
420
  cps.setObject(i, arg)
406
421
  end
@@ -10,33 +10,58 @@ Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
10
10
  module Sequel
11
11
  # Module for holding all MySQL-related classes and modules for Sequel.
12
12
  module MySQL
13
- # Mapping of type numbers to conversion procs
14
- MYSQL_TYPES = {}
13
+ TYPE_TRANSLATOR = tt = Class.new do
14
+ def boolean(s) s.to_i != 0 end
15
+ def blob(s) ::Sequel::SQL::Blob.new(s) end
16
+ def integer(s) s.to_i end
17
+ def float(s) s.to_f end
18
+ def decimal(s) ::BigDecimal.new(s) end
19
+ def date(s) ::Sequel.string_to_date(s) end
20
+ def time(s) ::Sequel.string_to_time(s) end
21
+ def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
22
+ def date_conv(s) ::Sequel::MySQL.convert_date_time(:string_to_date, s) end
23
+ def time_conv(s) ::Sequel::MySQL.convert_date_time(:string_to_time, s) end
24
+ def timestamp_conv(s) ::Sequel::MySQL.convert_date_time(:database_to_application_timestamp, s) end
25
+ end.new
15
26
 
16
- # Use only a single proc for each type to save on memory
17
- MYSQL_TYPE_PROCS = {
18
- [0, 246] => lambda{|v| BigDecimal.new(v)}, # decimal
19
- [1] => lambda{|v| convert_tinyint_to_bool ? v.to_i != 0 : v.to_i}, # tinyint
20
- [2, 3, 8, 9, 13, 247, 248] => lambda{|v| v.to_i}, # integer
21
- [4, 5] => lambda{|v| v.to_f}, # float
22
- [10, 14] => lambda{|v| convert_date_time(:string_to_date, v)}, # date
23
- [7, 12] => lambda{|v| convert_date_time(:database_to_application_timestamp, v)}, # datetime
24
- [11] => lambda{|v| convert_date_time(:string_to_time, v)}, # time
25
- [249, 250, 251, 252] => lambda{|v| Sequel::SQL::Blob.new(v)} # blob
26
- }
27
- MYSQL_TYPE_PROCS.each do |k,v|
27
+ # Hash with integer keys and callable values for converting MySQL types.
28
+ MYSQL_TYPES = {}
29
+ {
30
+ [0, 246] => tt.method(:decimal),
31
+ [2, 3, 8, 9, 13, 247, 248] => tt.method(:integer),
32
+ [4, 5] => tt.method(:float),
33
+ [249, 250, 251, 252] => tt.method(:blob)
34
+ }.each do |k,v|
28
35
  k.each{|n| MYSQL_TYPES[n] = v}
29
36
  end
30
-
31
- @convert_invalid_date_time = false
37
+
38
+ # Modify the type translator used for the tinyint type based
39
+ # on the value given.
40
+ def self.convert_tinyint_to_bool=(v)
41
+ MYSQL_TYPES[1] = TYPE_TRANSLATOR.method(v ? :boolean : :integer)
42
+ @convert_tinyint_to_bool = v
43
+ end
44
+ self.convert_tinyint_to_bool = convert_tinyint_to_bool
32
45
 
33
46
  class << self
34
47
  # By default, Sequel raises an exception if in invalid date or time is used.
35
48
  # However, if this is set to nil or :nil, the adapter treats dates
36
49
  # like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
37
50
  # it returns the strings as is.
38
- attr_accessor :convert_invalid_date_time
51
+ attr_reader :convert_invalid_date_time
52
+ end
53
+
54
+ # Modify the type translators for the date, time, and timestamp types
55
+ # depending on the value given.
56
+ def self.convert_invalid_date_time=(v)
57
+ MYSQL_TYPES[11] = TYPE_TRANSLATOR.method(v == false ? :time : :time_conv)
58
+ m = TYPE_TRANSLATOR.method(v == false ? :date : :date_conv)
59
+ [10, 14].each{|i| MYSQL_TYPES[i] = m}
60
+ m = TYPE_TRANSLATOR.method(v == false ? :timestamp : :timestamp_conv)
61
+ [7, 12].each{|i| MYSQL_TYPES[i] = m}
62
+ @convert_invalid_date_time = v
39
63
  end
64
+ self.convert_invalid_date_time = false
40
65
 
41
66
  # If convert_invalid_date_time is nil, :nil, or :string and
42
67
  # the conversion raises an InvalidValue exception, return v
@@ -87,7 +87,7 @@ module Sequel
87
87
  ODBC_DATE_FORMAT = "{d '%Y-%m-%d'}".freeze
88
88
  TIMESTAMP_FORMAT="{ts '%Y-%m-%d %H:%M:%S'}".freeze
89
89
 
90
- def fetch_rows(sql, &block)
90
+ def fetch_rows(sql)
91
91
  execute(sql) do |s|
92
92
  i = -1
93
93
  cols = s.columns(true).map{|c| [output_identifier(c.name), i+=1]}
@@ -102,7 +102,7 @@ module Sequel
102
102
  class Dataset < Sequel::Dataset
103
103
  include DatasetMethods
104
104
 
105
- def fetch_rows(sql, &block)
105
+ def fetch_rows(sql)
106
106
  execute(sql) do |cursor|
107
107
  begin
108
108
  @columns = cursor.get_col_names.map{|c| output_identifier(c)}
@@ -88,32 +88,45 @@ module Sequel
88
88
  module Postgres
89
89
  CONVERTED_EXCEPTIONS << PGError
90
90
 
91
- # Hash with integer keys and proc values for converting PostgreSQL types.
92
- PG_TYPES = {}
91
+ TYPE_TRANSLATOR = tt = Class.new do
92
+ def boolean(s) s == 't' end
93
+ def bytea(s) ::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s)) end
94
+ def integer(s) s.to_i end
95
+ def float(s) s.to_f end
96
+ def numeric(s) ::BigDecimal.new(s) end
97
+ def date(s) ::Sequel.string_to_date(s) end
98
+ def date_iso(s) ::Date.new(*s.split("-").map{|x| x.to_i}) end
99
+ def time(s) ::Sequel.string_to_time(s) end
100
+ def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
101
+ end.new
93
102
 
94
- # Use a single proc for each type to conserve memory
95
- PG_TYPE_PROCS = {
96
- [16] => lambda{|s| s == 't'}, # boolean
97
- [17] => lambda{|s| ::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s))}, # bytea
98
- [20, 21, 22, 23, 26] => lambda{|s| s.to_i}, # integer
99
- [700, 701] => lambda{|s| s.to_f}, # float
100
- [790, 1700] => lambda{|s| BigDecimal.new(s)}, # numeric
101
- [1082] => lambda{|s| @use_iso_date_format ? Date.new(*s.split("-").map{|x| x.to_i}) : Sequel.string_to_date(s)}, # date
102
- [1083, 1266] => lambda{|s| Sequel.string_to_time(s)}, # time
103
- [1114, 1184] => lambda{|s| Sequel.database_to_application_timestamp(s)}, # timestamp
104
- }
105
- PG_TYPE_PROCS.each do |k,v|
103
+ # Hash with integer keys and callable values for converting PostgreSQL types.
104
+ PG_TYPES = {}
105
+ {
106
+ [16] => tt.method(:boolean),
107
+ [17] => tt.method(:bytea),
108
+ [20, 21, 22, 23, 26] => tt.method(:integer),
109
+ [700, 701] => tt.method(:float),
110
+ [790, 1700] => tt.method(:numeric),
111
+ [1083, 1266] => tt.method(:time),
112
+ [1114, 1184] => tt.method(:timestamp)
113
+ }.each do |k,v|
106
114
  k.each{|n| PG_TYPES[n] = v}
107
115
  end
108
116
 
109
- @use_iso_date_format = true
110
-
111
117
  class << self
112
118
  # As an optimization, Sequel sets the date style to ISO, so that PostgreSQL provides
113
119
  # the date in a known format that Sequel can parse faster. This can be turned off
114
120
  # if you require a date style other than ISO.
115
- attr_accessor :use_iso_date_format
121
+ attr_reader :use_iso_date_format
122
+ end
123
+
124
+ # Modify the type translator for the date type depending on the value given.
125
+ def self.use_iso_date_format=(v)
126
+ PG_TYPES[1082] = TYPE_TRANSLATOR.method(v ? :date_iso : :date)
127
+ @use_iso_date_format = v
116
128
  end
129
+ self.use_iso_date_format = true
117
130
 
118
131
  # PGconn subclass for connection specific methods used with the
119
132
  # pg, postgres, or postgres-pr driver.
@@ -181,9 +181,9 @@ module Sequel
181
181
  :bit
182
182
  end
183
183
 
184
- # MSSQL uses image type for blobs
184
+ # MSSQL uses varbinary(max) type for blobs
185
185
  def type_literal_generic_file(column)
186
- :image
186
+ :'varbinary(max)'
187
187
  end
188
188
 
189
189
  # support for clustered index type
@@ -356,7 +356,7 @@ module Sequel
356
356
  end
357
357
 
358
358
  # HAVING requires GROUP BY on SQLite
359
- def having(*cond, &block)
359
+ def having(*cond)
360
360
  raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") unless @opts[:group]
361
361
  super
362
362
  end
@@ -10,27 +10,36 @@ module Sequel
10
10
  # Top level module for holding all SQLite-related modules and classes
11
11
  # for Sequel.
12
12
  module SQLite
13
- UNIX_EPOCH_TIME_FORMAT = /\A\d+\z/.freeze
13
+ TYPE_TRANSLATOR = tt = Class.new do
14
+ FALSE_VALUES = %w'0 false f no n'.freeze
15
+ def boolean(s) !FALSE_VALUES.include?(s.downcase) end
16
+ def blob(s) ::Sequel::SQL::Blob.new(s) end
17
+ def integer(s) s.to_i end
18
+ def float(s) s.to_f end
19
+ def numeric(s) ::BigDecimal.new(s) rescue s end
20
+ def date(s) ::Sequel.string_to_date(s) end
21
+ def time(s) ::Sequel.string_to_time(s) end
22
+ def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
23
+ end.new
24
+
25
+ # Hash with string keys and callable values for converting SQLite types.
14
26
  SQLITE_TYPES = {}
15
- FALSE_VALUES = %w'0 false f no n'.freeze
16
- SQLITE_TYPE_PROCS = {
17
- %w'timestamp datetime' => proc{|v| Sequel.database_to_application_timestamp(v)},
18
- %w'date' => proc{|v| Sequel.string_to_date(v)},
19
- %w'time' => proc{|v| Sequel.string_to_time(v)},
20
- %w'bit bool boolean' => proc{|v| !FALSE_VALUES.include?(v.downcase)},
21
- %w'integer smallint mediumint int bigint' => proc{|v| Integer(v) rescue v},
22
- %w'numeric decimal money' => proc{|v| BigDecimal.new(v) rescue v},
23
- %w'float double real dec fixed' + ['double precision'] => proc{|v| Float(v) rescue v},
24
- %w'blob' => proc{|v| ::Sequel::SQL::Blob.new(v)}
25
- }
26
- SQLITE_TYPE_PROCS.each do |k,v|
27
+ {
28
+ %w'timestamp datetime' => tt.method(:timestamp),
29
+ %w'date' => tt.method(:date),
30
+ %w'time' => tt.method(:time),
31
+ %w'bit bool boolean' => tt.method(:boolean),
32
+ %w'integer smallint mediumint int bigint' => tt.method(:integer),
33
+ %w'numeric decimal money' => tt.method(:numeric),
34
+ %w'float double real dec fixed' + ['double precision'] => tt.method(:float),
35
+ %w'blob' => tt.method(:blob)
36
+ }.each do |k,v|
27
37
  k.each{|n| SQLITE_TYPES[n] = v}
28
38
  end
29
39
 
30
40
  # Database class for SQLite databases used with Sequel and the
31
41
  # ruby-sqlite3 driver.
32
42
  class Database < Sequel::Database
33
- UNIX_EPOCH_TIME_FORMAT = /\A\d+\z/.freeze
34
43
  include ::Sequel::SQLite::DatabaseMethods
35
44
 
36
45
  set_adapter_scheme :sqlite
@@ -138,11 +147,24 @@ module Sequel
138
147
  o
139
148
  end
140
149
 
150
+ def prepared_statement_argument(arg)
151
+ case arg
152
+ when Date, DateTime, Time, TrueClass, FalseClass
153
+ literal(arg)[1...-1]
154
+ when SQL::Blob
155
+ arg.to_blob
156
+ else
157
+ arg
158
+ end
159
+ end
160
+
141
161
  # Execute a prepared statement on the database using the given name.
142
162
  def execute_prepared_statement(conn, type, name, opts, &block)
143
163
  ps = prepared_statements[name]
144
164
  sql = ps.prepared_sql
145
165
  args = opts[:arguments]
166
+ ps_args = {}
167
+ args.each{|k, v| ps_args[k] = prepared_statement_argument(v)}
146
168
  if cpsa = conn.prepared_statements[name]
147
169
  cps, cps_sql = cpsa
148
170
  if cps_sql != sql
@@ -155,9 +177,9 @@ module Sequel
155
177
  conn.prepared_statements[name] = [cps, sql]
156
178
  end
157
179
  if block
158
- log_yield("Executing prepared statement #{name}", args){cps.execute(args, &block)}
180
+ log_yield("Executing prepared statement #{name}", args){cps.execute(ps_args, &block)}
159
181
  else
160
- log_yield("Executing prepared statement #{name}", args){cps.execute!(args){|r|}}
182
+ log_yield("Executing prepared statement #{name}", args){cps.execute!(ps_args){|r|}}
161
183
  case type
162
184
  when :insert
163
185
  conn.last_insert_row_id
@@ -174,6 +196,7 @@ module Sequel
174
196
 
175
197
  # Disconnect given connections from the database.
176
198
  def disconnect_connection(c)
199
+ c.prepared_statements.each_value{|v| v.first.close}
177
200
  c.close
178
201
  end
179
202
  end
@@ -14,7 +14,7 @@ module Sequel
14
14
 
15
15
  # Log all SQL that goes through the execute method to the related
16
16
  # database object.
17
- def execute(sql, *args, &block)
17
+ def execute(sql, *args)
18
18
  @db.log_yield(sql){super}
19
19
  rescue SwiftError => e
20
20
  @db.send(:raise_error, e)
@@ -133,11 +133,11 @@ module Sequel
133
133
  get(1).nil?
134
134
  end
135
135
 
136
- # Executes a select query and fetches records, passing each record to the
136
+ # Executes a select query and fetches records, yielding each record to the
137
137
  # supplied block. The yielded records should be hashes with symbol keys.
138
138
  # This method should probably should not be called by user code, use +each+
139
139
  # instead.
140
- def fetch_rows(sql, &block)
140
+ def fetch_rows(sql)
141
141
  raise NotImplemented, NOTIMPL_MSG
142
142
  end
143
143
 
@@ -418,10 +418,10 @@ module Sequel
418
418
  table_name = table_alias || table
419
419
  end
420
420
 
421
- join = if expr.nil? and !block_given?
421
+ join = if expr.nil? and !block
422
422
  SQL::JoinClause.new(type, table, table_alias)
423
423
  elsif using_join
424
- raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block_given?
424
+ raise(Sequel::Error, "can't use a block if providing an array of symbols as expr") if block
425
425
  SQL::JoinUsingClause.new(expr, type, table, table_alias)
426
426
  else
427
427
  last_alias ||= @opts[:last_joined_table] || first_source_alias
@@ -432,7 +432,7 @@ module Sequel
432
432
  [k,v]
433
433
  end
434
434
  end
435
- if block_given?
435
+ if block
436
436
  expr2 = yield(table_name, last_alias, @opts[:join] || [])
437
437
  expr = expr ? SQL::BooleanExpression.new(:AND, expr, expr2) : expr2
438
438
  end
@@ -1067,6 +1067,8 @@ module Sequel
1067
1067
  klass = opts.associated_class
1068
1068
  if o.is_a?(Hash)
1069
1069
  o = klass.new(o)
1070
+ elsif o.is_a?(Integer) || o.is_a?(String) || o.is_a?(Array)
1071
+ o = klass[o]
1070
1072
  elsif !o.is_a?(klass)
1071
1073
  raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
1072
1074
  end
@@ -205,7 +205,7 @@ module Sequel
205
205
  # Artist.server!(:server1)
206
206
  def def_dataset_method(*args, &block)
207
207
  raise(Error, "No arguments given") if args.empty?
208
- if block_given?
208
+ if block
209
209
  raise(Error, "Defining a dataset method using a block requires only one argument") if args.length > 1
210
210
  meth = args.first
211
211
  @dataset_methods[meth] = block
@@ -729,7 +729,7 @@ module Sequel
729
729
  # If the column isn't in @values, we can't assume it is
730
730
  # NULL in the database, so assume it has changed.
731
731
  v = typecast_value(column, value)
732
- if new? || !@values.include?(column) || v != @values[column]
732
+ if new? || !@values.include?(column) || v != (c = @values[column]) || v.class != c.class
733
733
  changed_columns << column unless changed_columns.include?(column)
734
734
  @values[column] = v
735
735
  end
@@ -68,7 +68,7 @@ module Sequel
68
68
  # The class_table_inheritance plugin requires the lazy_attributes plugin
69
69
  # to handle lazily-loaded attributes for subclass instances returned
70
70
  # by superclass methods.
71
- def self.apply(model, opts={}, &block)
71
+ def self.apply(model, opts={})
72
72
  model.plugin :lazy_attributes
73
73
  end
74
74
 
@@ -81,7 +81,7 @@ module Sequel
81
81
  # * :table_map - Hash with class name symbol keys and table name symbol
82
82
  # values. Necessary if the implicit table name for the model class
83
83
  # does not match the database table name
84
- def self.configure(model, opts={}, &block)
84
+ def self.configure(model, opts={})
85
85
  model.instance_eval do
86
86
  m = method(:constantize)
87
87
  @cti_base_model = self
@@ -20,7 +20,7 @@ module Sequel
20
20
  module ClassMethods
21
21
  # Creates table, using the column information from set_schema.
22
22
  def create_table(*args, &block)
23
- set_schema(*args, &block) if block_given?
23
+ set_schema(*args, &block) if block
24
24
  db.create_table(table_name, :generator=>@schema)
25
25
  @db_schema = get_db_schema(true)
26
26
  columns
@@ -17,7 +17,7 @@ module Sequel
17
17
  # c.descendents # [sc1, ssc1, sc2]
18
18
  module Subclasses
19
19
  # Initialize the subclasses instance variable for the model.
20
- def self.apply(model, &block)
20
+ def self.apply(model)
21
21
  model.instance_variable_set(:@subclasses, [])
22
22
  end
23
23
 
@@ -20,7 +20,7 @@ module Sequel
20
20
  # the model object, you'll get Date objects instead of strings.
21
21
  module TypecastOnLoad
22
22
  # Call add_typecast_on_load_columns on the passed column arguments.
23
- def self.configure(model, *columns, &block)
23
+ def self.configure(model, *columns)
24
24
  model.instance_eval do
25
25
  @typecast_on_load_columns ||= []
26
26
  add_typecast_on_load_columns(*columns)
@@ -6,7 +6,9 @@ module Sequel
6
6
  # for the legacy class-level validation methods (e.g. validates_presence_of :column).
7
7
  #
8
8
  # It is recommended to use the validation_helpers plugin instead of this one,
9
- # as it is less complex and more flexible.
9
+ # as it is less complex and more flexible. However, this plugin provides reflection
10
+ # support, since it is class-level, while the instance-level validation_helpers
11
+ # plugin does not.
10
12
  #
11
13
  # Usage:
12
14
  #
@@ -21,14 +23,21 @@ module Sequel
21
23
  model.class_eval do
22
24
  @validation_mutex = Mutex.new
23
25
  @validations = {}
26
+ @validation_reflections = {}
24
27
  end
25
28
  end
26
29
 
27
30
  module ClassMethods
28
- # A hash of associations for this model class. Keys are column symbols,
31
+ # A hash of validations for this model class. Keys are column symbols,
29
32
  # values are arrays of validation procs.
30
33
  attr_reader :validations
31
34
 
35
+ # A hash of validation reflections for this model class. Keys are column
36
+ # symbols, values are an array of two element arrays, with the first element
37
+ # being the validation type symbol and the second being a hash of validation
38
+ # options.
39
+ attr_reader :validation_reflections
40
+
32
41
  # The Generator class is used to generate validation definitions using
33
42
  # the validates {} idiom.
34
43
  class Generator
@@ -49,12 +58,16 @@ module Sequel
49
58
  !validations.empty?
50
59
  end
51
60
 
52
- # Setup the validations hash in the subclass
61
+ # Setup the validations and validation_reflections hash in the subclass.
53
62
  def inherited(subclass)
54
63
  super
64
+ vr = @validation_reflections
55
65
  subclass.class_eval do
56
66
  @validation_mutex = Mutex.new
57
67
  @validations = {}
68
+ h = {}
69
+ vr.each{|k,v| h[k] = v.dup}
70
+ @validation_reflections = h
58
71
  end
59
72
  end
60
73
 
@@ -115,6 +128,7 @@ module Sequel
115
128
  :accept => '1',
116
129
  :tag => :acceptance,
117
130
  }.merge!(extract_options!(atts))
131
+ reflect_validation(:acceptance, opts, atts)
118
132
  atts << opts
119
133
  validates_each(*atts) do |o, a, v|
120
134
  o.errors.add(a, opts[:message]) unless v == opts[:accept]
@@ -136,6 +150,7 @@ module Sequel
136
150
  :message => 'is not confirmed',
137
151
  :tag => :confirmation,
138
152
  }.merge!(extract_options!(atts))
153
+ reflect_validation(:confirmation, opts, atts)
139
154
  atts << opts
140
155
  validates_each(*atts) do |o, a, v|
141
156
  o.errors.add(a, opts[:message]) unless v == o.send(:"#{a}_confirmation")
@@ -205,6 +220,7 @@ module Sequel
205
220
  raise ArgumentError, "A regular expression must be supplied as the :with option of the options hash"
206
221
  end
207
222
 
223
+ reflect_validation(:format, opts, atts)
208
224
  atts << opts
209
225
  validates_each(*atts) do |o, a, v|
210
226
  o.errors.add(a, opts[:message]) unless v.to_s =~ opts[:with]
@@ -231,6 +247,7 @@ module Sequel
231
247
  }.merge!(extract_options!(atts))
232
248
 
233
249
  opts[:tag] ||= ([:length] + [:maximum, :minimum, :is, :within].reject{|x| !opts.include?(x)}).join('-').to_sym
250
+ reflect_validation(:length, opts, atts)
234
251
  atts << opts
235
252
  validates_each(*atts) do |o, a, v|
236
253
  if m = opts[:maximum]
@@ -261,6 +278,7 @@ module Sequel
261
278
  opts = {
262
279
  :tag => :not_string,
263
280
  }.merge!(extract_options!(atts))
281
+ reflect_validation(:not_string, opts, atts)
264
282
  atts << opts
265
283
  validates_each(*atts) do |o, a, v|
266
284
  if v.is_a?(String)
@@ -286,6 +304,7 @@ module Sequel
286
304
  :message => 'is not a number',
287
305
  :tag => :numericality,
288
306
  }.merge!(extract_options!(atts))
307
+ reflect_validation(:numericality, opts, atts)
289
308
  atts << opts
290
309
  validates_each(*atts) do |o, a, v|
291
310
  begin
@@ -310,6 +329,7 @@ module Sequel
310
329
  :message => 'is not present',
311
330
  :tag => :presence,
312
331
  }.merge!(extract_options!(atts))
332
+ reflect_validation(:presence, opts, atts)
313
333
  atts << opts
314
334
  validates_each(*atts) do |o, a, v|
315
335
  o.errors.add(a, opts[:message]) if v.blank? && v != false
@@ -327,6 +347,7 @@ module Sequel
327
347
  raise ArgumentError, "The :in parameter is required, and respond to include?"
328
348
  end
329
349
  opts[:message] ||= "is not in range or set: #{opts[:in].inspect}"
350
+ reflect_validation(:inclusion, opts, atts)
330
351
  atts << opts
331
352
  validates_each(*atts) do |o, a, v|
332
353
  o.errors.add(a, opts[:message]) unless opts[:in].include?(v)
@@ -355,6 +376,7 @@ module Sequel
355
376
  :tag => :uniqueness,
356
377
  }.merge!(extract_options!(atts))
357
378
 
379
+ reflect_validation(:uniqueness, opts, atts)
358
380
  atts << opts
359
381
  validates_each(*atts) do |o, a, v|
360
382
  error_field = a
@@ -391,6 +413,13 @@ module Sequel
391
413
  array.last.is_a?(Hash) ? array.pop : {}
392
414
  end
393
415
 
416
+ # Add the validation reflection to the class's validations.
417
+ def reflect_validation(type, opts, atts)
418
+ atts.each do |att|
419
+ (validation_reflections[att] ||= []) << [type, opts]
420
+ end
421
+ end
422
+
394
423
  # Handle the :if option for validations
395
424
  def validation_if_proc(o, i)
396
425
  case i
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 3
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 18
6
+ MINOR = 19
7
7
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
8
8
  # releases that fix regressions from previous versions.
9
9
  TINY = 0
@@ -9,11 +9,30 @@ describe Sequel::Model do
9
9
  end
10
10
  end
11
11
 
12
- specify "should respond to validates, validations, has_validations?" do
12
+ specify "should respond to validations, has_validations?, and validation_reflections" do
13
13
  @c.should respond_to(:validations)
14
14
  @c.should respond_to(:has_validations?)
15
+ @c.should respond_to(:validation_reflections)
15
16
  end
16
17
 
18
+ specify "should be able to reflect on validations" do
19
+ @c.validation_reflections.should == {}
20
+ @c.validates_acceptance_of(:a)
21
+ @c.validation_reflections.should == {:a=>[[:acceptance, {:tag=>:acceptance, :message=>"is not accepted", :allow_nil=>true, :accept=>"1"}]]}
22
+ @c.validates_presence_of(:a)
23
+ @c.validation_reflections[:a].length.should == 2
24
+ @c.validation_reflections[:a].last.should == [:presence, {:tag=>:presence, :message=>"is not present"}]
25
+ end
26
+
27
+ specify "should handle validation reflections correctly when subclassing" do
28
+ @c.validates_acceptance_of(:a)
29
+ c = Class.new(@c)
30
+ c.validation_reflections.map{|k,v| k}.should == [:a]
31
+ c.validates_presence_of(:a)
32
+ @c.validation_reflections.should == {:a=>[[:acceptance, {:tag=>:acceptance, :message=>"is not accepted", :allow_nil=>true, :accept=>"1"}]]}
33
+ c.validation_reflections[:a].last.should == [:presence, {:tag=>:presence, :message=>"is not present"}]
34
+ end
35
+
17
36
  specify "should acccept validation definitions using validates_each" do
18
37
  @c.validates_each(:xx, :yy) {|o, a, v| o.errors[a] << 'too low' if v < 50}
19
38
  o = @c.new
@@ -196,6 +196,16 @@ describe "Sequel::Model Simple Associations" do
196
196
  @album.tags_dataset.first[:name].should == 'T2'
197
197
  end
198
198
 
199
+ specify "should have add method accept primary key and add related records" do
200
+ @artist.remove_all_albums
201
+ @artist.add_album(@album.id)
202
+ @artist.albums_dataset.first[:id].should == @album.id
203
+
204
+ @album.remove_all_tags
205
+ @album.add_tag(@tag.id)
206
+ @album.tags_dataset.first[:id].should == @tag.id
207
+ end
208
+
199
209
  specify "should have remove method accept primary key and remove related album" do
200
210
  @artist.add_album(@album)
201
211
  @artist.reload.remove_album(@album.id)
@@ -287,6 +297,16 @@ describe "Sequel::Model Composite Key Associations" do
287
297
  @album.tags_dataset.first[:name].should == 'T2'
288
298
  end
289
299
 
300
+ specify "should have add method accept primary key and add related records" do
301
+ @artist.remove_all_albums
302
+ @artist.add_album([@album.id1, @album.id2])
303
+ @artist.albums_dataset.first.pk.should == [@album.id1, @album.id2]
304
+
305
+ @album.remove_all_tags
306
+ @album.add_tag([@tag.id1, @tag.id2])
307
+ @album.tags_dataset.first.pk.should == [@tag.id1, @tag.id2]
308
+ end
309
+
290
310
  specify "should have remove method accept primary key and remove related album" do
291
311
  @artist.add_album(@album)
292
312
  @artist.reload.remove_album([@album.id1, @album.id2])
@@ -8,6 +8,12 @@ describe Sequel::Database do
8
8
  INTEGRATION_DB.pool.size.should == 0
9
9
  end
10
10
 
11
+ specify "should provide disconnect functionality after preparing a connection" do
12
+ INTEGRATION_DB['SELECT 1'].prepare(:first, :a).call
13
+ INTEGRATION_DB.disconnect
14
+ INTEGRATION_DB.pool.size.should == 0
15
+ end
16
+
11
17
  specify "should raise Sequel::DatabaseError on invalid SQL" do
12
18
  proc{INTEGRATION_DB << "SELECT"}.should raise_error(Sequel::DatabaseError)
13
19
  end
@@ -12,7 +12,6 @@ describe "Prepared Statements and Bound Arguments" do
12
12
  @ds.meta_def(:ba) do |sym|
13
13
  prepared_arg_placeholder == '$' ? :"#{sym}__int" : sym
14
14
  end
15
- clear_sqls
16
15
  end
17
16
  after do
18
17
  INTEGRATION_DB.drop_table(:items)
@@ -211,3 +210,61 @@ describe "Prepared Statements and Bound Arguments" do
211
210
  end
212
211
  end
213
212
  end
213
+
214
+ describe "Bound Argument Types" do
215
+ before do
216
+ INTEGRATION_DB.create_table!(:items) do
217
+ primary_key :id
218
+ Date :d
219
+ DateTime :dt
220
+ File :file
221
+ String :s
222
+ Time :t
223
+ Float :f
224
+ TrueClass :b
225
+ end
226
+ @ds = INTEGRATION_DB[:items]
227
+ @vs = {:d=>Date.civil(2010, 10, 11), :dt=>DateTime.civil(2010, 10, 12, 13, 14, 15), :f=>1.0, :s=>'str', :t=>Time.at(20101010), :file=>Sequel::SQL::Blob.new('blob'), :b=>true}
228
+ @ds.insert(@vs)
229
+ @ds.meta_def(:ba) do |sym, type|
230
+ prepared_arg_placeholder == '$' ? :"#{sym}__#{type}" : sym
231
+ end
232
+ end
233
+ after do
234
+ Sequel.datetime_class = Time
235
+ if INTEGRATION_DB.adapter_scheme == :jdbc && INTEGRATION_DB.database_type == :sqlite
236
+ INTEGRATION_DB.synchronize{|c| c.prepared_statements.each{|k, ps| ps[1].close}.clear}
237
+ end
238
+ INTEGRATION_DB.drop_table(:items)
239
+ end
240
+
241
+ cspecify "should handle date type", [:do, :sqlite], :mssql, [:jdbc, :sqlite] do
242
+ @ds.filter(:d=>@ds.ba(:$x, :date)).prepare(:first, :ps_date).call(:x=>@vs[:d])[:d].should == @vs[:d]
243
+ end
244
+
245
+ cspecify "should handle datetime type", [:do], [:mysql2], [:swift], [:jdbc, :sqlite] do
246
+ Sequel.datetime_class = DateTime
247
+ @ds.filter(:dt=>@ds.ba(:$x, :timestamp)).prepare(:first, :ps_datetime).call(:x=>@vs[:dt])[:dt].should == @vs[:dt]
248
+ end
249
+
250
+ cspecify "should handle time type", [:do], [:jdbc, :sqlite] do
251
+ @ds.filter(:t=>@ds.ba(:$x, :timestamp)).prepare(:first, :ps_time).call(:x=>@vs[:t])[:t].should == @vs[:t]
252
+ end
253
+
254
+ cspecify "should handle blob type", [:swift], [:jdbc, :postgres] do
255
+ @ds.filter(:file=>@ds.ba(:$x, :bytea)).prepare(:first, :ps_blob).call(:x=>@vs[:file])[:file].should == @vs[:file]
256
+ end
257
+
258
+ specify "should handle float type" do
259
+ @ds.filter(:f=>@ds.ba(:$x, :"double precision")).prepare(:first, :ps_float).call(:x=>@vs[:f])[:f].should == @vs[:f]
260
+ end
261
+
262
+ specify "should handle string type" do
263
+ @ds.filter(:s=>@ds.ba(:$x, :text)).prepare(:first, :ps_string).call(:x=>@vs[:s])[:s].should == @vs[:s]
264
+ end
265
+
266
+ cspecify "should handle boolean type", [:do, :sqlite], [:odbc, :mssql], [:jdbc, :sqlite] do
267
+ @ds.filter(:b=>@ds.ba(:$x, :boolean)).prepare(:first, :ps_string).call(:x=>@vs[:b])[:b].should == @vs[:b]
268
+ end
269
+ end unless INTEGRATION_DB.adapter_scheme == :swift && INTEGRATION_DB.database_type == :postgres
270
+
@@ -9,10 +9,10 @@ describe "Database schema parser" do
9
9
  clear_sqls
10
10
  end
11
11
  after do
12
- INTEGRATION_DB.drop_table(:items) if INTEGRATION_DB.table_exists?(:items)
13
12
  INTEGRATION_DB.identifier_output_method = @iom
14
13
  INTEGRATION_DB.identifier_input_method = @iim
15
14
  INTEGRATION_DB.default_schema = @defsch
15
+ INTEGRATION_DB.drop_table(:items) if INTEGRATION_DB.table_exists?(:items)
16
16
  end
17
17
 
18
18
  specify "should handle a database with a identifier_output_method" do
@@ -79,7 +79,7 @@ describe "Supported types" do
79
79
  ds.first[:tim].strftime('%Y%m%d%H%M%S').should == t.strftime('%Y%m%d%H%M%S')
80
80
  end
81
81
 
82
- cspecify "should support generic file type", [:do], [:odbc, :mssql], [:mysql2], [:swift] do
82
+ cspecify "should support generic file type", [:do], [:odbc, :mssql], [:mysql2], [:swift], [:jdbc, :postgres] do
83
83
  ds = create_items_table_with_column(:name, File)
84
84
  ds.insert(:name => ("a\0"*300).to_sequel_blob)
85
85
  ds.all.should == [{:name=>("a\0"*300).to_sequel_blob}]
@@ -1077,7 +1077,11 @@ describe Sequel::Model, "one_to_many" do
1077
1077
  @c1 = Class.new(Sequel::Model(:attributes)) do
1078
1078
  def _refresh(ds); end
1079
1079
  unrestrict_primary_key
1080
- columns :id, :node_id, :y
1080
+ columns :id, :node_id, :y, :z
1081
+
1082
+ def self.[](id)
1083
+ load(id.is_a?(Array) ? {:id => id[0], :z => id[1]} : {:id => id})
1084
+ end
1081
1085
  end
1082
1086
 
1083
1087
  @c2 = Class.new(Sequel::Model(:nodes)) do
@@ -1228,6 +1232,14 @@ describe Sequel::Model, "one_to_many" do
1228
1232
  MODEL_DB.sqls.first.should =~ /INSERT INTO attributes \((node_)?id, (node_)?id\) VALUES \(1?234, 1?234\)/
1229
1233
  end
1230
1234
 
1235
+ it "should accept a primary key for the add_ method" do
1236
+ @c2.one_to_many :attributes, :class => @c1
1237
+ n = @c2.new(:id => 1234)
1238
+ MODEL_DB.reset
1239
+ @c1.load(:node_id => 1234, :id => 234).should == n.add_attribute(234)
1240
+ MODEL_DB.sqls.first.should == "UPDATE attributes SET node_id = 1234 WHERE (id = 234)"
1241
+ end
1242
+
1231
1243
  it "should raise an error in the add_ method if the passed associated object is not of the correct type" do
1232
1244
  @c2.one_to_many :attributes, :class => @c1
1233
1245
  proc{@c2.new(:id => 1234).add_attribute(@c2.new)}.should raise_error(Sequel::Error)
@@ -1286,6 +1298,16 @@ describe Sequel::Model, "one_to_many" do
1286
1298
  a.should == n.add_attribute(a)
1287
1299
  MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id = 1234|y = 5), (node_id = 1234|y = 5) WHERE \(id = 2345\)/
1288
1300
  end
1301
+
1302
+ it "should have add_ method accept a composite key" do
1303
+ @c1.set_primary_key :id, :z
1304
+ @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
1305
+
1306
+ n = @c2.load(:id => 1234, :x=>5)
1307
+ a = @c1.load(:id => 2345, :z => 8, :node_id => 1234, :y=>5)
1308
+ a.should == n.add_attribute([2345, 8])
1309
+ MODEL_DB.sqls.first.should =~ /UPDATE attributes SET (node_id|y) = (1234|5), (node_id|y) = (1234|5) WHERE \(\((id|z) = (2345|8)\) AND \((id|z) = (2345|8)\)\)/
1310
+ end
1289
1311
 
1290
1312
  it "should have remove_ method respect composite keys" do
1291
1313
  @c2.one_to_many :attributes, :class => @c1, :key =>[:node_id, :y], :primary_key=>[:id, :x]
@@ -1834,9 +1856,12 @@ describe Sequel::Model, "many_to_many" do
1834
1856
  def self.to_s; 'Attribute'; end
1835
1857
  columns :id, :y
1836
1858
  def _refresh(ds)
1837
- self.id = 1
1838
1859
  self
1839
1860
  end
1861
+
1862
+ def self.[](id)
1863
+ load(id.is_a?(Array) ? {:id => id[0], :y => id[1]} : {:id => id})
1864
+ end
1840
1865
  end
1841
1866
 
1842
1867
  @c2 = Class.new(Sequel::Model(:nodes)) do
@@ -2063,6 +2088,17 @@ describe Sequel::Model, "many_to_many" do
2063
2088
  ].should(include(MODEL_DB.sqls.first))
2064
2089
  end
2065
2090
 
2091
+ it "should define an add_ method that works with a primary key" do
2092
+ @c2.many_to_many :attributes, :class => @c1
2093
+
2094
+ n = @c2.load(:id => 1234)
2095
+ a = @c1.load(:id => 2345)
2096
+ a.should == n.add_attribute(2345)
2097
+ ['INSERT INTO attributes_nodes (node_id, attribute_id) VALUES (1234, 2345)',
2098
+ 'INSERT INTO attributes_nodes (attribute_id, node_id) VALUES (2345, 1234)'
2099
+ ].should(include(MODEL_DB.sqls.first))
2100
+ end
2101
+
2066
2102
  it "should allow passing a hash to the add_ method which creates a new record" do
2067
2103
  @c2.many_to_many :attributes, :class => @c1
2068
2104
 
@@ -2148,6 +2184,14 @@ describe Sequel::Model, "many_to_many" do
2148
2184
  v.should == true
2149
2185
  end
2150
2186
  end
2187
+
2188
+ it "should have the add_ method respect composite keys" do
2189
+ @c2.many_to_many :attributes, :class => @c1, :left_key=>[:l1, :l2], :right_key=>[:r1, :r2], :left_primary_key=>[:id, :x], :right_primary_key=>[:id, :y]
2190
+ n = @c2.load(:id => 1234, :x=>5)
2191
+ a = @c1.load(:id => 2345, :y=>8)
2192
+ a.should == n.add_attribute([2345, 8])
2193
+ MODEL_DB.sqls.first.should =~ /INSERT INTO attributes_nodes \([lr][12], [lr][12], [lr][12], [lr][12]\) VALUES \((1234|5|2345|8), (1234|5|2345|8), (1234|5|2345|8), (1234|5|2345|8)\)/
2194
+ end
2151
2195
 
2152
2196
  it "should have the remove_ method respect the :left_primary_key and :right_primary_key options" do
2153
2197
  @c2.many_to_many :attributes, :class => @c1, :left_primary_key=>:xxx, :right_primary_key=>:yyy
@@ -452,6 +452,17 @@ describe Sequel::Model, "attribute accessors" do
452
452
  o.x = '34'
453
453
  o.x.should == 34
454
454
  end
455
+
456
+ it "should typecast if the new value is the same as the existing but has a different class" do
457
+ @c.set_dataset(@dataset.select(:y))
458
+ o = @c.new
459
+
460
+ o.x = 34
461
+ o.x = 34.0
462
+ o.x.should == 34.0
463
+ o.x = 34
464
+ o.x.should == 34
465
+ end
455
466
  end
456
467
 
457
468
  describe Sequel::Model, ".[]" do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- hash: 79
4
+ hash: 75
5
5
  prerelease: false
6
6
  segments:
7
7
  - 3
8
- - 18
8
+ - 19
9
9
  - 0
10
- version: 3.18.0
10
+ version: 3.19.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jeremy Evans
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-12-01 00:00:00 -08:00
18
+ date: 2011-01-03 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -28,7 +28,7 @@ extensions: []
28
28
  extra_rdoc_files:
29
29
  - README.rdoc
30
30
  - CHANGELOG
31
- - COPYING
31
+ - MIT-LICENSE
32
32
  - doc/advanced_associations.rdoc
33
33
  - doc/cheat_sheet.rdoc
34
34
  - doc/dataset_filtering.rdoc
@@ -82,8 +82,9 @@ extra_rdoc_files:
82
82
  - doc/release_notes/3.16.0.txt
83
83
  - doc/release_notes/3.17.0.txt
84
84
  - doc/release_notes/3.18.0.txt
85
+ - doc/release_notes/3.19.0.txt
85
86
  files:
86
- - COPYING
87
+ - MIT-LICENSE
87
88
  - CHANGELOG
88
89
  - README.rdoc
89
90
  - Rakefile
@@ -131,6 +132,7 @@ files:
131
132
  - doc/release_notes/3.16.0.txt
132
133
  - doc/release_notes/3.17.0.txt
133
134
  - doc/release_notes/3.18.0.txt
135
+ - doc/release_notes/3.19.0.txt
134
136
  - doc/sharding.rdoc
135
137
  - doc/sql.rdoc
136
138
  - doc/virtual_rows.rdoc