sequel 4.12.0 → 4.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +64 -0
  3. data/Rakefile +3 -1
  4. data/bin/sequel +13 -5
  5. data/doc/release_notes/4.13.0.txt +169 -0
  6. data/doc/sql.rdoc +3 -3
  7. data/lib/sequel/adapters/do.rb +11 -23
  8. data/lib/sequel/adapters/do/mysql.rb +8 -0
  9. data/lib/sequel/adapters/do/postgres.rb +8 -0
  10. data/lib/sequel/adapters/do/{sqlite.rb → sqlite3.rb} +9 -0
  11. data/lib/sequel/adapters/jdbc.rb +16 -139
  12. data/lib/sequel/adapters/jdbc/as400.rb +9 -0
  13. data/lib/sequel/adapters/jdbc/cubrid.rb +9 -0
  14. data/lib/sequel/adapters/jdbc/db2.rb +9 -0
  15. data/lib/sequel/adapters/jdbc/derby.rb +9 -0
  16. data/lib/sequel/adapters/jdbc/{firebird.rb → firebirdsql.rb} +9 -0
  17. data/lib/sequel/adapters/jdbc/h2.rb +10 -0
  18. data/lib/sequel/adapters/jdbc/hsqldb.rb +9 -0
  19. data/lib/sequel/adapters/jdbc/{informix.rb → informix-sqli.rb} +9 -0
  20. data/lib/sequel/adapters/jdbc/{progress.rb → jdbcprogress.rb} +9 -0
  21. data/lib/sequel/adapters/jdbc/jtds.rb +10 -0
  22. data/lib/sequel/adapters/jdbc/mysql.rb +14 -0
  23. data/lib/sequel/adapters/jdbc/oracle.rb +9 -0
  24. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -0
  25. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +23 -0
  26. data/lib/sequel/adapters/jdbc/sqlite.rb +10 -0
  27. data/lib/sequel/adapters/jdbc/sqlserver.rb +10 -0
  28. data/lib/sequel/adapters/odbc.rb +6 -14
  29. data/lib/sequel/adapters/odbc/db2.rb +9 -0
  30. data/lib/sequel/adapters/odbc/mssql.rb +8 -0
  31. data/lib/sequel/adapters/odbc/progress.rb +8 -0
  32. data/lib/sequel/adapters/oracle.rb +1 -1
  33. data/lib/sequel/adapters/postgres.rb +1 -1
  34. data/lib/sequel/adapters/shared/firebird.rb +8 -1
  35. data/lib/sequel/adapters/shared/mssql.rb +68 -27
  36. data/lib/sequel/adapters/shared/mysql.rb +3 -5
  37. data/lib/sequel/adapters/shared/oracle.rb +17 -3
  38. data/lib/sequel/adapters/shared/postgres.rb +9 -4
  39. data/lib/sequel/adapters/shared/sqlanywhere.rb +6 -6
  40. data/lib/sequel/database/connecting.rb +38 -17
  41. data/lib/sequel/dataset/actions.rb +6 -2
  42. data/lib/sequel/dataset/graph.rb +18 -20
  43. data/lib/sequel/dataset/misc.rb +37 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +1 -2
  45. data/lib/sequel/dataset/query.rb +1 -0
  46. data/lib/sequel/dataset/sql.rb +17 -10
  47. data/lib/sequel/extensions/dataset_source_alias.rb +90 -0
  48. data/lib/sequel/extensions/pg_array.rb +14 -10
  49. data/lib/sequel/extensions/pg_enum.rb +135 -0
  50. data/lib/sequel/extensions/pg_hstore.rb +4 -6
  51. data/lib/sequel/extensions/pg_inet.rb +4 -5
  52. data/lib/sequel/extensions/pg_interval.rb +3 -3
  53. data/lib/sequel/extensions/pg_json.rb +16 -12
  54. data/lib/sequel/extensions/pg_range.rb +5 -3
  55. data/lib/sequel/extensions/pg_row.rb +2 -2
  56. data/lib/sequel/extensions/round_timestamps.rb +52 -0
  57. data/lib/sequel/model.rb +5 -2
  58. data/lib/sequel/model/associations.rb +29 -3
  59. data/lib/sequel/model/base.rb +68 -29
  60. data/lib/sequel/plugins/class_table_inheritance.rb +25 -16
  61. data/lib/sequel/plugins/column_select.rb +57 -0
  62. data/lib/sequel/plugins/composition.rb +14 -16
  63. data/lib/sequel/plugins/dirty.rb +9 -11
  64. data/lib/sequel/plugins/insert_returning_select.rb +70 -0
  65. data/lib/sequel/plugins/instance_filters.rb +7 -9
  66. data/lib/sequel/plugins/lazy_attributes.rb +16 -4
  67. data/lib/sequel/plugins/list.rb +9 -0
  68. data/lib/sequel/plugins/modification_detection.rb +90 -0
  69. data/lib/sequel/plugins/serialization.rb +13 -15
  70. data/lib/sequel/plugins/serialization_modification_detection.rb +9 -9
  71. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  72. data/lib/sequel/plugins/timestamps.rb +6 -6
  73. data/lib/sequel/version.rb +1 -1
  74. data/spec/adapters/mysql_spec.rb +7 -0
  75. data/spec/adapters/postgres_spec.rb +41 -0
  76. data/spec/bin_spec.rb +4 -1
  77. data/spec/core/database_spec.rb +6 -0
  78. data/spec/core/dataset_spec.rb +100 -90
  79. data/spec/core/object_graph_spec.rb +5 -0
  80. data/spec/extensions/class_table_inheritance_spec.rb +18 -13
  81. data/spec/extensions/column_select_spec.rb +108 -0
  82. data/spec/extensions/composition_spec.rb +20 -0
  83. data/spec/extensions/dataset_source_alias_spec.rb +51 -0
  84. data/spec/extensions/insert_returning_select_spec.rb +46 -0
  85. data/spec/extensions/lazy_attributes_spec.rb +24 -20
  86. data/spec/extensions/list_spec.rb +5 -0
  87. data/spec/extensions/modification_detection_spec.rb +80 -0
  88. data/spec/extensions/pg_enum_spec.rb +64 -0
  89. data/spec/extensions/pg_json_spec.rb +7 -13
  90. data/spec/extensions/prepared_statements_spec.rb +6 -4
  91. data/spec/extensions/round_timestamps_spec.rb +43 -0
  92. data/spec/extensions/serialization_modification_detection_spec.rb +10 -1
  93. data/spec/extensions/serialization_spec.rb +18 -0
  94. data/spec/extensions/single_table_inheritance_spec.rb +5 -0
  95. data/spec/extensions/timestamps_spec.rb +6 -0
  96. data/spec/integration/plugin_test.rb +14 -8
  97. data/spec/integration/prepared_statement_test.rb +12 -0
  98. data/spec/model/associations_spec.rb +24 -0
  99. data/spec/model/model_spec.rb +13 -3
  100. data/spec/model/record_spec.rb +24 -1
  101. metadata +22 -6
