sequel 4.12.0 → 4.13.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 (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