sequel 3.18.0 → 3.19.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 (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