@@ -59,15 +59,6 @@ module Sequel
59
59
  clear_instance_filters
60
60
  end
61
61
 
62
- # Duplicate internal structures when duplicating model instance.
63
- def dup
64
- ifs = instance_filters.dup
65
- super.instance_eval do
66
- @instance_filters = ifs
67
- self
68
- end
69
- end
70
-
71
62
  # Freeze the instance filters when freezing the object
72
63
  def freeze
73
64
  instance_filters.freeze
@@ -93,6 +84,13 @@ module Sequel
93
84
  end
94
85
  end
95
86
 
87
+ # Duplicate internal structures when duplicating model instance.
88
+ def initialize_copy(other)
89
+ super
90
+ @instance_filters = other.send(:instance_filters).dup
91
+ self
92
+ end
93
+
96
94
  # Lazily initialize the instance filter array.
97
95
  def instance_filters
98
96
  @instance_filters ||= []
@@ -17,6 +17,13 @@ module Sequel
17
17
  #
18
18
  # # You can specify multiple columns to lazily load:
19
19
  # Album.plugin :lazy_attributes, :review, :tracklist
20
+ #
21
+ # Note that by default on databases that supporting RETURNING,
22
+ # using explicit column selections will cause instance creations
23
+ # to use two queries (insert and refresh) instead of a single
24
+ # query using RETURNING. You can use the insert_returning_select
25
+ # plugin to automatically use RETURNING for instance creations
26
+ # for models using the lazy_attributes plugin.
20
27
  module LazyAttributes
21
28
  # Lazy attributes requires the tactical_eager_loading plugin
22
29
  def self.apply(model, *attrs)
@@ -37,7 +44,10 @@ module Sequel
37
44
  # For each attribute given, create an accessor method that allows a lazy
38
45
  # lookup of the attribute. Each attribute should be given as a symbol.
39
46
  def lazy_attributes(*attrs)
40
- set_dataset(dataset.select(*(columns - attrs)))
47
+ unless select = dataset.opts[:select]
48
+ select = dataset.columns.map{|c| Sequel.qualify(dataset.first_source, c)}
49
+ end
50
+ set_dataset(dataset.select(*select.reject{|c| attrs.include?(dataset.send(:_hash_key_symbol, c))}))
41
51
  attrs.each{|a| define_lazy_attribute_getter(a)}
42
52
  end
43
53
 
@@ -66,8 +76,9 @@ module Sequel
66
76
  # the attribute for just the current object. Return the value of
67
77
  # the attribute for the current object.
68
78
  def lazy_attribute_lookup(a)
79
+ selection = Sequel.qualify(model.table_name, a)
69
80
  if frozen?
70
- return this.dup.select(a).get(a)
81
+ return this.dup.get(selection)
71
82
  end
72
83
 
73
84
  if retrieved_with
@@ -75,14 +86,15 @@ module Sequel
75
86
  composite_pk = true if primary_key.is_a?(Array)
76
87
  id_map = {}
77
88
  retrieved_with.each{|o| id_map[o.pk] = o unless o.values.has_key?(a) || o.frozen?}
78
- model.select(*(Array(primary_key) + [a])).filter(primary_key=>id_map.keys).naked.each do |row|
89
+ predicate_key = composite_pk ? primary_key.map{|k| Sequel.qualify(model.table_name, k)} : Sequel.qualify(model.table_name, primary_key)
90
+ model.select(*(Array(primary_key).map{|k| Sequel.qualify(model.table_name, k)} + [selection])).where(predicate_key=>id_map.keys).naked.each do |row|
79
91
  obj = id_map[composite_pk ? row.values_at(*primary_key) : row[primary_key]]
80
92
  if obj && !obj.values.has_key?(a)
81
93
  obj.values[a] = row[a]
82
94
  end
83
95
  end
84
96
  end
85
- values[a] = this.select(a).get(a) unless values.has_key?(a)
97
+ values[a] = this.get(selection) unless values.has_key?(a)
86
98
  values[a]
87
99
  end
88
100
  end
@@ -96,6 +96,15 @@ module Sequel
96
96
  super
97
97
  end
98
98
 
99
+ # When destroying an instance, move all entries after the instance down
100
+ # one position, so that there aren't any gaps
101
+ def after_destroy
102
+ super
103
+
104
+ f = Sequel.expr(position_field)
105
+ list_dataset.where(f > position_value).update(f => f - 1)
106
+ end
107
+
99
108
  # Find the last position in the list containing this instance.
100
109
  def last_position
101
110
  list_dataset.max(position_field).to_i
@@ -0,0 +1,90 @@
1
+ module Sequel
2
+ module Plugins
3
+ # This plugin automatically detects in-place modifications to
4
+ # columns as well as direct modifications of the values hash.
5
+ #
6
+ # class User < Sequel::Model
7
+ # plugin :modification_detection
8
+ # end
9
+ # user = User[1]
10
+ # user.a # => 'a'
11
+ # user.a << 'b'
12
+ # user.save_changes
13
+ # # UPDATE users SET a = 'ab' WHERE (id = 1)
14
+ #
15
+ # Note that for this plugin to work correctly, the column values must
16
+ # correctly implement the #hash method, returning the same value if
17
+ # the object is equal, and a different value if the object is not equal.
18
+ #
19
+ # Note that this plugin causes a performance hit for all retrieved
20
+ # objects, so it shouldn't be used in cases where performance is a
21
+ # primary concern.
22
+ #
23
+ # Usage:
24
+ #
25
+ # # Make all model subclass automatically detect column modifications
26
+ # Sequel::Model.plugin :modification_detection
27
+ #
28
+ # # Make the Album class automatically detect column modifications
29
+ # Album.plugin :modification_detection
30
+ module ModificationDetection
31
+ module ClassMethods
32
+ # Calculate the hashes for all of the column values, so that they
33
+ # can be compared later to determine if the column value has changed.
34
+ def call(_)
35
+ v = super
36
+ v.calculate_values_hashes
37
+ v
38
+ end
39
+ end
40
+
41
+ module InstanceMethods
42
+ # Recalculate the column value hashes after updating.
43
+ def after_update
44
+ super
45
+ recalculate_values_hashes
46
+ end
47
+
48
+ # Calculate the column hash values if they haven't been already calculated.
49
+ def calculate_values_hashes
50
+ @values_hashes || recalculate_values_hashes
51
+ end
52
+
53
+ # Detect which columns have been modified by comparing the cached hash
54
+ # value to the hash of the current value.
55
+ def changed_columns
56
+ cc = super
57
+ changed = []
58
+ v = @values
59
+ if vh = @values_hashes
60
+ (vh.keys - cc).each{|c| changed << c unless v.has_key?(c) && vh[c] == v[c].hash}
61
+ end
62
+ cc + changed
63
+ end
64
+
65
+ private
66
+
67
+ # Recalculate the column value hashes after manually refreshing.
68
+ def _refresh(dataset)
69
+ super
70
+ recalculate_values_hashes
71
+ end
72
+
73
+ # Recalculate the column value hashes after refreshing after saving a new object.
74
+ def _save_refresh
75
+ super
76
+ recalculate_values_hashes
77
+ end
78
+
79
+ # Recalculate the column value hashes, caching them for later use.
80
+ def recalculate_values_hashes
81
+ vh = {}
82
+ @values.each do |k,v|
83
+ vh[k] = v.hash
84
+ end
85
+ @values_hashes = vh.freeze
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -175,26 +175,11 @@ module Sequel
175
175
  end
176
176
 
177
177
  module InstanceMethods
178
- # Serialize deserialized values before saving
179
- def before_save
180
- serialize_deserialized_values
181
- super
182
- end
183
-
184
178
  # Hash of deserialized values, used as a cache.
185
179
  def deserialized_values
186
180
  @deserialized_values ||= {}
187
181
  end
188
182
 
189
- # Freeze the deserialized values
190
- def dup
191
- dv = deserialized_values.dup
192
- super.instance_eval do
193
- @deserialized_values = dv
194
- self
195
- end
196
- end
197
-
198
183
  # Freeze the deserialized values
199
184
  def freeze
200
185
  deserialized_values.freeze
@@ -203,6 +188,12 @@ module Sequel
203
188
 
204
189
  private
205
190
 
191
+ # Serialize deserialized values before saving
192
+ def _before_validation
193
+ serialize_deserialized_values
194
+ super
195
+ end
196
+
206
197
  # Clear any cached deserialized values when doing a manual refresh.
207
198
  def _refresh_set_values(hash)
208
199
  @deserialized_values.clear if @deserialized_values
@@ -218,6 +209,13 @@ module Sequel
218
209
  end
219
210
  end
220
211
 
212
+ # Dup the deserialized values when duping model instance.
213
+ def initialize_copy(other)
214
+ super
215
+ @deserialized_values = other.deserialized_values.dup
216
+ self
217
+ end
218
+
221
219
  # Serialize all deserialized values
222
220
  def serialize_deserialized_values
223
221
  deserialized_values.each{|k,v| @values[k] = serialize_value(k, v)}
@@ -45,15 +45,6 @@ module Sequel
45
45
  cc
46
46
  end
47
47
 
48
- # Duplicate the original deserialized values when duplicating instance.
49
- def dup
50
- o = @original_deserialized_values
51
- super.instance_eval do
52
- @original_deserialized_values = o.dup if o
53
- self
54
- end
55
- end
56
-
57
48
  # Freeze the original deserialized values when freezing the instance.
58
49
  def freeze
59
50
  @original_deserialized_values ||= {}
@@ -63,6 +54,15 @@ module Sequel
63
54
 
64
55
  private
65
56
 
57
+ # Duplicate the original deserialized values when duplicating instance.
58
+ def initialize_copy(other)
59
+ super
60
+ if o = other.instance_variable_get(:@original_deserialized_values)
61
+ @original_deserialized_values = o.dup
62
+ end
63
+ self
64
+ end
65
+
66
66
  # For new objects, serialize any existing deserialized values so that changes can
67
67
  # be detected.
68
68
  def initialize_set(values)
@@ -215,8 +215,10 @@ module Sequel
215
215
  end
216
216
 
217
217
  module InstanceMethods
218
+ private
219
+
218
220
  # Set the sti_key column based on the sti_key_map.
219
- def before_validation
221
+ def _before_validation
220
222
  if new? && !self[model.sti_key]
221
223
  send("#{model.sti_key}=", model.sti_key_chooser.call(self))
222
224
  end
@@ -57,12 +57,6 @@ module Sequel
57
57
  end
58
58
 
59
59
  module InstanceMethods
60
- # Set the create timestamp when creating
61
- def before_validation
62
- set_create_timestamp if new?
63
- super
64
- end
65
-
66
60
  # Set the update timestamp when updating
67
61
  def before_update
68
62
  set_update_timestamp
@@ -71,6 +65,12 @@ module Sequel
71
65
 
72
66
  private
73
67
 
68
+ # Set the create timestamp when creating
69
+ def _before_validation
70
+ set_create_timestamp if new?
71
+ super
72
+ end
73
+
74
74
  # If the object has accessor methods for the create timestamp field, and
75
75
  # the create timestamp value is nil or overwriting it is allowed, set the
76
76
  # create timestamp field to the time given or the current time. If setting
@@ -3,7 +3,7 @@ module Sequel
3
3
  MAJOR = 4
4
4
  # The minor version of Sequel. Bumped for every non-patch level
5
5
  # release, generally around once a month.
6
- MINOR = 12
6
+ MINOR = 13
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
@@ -509,6 +509,13 @@ describe "A MySQL database" do
509
509
  end
510
510
  end
511
511
 
512
+ specify "should correctly format CREATE TABLE statements with foreign keys, when :key != the default (:id)" do
513
+ @db.create_table(:items){primary_key :id; Integer :other_than_id; foreign_key :p_id, :items, :key => :other_than_id, :null => false, :on_delete => :cascade}
514
+ check_sqls do
515
+ @db.sqls.should == ["CREATE TABLE `items` (`id` integer PRIMARY KEY AUTO_INCREMENT, `other_than_id` integer, `p_id` integer NOT NULL, UNIQUE (`other_than_id`), FOREIGN KEY (`p_id`) REFERENCES `items`(`other_than_id`) ON DELETE CASCADE)"]
516
+ end
517
+ end
518
+
512
519
  specify "should correctly format ALTER TABLE statements with foreign keys" do
513
520
  @db.create_table(:items){Integer :id}
514
521
  @db.create_table(:users){primary_key :id}
@@ -3436,3 +3436,44 @@ describe 'pg_static_cache_updater extension' do
3436
3436
  q.pop
3437
3437
  end
3438
3438
  end if DB.adapter_scheme == :postgres && SEQUEL_POSTGRES_USES_PG && DB.server_version >= 90000
3439
+
3440
+ describe 'PostgreSQL enum types' do
3441
+ before(:all) do
3442
+ @db = DB
3443
+ @db.extension :pg_array, :pg_enum
3444
+ @db.create_enum(:test_enum, %w'a b c d')
3445
+
3446
+ @db.create_table!(:test_enumt) do
3447
+ test_enum :t
3448
+ end
3449
+ end
3450
+ after(:all) do
3451
+ @db.drop_table?(:test_enumt)
3452
+ @db.drop_enum(:test_enum)
3453
+ end
3454
+
3455
+ specify "should return correct entries in the schema" do
3456
+ s = @db.schema(:test_enumt)
3457
+ s.first.last[:type].should == :enum
3458
+ s.first.last[:enum_values].should == %w'a b c d'
3459
+ end
3460
+
3461
+ it "should add array parsers for enum values" do
3462
+ @db.get(Sequel.pg_array(%w'a b', :test_enum)).should == %w'a b'
3463
+ end if DB.adapter_scheme == :postgres || DB.adapter_scheme == :jdbc
3464
+
3465
+ it "should set up model typecasting correctly" do
3466
+ c = Class.new(Sequel::Model(:test_enumt))
3467
+ o = c.new
3468
+ o.t = :a
3469
+ o.t.should == 'a'
3470
+ end
3471
+
3472
+ it "should add values to existing enum" do
3473
+ @db.add_enum_value(:test_enum, 'e')
3474
+ @db.add_enum_value(:test_enum, 'f', :after=>'a')
3475
+ @db.add_enum_value(:test_enum, 'g', :before=>'b')
3476
+ @db.add_enum_value(:test_enum, 'a', :if_not_exists=>true) if @db.server_version >= 90300
3477
+ @db.schema(:test_enumt, :reload=>true).first.last[:enum_values].should == %w'a f g b c d e'
3478
+ end if DB.server_version >= 90100
3479
+ end
data/spec/bin_spec.rb CHANGED
@@ -56,6 +56,7 @@ describe "bin/sequel" do
56
56
  bin(:args=>'-c "print DB.tables.inspect"').should == '[]'
57
57
  DB.create_table(:a){Integer :a}
58
58
  bin(:args=>'-c "print DB.tables.inspect"').should == '[:a]'
59
+ bin(:args=>'-v -c "print DB.tables.inspect"').should == "sequel #{Sequel.version}\n[:a]"
59
60
  end
60
61
 
61
62
  it "-C should copy databases" do
@@ -188,7 +189,7 @@ END
188
189
  bin(:args=>'-t -c "lambda{lambda{lambda{raise \'foo\'}.call}.call}.call"', :stderr=>true).count("\n").should > 3
189
190
  end
190
191
 
191
- it "-v should output the Sequel version" do
192
+ it "-v should output the Sequel version and exit if database is not given" do
192
193
  bin(:args=>"-v", :no_conn=>true).should == "sequel #{Sequel.version}\n"
193
194
  end
194
195
 
@@ -201,6 +202,7 @@ END
201
202
  bin(:args=>'-D -d', :stderr=>true).should == "Error: Cannot specify -D and -d together\n"
202
203
  bin(:args=>'-m foo -d', :stderr=>true).should == "Error: Cannot specify -m and -d together\n"
203
204
  bin(:args=>'-S foo -d', :stderr=>true).should == "Error: Cannot specify -S and -d together\n"
205
+ bin(:args=>'-S foo -C', :stderr=>true).should == "Error: Cannot specify -S and -C together\n"
204
206
  end
205
207
 
206
208
  it "should use a mock database if no database is given" do
@@ -243,6 +245,7 @@ END
243
245
  bin(:post=>TMP_FILE).should == '[]'
244
246
  DB.create_table(:a){Integer :a}
245
247
  bin(:post=>TMP_FILE).should == '[:a]'
248
+ bin(:post=>TMP_FILE, :args=>'-v').should == "sequel #{Sequel.version}\n[:a]"
246
249
  end
247
250
 
248
251
  it "should run code provided on stdin" do
@@ -1290,6 +1290,12 @@ describe "A broken adapter (lib is there but the class is not)" do
1290
1290
  end
1291
1291
  end
1292
1292
 
1293
+ describe "Sequel::Database.load_adapter" do
1294
+ specify "should not raise an error if subadapter does not exist" do
1295
+ Sequel::Database.load_adapter(:foo, :subdir=>'bar').should == nil
1296
+ end
1297
+ end
1298
+
1293
1299
  describe "A single threaded database" do
1294
1300
  after do
1295
1301
  Sequel::Database.single_threaded = false
@@ -982,22 +982,17 @@ describe "Dataset#literal" do
982
982
  d.literal(d).should == "(#{d.sql})"
983
983
  end
984
984
 
985
- specify "should literalize Sequel::SQLTime properly" do
986
- t = Sequel::SQLTime.now
987
- s = t.strftime("'%H:%M:%S")
988
- @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
985
+ specify "should literalize times properly" do
986
+ @dataset.literal(Sequel::SQLTime.create(1, 2, 3, 500000)).should == "'01:02:03.500000'"
987
+ @dataset.literal(Time.local(2010, 1, 2, 3, 4, 5, 500000)).should == "'2010-01-02 03:04:05.500000'"
988
+ @dataset.literal(DateTime.new(2010, 1, 2, 3, 4, Rational(55, 10))).should == "'2010-01-02 03:04:05.500000'"
989
989
  end
990
990
 
991
- specify "should literalize Time properly" do
992
- t = Time.now
993
- s = t.strftime("'%Y-%m-%d %H:%M:%S")
994
- @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
995
- end
996
-
997
- specify "should literalize DateTime properly" do
998
- t = DateTime.now
999
- s = t.strftime("'%Y-%m-%d %H:%M:%S")
1000
- @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.sec_fraction * (RUBY_VERSION < '1.9.0' ? 86400000000 : 1000000))}'"
991
+ specify "should literalize times properly for databases supporting millisecond precision" do
992
+ meta_def(@dataset, :timestamp_precision){3}
993
+ @dataset.literal(Sequel::SQLTime.create(1, 2, 3, 500000)).should == "'01:02:03.500'"
994
+ @dataset.literal(Time.local(2010, 1, 2, 3, 4, 5, 500000)).should == "'2010-01-02 03:04:05.500'"
995
+ @dataset.literal(DateTime.new(2010, 1, 2, 3, 4, Rational(55, 10))).should == "'2010-01-02 03:04:05.500'"
1001
996
  end
1002
997
 
1003
998
  specify "should literalize Date properly" do
@@ -1015,52 +1010,19 @@ describe "Dataset#literal" do
1015
1010
 
1016
1011
  specify "should literalize Time, DateTime, Date properly if SQL standard format is required" do
1017
1012
  meta_def(@dataset, :requires_sql_standard_datetimes?){true}
1018
-
1019
- t = Time.now
1020
- s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S")
1021
- @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}'"
1022
-
1023
- t = DateTime.now
1024
- s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S")
1025
- @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.sec_fraction* (RUBY_VERSION < '1.9.0' ? 86400000000 : 1000000))}'"
1026
-
1027
- d = Date.today
1028
- s = d.strftime("DATE '%Y-%m-%d'")
1029
- @dataset.literal(d).should == s
1013
+ @dataset.literal(Time.local(2010, 1, 2, 3, 4, 5, 500000)).should == "TIMESTAMP '2010-01-02 03:04:05.500000'"
1014
+ @dataset.literal(DateTime.new(2010, 1, 2, 3, 4, Rational(55, 10))).should == "TIMESTAMP '2010-01-02 03:04:05.500000'"
1015
+ @dataset.literal(Date.new(2010, 1, 2)).should == "DATE '2010-01-02'"
1030
1016
  end
1031
1017
 
1032
1018
  specify "should literalize Time and DateTime properly if the database support timezones in timestamps" do
1033
1019
  meta_def(@dataset, :supports_timestamp_timezones?){true}
1020
+ @dataset.literal(Time.utc(2010, 1, 2, 3, 4, 5, 500000)).should == "'2010-01-02 03:04:05.500000+0000'"
1021
+ @dataset.literal(DateTime.new(2010, 1, 2, 3, 4, Rational(55, 10))).should == "'2010-01-02 03:04:05.500000+0000'"
1034
1022
 
1035
- t = Time.now.utc
1036
- s = t.strftime("'%Y-%m-%d %H:%M:%S")
1037
- @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.usec)}+0000'"
1038
-
1039
- t = DateTime.now.new_offset(0)
1040
- s = t.strftime("'%Y-%m-%d %H:%M:%S")
1041
- @dataset.literal(t).should == "#{s}.#{sprintf('%06i', t.sec_fraction* (RUBY_VERSION < '1.9.0' ? 86400000000 : 1000000))}+0000'"
1042
- end
1043
-
1044
- specify "should literalize Time and DateTime properly if the database doesn't support usecs in timestamps" do
1045
1023
  meta_def(@dataset, :supports_timestamp_usecs?){false}
1046
-
1047
- t = Time.now.utc
1048
- s = t.strftime("'%Y-%m-%d %H:%M:%S")
1049
- @dataset.literal(t).should == "#{s}'"
1050
-
1051
- t = DateTime.now.new_offset(0)
1052
- s = t.strftime("'%Y-%m-%d %H:%M:%S")
1053
- @dataset.literal(t).should == "#{s}'"
1054
-
1055
- meta_def(@dataset, :supports_timestamp_timezones?){true}
1056
-
1057
- t = Time.now.utc
1058
- s = t.strftime("'%Y-%m-%d %H:%M:%S")
1059
- @dataset.literal(t).should == "#{s}+0000'"
1060
-
1061
- t = DateTime.now.new_offset(0)
1062
- s = t.strftime("'%Y-%m-%d %H:%M:%S")
1063
- @dataset.literal(t).should == "#{s}+0000'"
1024
+ @dataset.literal(Time.utc(2010, 1, 2, 3, 4, 5)).should == "'2010-01-02 03:04:05+0000'"
1025
+ @dataset.literal(DateTime.new(2010, 1, 2, 3, 4, 5)).should == "'2010-01-02 03:04:05+0000'"
1064
1026
  end
1065
1027
 
1066
1028
  specify "should not modify literal strings" do
@@ -3249,6 +3211,11 @@ describe "Dataset#insert_sql" do
3249
3211
  specify "should accept an array of columns and an LiteralString" do
3250
3212
  @ds.insert_sql([:a, :b, :c], Sequel.lit('VALUES (1, 2, 3)')).should == "INSERT INTO items (a, b, c) VALUES (1, 2, 3)"
3251
3213
  end
3214
+
3215
+ specify "should use unaliased table name" do
3216
+ @ds.from(:items___i).insert_sql(1).should == "INSERT INTO items VALUES (1)"
3217
+ @ds.from(Sequel.as(:items, :i)).insert_sql(1).should == "INSERT INTO items VALUES (1)"
3218
+ end
3252
3219
  end
3253
3220
 
3254
3221
  describe "Dataset#inspect" do
@@ -3457,7 +3424,7 @@ describe "Dataset prepared statements and bound variables " do
3457
3424
  before do
3458
3425
  @db = Sequel.mock
3459
3426
  @ds = @db[:items]
3460
- meta_def(@ds, :insert_sql){|*v| "#{super(*v)}#{' RETURNING *' if opts.has_key?(:returning)}" }
3427
+ meta_def(@ds, :insert_select_sql){|*v| "#{insert_sql(*v)} RETURNING *" }
3461
3428
  end
3462
3429
 
3463
3430
  specify "#call should take a type and bind hash and interpolate it" do
@@ -3935,7 +3902,12 @@ describe "Sequel timezone support" do
3935
3902
  @dataset = @db.dataset
3936
3903
  meta_def(@dataset, :supports_timestamp_timezones?){true}
3937
3904
  meta_def(@dataset, :supports_timestamp_usecs?){false}
3938
- @offset = sprintf("%+03i%02i", *(Time.now.utc_offset/60).divmod(60))
3905
+ @utc_time = Time.utc(2010, 1, 2, 3, 4, 5)
3906
+ @local_time = Time.local(2010, 1, 2, 3, 4, 5)
3907
+ @offset = sprintf("%+03i%02i", *(@local_time.utc_offset/60).divmod(60))
3908
+ @dt_offset = @local_time.utc_offset/Rational(86400, 1)
3909
+ @utc_datetime = DateTime.new(2010, 1, 2, 3, 4, 5)
3910
+ @local_datetime = DateTime.new(2010, 1, 2, 3, 4, 5, @dt_offset)
3939
3911
  end
3940
3912
  after do
3941
3913
  Sequel.default_timezone = nil
@@ -3944,50 +3916,26 @@ describe "Sequel timezone support" do
3944
3916
 
3945
3917
  specify "should handle an database timezone of :utc when literalizing values" do
3946
3918
  Sequel.database_timezone = :utc
3947
-
3948
- t = Time.now
3949
- s = t.getutc.strftime("'%Y-%m-%d %H:%M:%S")
3950
- @dataset.literal(t).should == "#{s}+0000'"
3951
-
3952
- t = DateTime.now
3953
- s = t.new_offset(0).strftime("'%Y-%m-%d %H:%M:%S")
3954
- @dataset.literal(t).should == "#{s}+0000'"
3919
+ @dataset.literal(Time.utc(2010, 1, 2, 3, 4, 5)).should == "'2010-01-02 03:04:05+0000'"
3920
+ @dataset.literal(DateTime.new(2010, 1, 2, 3, 4, 5)).should == "'2010-01-02 03:04:05+0000'"
3955
3921
  end
3956
3922
 
3957
3923
  specify "should handle an database timezone of :local when literalizing values" do
3958
3924
  Sequel.database_timezone = :local
3959
-
3960
- t = Time.now.utc
3961
- s = t.getlocal.strftime("'%Y-%m-%d %H:%M:%S")
3962
- @dataset.literal(t).should == "#{s}#{@offset}'"
3963
-
3964
- t = DateTime.now.new_offset(0)
3965
- s = t.new_offset(DateTime.now.offset).strftime("'%Y-%m-%d %H:%M:%S")
3966
- @dataset.literal(t).should == "#{s}#{@offset}'"
3925
+ @dataset.literal(Time.local(2010, 1, 2, 3, 4, 5)).should == "'2010-01-02 03:04:05#{@offset}'"
3926
+ @dataset.literal(DateTime.new(2010, 1, 2, 3, 4, 5, @dt_offset)).should == "'2010-01-02 03:04:05#{@offset}'"
3967
3927
  end
3968
3928
 
3969
3929
  specify "should have Database#timezone override Sequel.database_timezone" do
3970
3930
  Sequel.database_timezone = :local
3971
3931
  @db.timezone = :utc
3972
-
3973
- t = Time.now
3974
- s = t.getutc.strftime("'%Y-%m-%d %H:%M:%S")
3975
- @dataset.literal(t).should == "#{s}+0000'"
3976
-
3977
- t = DateTime.now
3978
- s = t.new_offset(0).strftime("'%Y-%m-%d %H:%M:%S")
3979
- @dataset.literal(t).should == "#{s}+0000'"
3932
+ @dataset.literal(Time.utc(2010, 1, 2, 3, 4, 5)).should == "'2010-01-02 03:04:05+0000'"
3933
+ @dataset.literal(DateTime.new(2010, 1, 2, 3, 4, 5)).should == "'2010-01-02 03:04:05+0000'"
3980
3934
 
3981
3935
  Sequel.database_timezone = :utc
3982
3936
  @db.timezone = :local
3983
-
3984
- t = Time.now.utc
3985
- s = t.getlocal.strftime("'%Y-%m-%d %H:%M:%S")
3986
- @dataset.literal(t).should == "#{s}#{@offset}'"
3987
-
3988
- t = DateTime.now.new_offset(0)
3989
- s = t.new_offset(DateTime.now.offset).strftime("'%Y-%m-%d %H:%M:%S")
3990
- @dataset.literal(t).should == "#{s}#{@offset}'"
3937
+ @dataset.literal(Time.local(2010, 1, 2, 3, 4, 5)).should == "'2010-01-02 03:04:05#{@offset}'"
3938
+ @dataset.literal(DateTime.new(2010, 1, 2, 3, 4, 5, @dt_offset)).should == "'2010-01-02 03:04:05#{@offset}'"
3991
3939
  end
3992
3940
 
3993
3941
  specify "should handle converting database timestamps into application timestamps" do
@@ -4418,9 +4366,10 @@ end
4418
4366
 
4419
4367
  describe "Dataset#returning" do
4420
4368
  before do
4421
- @ds = Sequel.mock(:fetch=>proc{|s| {:foo=>s}})[:t].returning(:foo)
4369
+ @db = Sequel.mock(:fetch=>proc{|s| {:foo=>s}})
4370
+ @db.extend_datasets{def supports_returning?(type) true end}
4371
+ @ds = @db[:t].returning(:foo)
4422
4372
  @pr = proc do
4423
- def @ds.supports_returning?(*) true end
4424
4373
  sc = class << @ds; self; end
4425
4374
  Sequel::Dataset.def_sql_method(sc, :delete, %w'delete from where returning')
4426
4375
  Sequel::Dataset.def_sql_method(sc, :insert, %w'insert into columns values returning')
@@ -4458,6 +4407,11 @@ describe "Dataset#returning" do
4458
4407
  @ds.insert(1).should == [{:foo=>"INSERT INTO t VALUES (1) RETURNING foo"}]
4459
4408
  @ds.update(:foo=>1).should == [{:foo=>"UPDATE t SET foo = 1 RETURNING foo"}]
4460
4409
  end
4410
+
4411
+ specify "should raise an error if RETURNING is not supported" do
4412
+ @db.extend_datasets{def supports_returning?(type) false end}
4413
+ proc{@db[:t].returning}.should raise_error(Sequel::Error)
4414
+ end
4461
4415
  end
4462
4416
 
4463
4417
  describe "Dataset emulating bitwise operator support" do
@@ -4943,3 +4897,59 @@ describe "Dataset emulated complex expression operators" do
4943
4897
  @ds.literal(~@n).should == "((0 - x) - 1)"
4944
4898
  end
4945
4899
  end
4900
+
4901
+ describe "#joined_dataset?" do
4902
+ before do
4903
+ @ds = Sequel.mock.dataset
4904
+ end
4905
+
4906
+ it "should be false if the dataset has 0 or 1 from table" do
4907
+ @ds.joined_dataset?.should == false
4908
+ @ds.from(:a).joined_dataset?.should == false
4909
+ end
4910
+
4911
+ it "should be true if the dataset has 2 or more from tables" do
4912
+ @ds.from(:a, :b).joined_dataset?.should == true
4913
+ @ds.from(:a, :b, :c).joined_dataset?.should == true
4914
+ end
4915
+
4916
+ it "should be true if the dataset has any join tables" do
4917
+ @ds.from(:a).cross_join(:b).joined_dataset?.should == true
4918
+ end
4919
+ end
4920
+
4921
+ describe "#unqualified_column_for" do
4922
+ before do
4923
+ @ds = Sequel.mock.dataset
4924
+ end
4925
+
4926
+ it "should handle Symbols" do
4927
+ @ds.unqualified_column_for(:a).should == Sequel.identifier('a')
4928
+ @ds.unqualified_column_for(:b__a).should == Sequel.identifier('a')
4929
+ @ds.unqualified_column_for(:a___c).should == Sequel.identifier('a').as('c')
4930
+ @ds.unqualified_column_for(:b__a___c).should == Sequel.identifier('a').as('c')
4931
+ end
4932
+
4933
+ it "should handle SQL::Identifiers" do
4934
+ @ds.unqualified_column_for(Sequel.identifier(:a)).should == Sequel.identifier(:a)
4935
+ end
4936
+
4937
+ it "should handle SQL::QualifiedIdentifiers" do
4938
+ @ds.unqualified_column_for(Sequel.qualify(:b, :a)).should == Sequel.identifier('a')
4939
+ @ds.unqualified_column_for(Sequel.qualify(:b, 'a')).should == Sequel.identifier('a')
4940
+ end
4941
+
4942
+ it "should handle SQL::AliasedExpressions" do
4943
+ @ds.unqualified_column_for(Sequel.qualify(:b, :a).as(:c)).should == Sequel.identifier('a').as(:c)
4944
+ end
4945
+
4946
+ it "should return nil for other objects" do
4947
+ @ds.unqualified_column_for(Object.new).should == nil
4948
+ @ds.unqualified_column_for('a').should == nil
4949
+ end
4950
+
4951
+ it "should return nil for other objects inside SQL::AliasedExpressions" do
4952
+ @ds.unqualified_column_for(Sequel.as(Object.new, 'a')).should == nil
4953
+ @ds.unqualified_column_for(Sequel.as('a', 'b')).should == nil
4954
+ end
4955
+ end