sequel 3.6.0 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -107,11 +107,29 @@ module Sequel
107
107
  db[:dual].where(exists).get(1) == nil
108
108
  end
109
109
 
110
+ # If this dataset is associated with a sequence, return the most recently
111
+ # inserted sequence value.
112
+ def insert(*args)
113
+ r = super
114
+ if s = opts[:sequence]
115
+ with_sql("SELECT #{literal(s)}.currval FROM dual").single_value.to_i
116
+ else
117
+ r
118
+ end
119
+ end
120
+
110
121
  # Oracle requires SQL standard datetimes
111
122
  def requires_sql_standard_datetimes?
112
123
  true
113
124
  end
114
125
 
126
+ # Create a copy of this dataset associated to the given sequence name,
127
+ # which will be used when calling insert to find the most recently
128
+ # inserted value for the sequence.
129
+ def sequence(s)
130
+ clone(:sequence=>s)
131
+ end
132
+
115
133
  # Oracle does not support DISTINCT ON
116
134
  def supports_distinct_on?
117
135
  false
@@ -335,7 +335,7 @@ module Sequel
335
335
  # PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
336
336
  # managing incrementing primary keys.
337
337
  def serial_primary_key_options
338
- {:primary_key => true, :type => :serial}
338
+ {:primary_key => true, :serial => true, :type=>Integer}
339
339
  end
340
340
 
341
341
  # The version of the PostgreSQL server, used for determining capability.
@@ -551,11 +551,21 @@ module Sequel
551
551
  "(#{Array(args).map{|a| Array(a).reverse.join(' ')}.join(', ')})"
552
552
  end
553
553
 
554
+ # Handle bigserial type if :serial option is present
555
+ def type_literal_generic_bignum(column)
556
+ column[:serial] ? :bigserial : super
557
+ end
558
+
554
559
  # PostgreSQL uses the bytea data type for blobs
555
560
  def type_literal_generic_file(column)
556
561
  :bytea
557
562
  end
558
563
 
564
+ # Handle serial type if :serial option is present
565
+ def type_literal_generic_integer(column)
566
+ column[:serial] ? :serial : super
567
+ end
568
+
559
569
  # PostgreSQL prefers the text datatype. If a fixed size is requested,
560
570
  # the char type is used. If the text type is specifically
561
571
  # disallowed or there is a size specified, use the varchar type.
@@ -578,6 +588,7 @@ module Sequel
578
588
  BOOL_FALSE = 'false'.freeze
579
589
  BOOL_TRUE = 'true'.freeze
580
590
  COMMA_SEPARATOR = ', '.freeze
591
+ DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from using where')
581
592
  EXCLUSIVE = 'EXCLUSIVE'.freeze
582
593
  EXPLAIN = 'EXPLAIN '.freeze
583
594
  EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
@@ -595,6 +606,7 @@ module Sequel
595
606
  SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
596
607
  SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
597
608
  SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
609
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set from where')
598
610
 
599
611
  # Shared methods for prepared statements when used with PostgreSQL databases.
600
612
  module PreparedStatementMethods
@@ -696,6 +708,11 @@ module Sequel
696
708
  [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
697
709
  end
698
710
 
711
+ # PostgreSQL supports modifying joined datasets
712
+ def supports_modifying_joins?
713
+ true
714
+ end
715
+
699
716
  # PostgreSQL supports timezones in literal timestamps
700
717
  def supports_timestamp_timezones?
701
718
  true
@@ -713,12 +730,38 @@ module Sequel
713
730
 
714
731
  private
715
732
 
733
+ # PostgreSQL allows deleting from joined datasets
734
+ def delete_clause_methods
735
+ DELETE_CLAUSE_METHODS
736
+ end
737
+
738
+ # Only include the primary table in the main delete clause
739
+ def delete_from_sql(sql)
740
+ sql << " FROM #{source_list(@opts[:from][0..0])}"
741
+ end
742
+
743
+ # Use USING to specify additional tables in a delete query
744
+ def delete_using_sql(sql)
745
+ join_from_sql(:USING, sql)
746
+ end
747
+
716
748
  # Use the RETURNING clause to return the primary key of the inserted record, if it exists
717
749
  def insert_returning_pk_sql(*values)
718
750
  pk = db.primary_key(opts[:from].first) if opts[:from] && !opts[:from].empty?
719
751
  insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : NULL, *values)
720
752
  end
721
753
 
754
+ # For multiple table support, PostgreSQL requires at least
755
+ # two from tables, with joins allowed.
756
+ def join_from_sql(type, sql)
757
+ if(from = @opts[:from][1..-1]).empty?
758
+ raise(Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs') if @opts[:join]
759
+ else
760
+ sql << " #{type} #{source_list(from)}"
761
+ select_join_sql(sql)
762
+ end
763
+ end
764
+
722
765
  # Use a generic blob quoting method, hopefully overridden in one of the subadapter methods
723
766
  def literal_blob(v)
724
767
  "'#{v.gsub(/[\000-\037\047\134\177-\377]/){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"}}'"
@@ -776,6 +819,21 @@ module Sequel
776
819
  cols.pop
777
820
  literal(SQL::StringExpression.new(:'||', *cols))
778
821
  end
822
+
823
+ # PostgreSQL splits the main table from the joined tables
824
+ def update_clause_methods
825
+ UPDATE_CLAUSE_METHODS
826
+ end
827
+
828
+ # Use FROM to specify additional tables in an update query
829
+ def update_from_sql(sql)
830
+ join_from_sql(:FROM, sql)
831
+ end
832
+
833
+ # Only include the primary table in the main update clause
834
+ def update_table_sql(sql)
835
+ sql << " #{source_list(@opts[:from][0..0])}"
836
+ end
779
837
  end
780
838
  end
781
839
  end
@@ -3,8 +3,7 @@ module Sequel
3
3
  COMMA_SEPARATOR = ', '.freeze
4
4
  COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, LiteralString.new('*'.freeze)).as(:count)
5
5
  ARRAY_ACCESS_ERROR_MSG = 'You cannot call Dataset#[] with an integer or with no arguments.'.freeze
6
- MAP_ERROR_MSG = 'Using Dataset#map with an argument and a block is not allowed'.freeze
7
- GET_ERROR_MSG = 'must provide argument or block to Dataset#get, not both'.freeze
6
+ ARG_BLOCK_ERROR_MSG = 'Must use either an argument or a block, not both'.freeze
8
7
  IMPORT_ERROR_MSG = 'Using Sequel::Dataset#import an empty column array is not allowed'.freeze
9
8
 
10
9
  # Returns the first record matching the conditions. Examples:
@@ -74,7 +73,7 @@ module Sequel
74
73
  # ds.get{|o| o.sum(:id)}
75
74
  def get(column=nil, &block)
76
75
  if column
77
- raise(Error, GET_ERROR_MSG) if block
76
+ raise(Error, ARG_BLOCK_ERROR_MSG) if block
78
77
  select(column).single_value
79
78
  else
80
79
  select(&block).single_value
@@ -91,13 +90,9 @@ module Sequel
91
90
  # ds.group_and_count(:first_name, :last_name).all => [{:first_name=>'a', :last_name=>'b', :count=>1}, ...]
92
91
  # ds.group_and_count(:first_name___name).all => [{:name=>'a', :count=>1}, ...]
93
92
  def group_and_count(*columns)
94
- groups = columns.map do |c|
95
- c_table, column, _ = split_symbol(c)
96
- c_table ? column.to_sym.qualify(c_table) : column.to_sym
97
- end
98
- group(*groups).select(*(columns + [COUNT_OF_ALL_AS_COUNT])).order(:count)
93
+ group(*columns.map{|c| unaliased_identifier(c)}).select(*(columns + [COUNT_OF_ALL_AS_COUNT])).order(:count)
99
94
  end
100
-
95
+
101
96
  # Inserts multiple records into the associated table. This method can be
102
97
  # to efficiently insert a large amounts of records into a table. Inserts
103
98
  # are automatically wrapped in a transaction.
@@ -158,7 +153,7 @@ module Sequel
158
153
  # ds.map{|r| r[:id] * 2} => [2, 4, 6, ...]
159
154
  def map(column=nil, &block)
160
155
  if column
161
- raise(Error, MAP_ERROR_MSG) if block
156
+ raise(Error, ARG_BLOCK_ERROR_MSG) if block
162
157
  super(){|r| r[column]}
163
158
  else
164
159
  super(&block)
@@ -199,6 +194,32 @@ module Sequel
199
194
  end
200
195
  end
201
196
 
197
+ def select_hash(key_column, value_column)
198
+ select(key_column, value_column).to_hash(hash_key_symbol(key_column), hash_key_symbol(value_column))
199
+ end
200
+
201
+ def select_map(column=nil, &block)
202
+ ds = naked.ungraphed
203
+ ds = if column
204
+ raise(Error, ARG_BLOCK_ERROR_MSG) if block
205
+ ds.select(column)
206
+ else
207
+ ds.select(&block)
208
+ end
209
+ ds.map{|r| r.values.first}
210
+ end
211
+
212
+ def select_order_map(column=nil, &block)
213
+ ds = naked.ungraphed
214
+ ds = if column
215
+ raise(Error, ARG_BLOCK_ERROR_MSG) if block
216
+ ds.select(column).order(unaliased_identifier(column))
217
+ else
218
+ ds.select(&block).order(&block)
219
+ end
220
+ ds.map{|r| r.values.first}
221
+ end
222
+
202
223
  # Returns the first record in the dataset.
203
224
  def single_record
204
225
  clone(:limit=>1).each{|r| return r}
@@ -208,7 +229,7 @@ module Sequel
208
229
  # Returns the first value of the first record in the dataset.
209
230
  # Returns nil if dataset is empty.
210
231
  def single_value
211
- if r = naked.clone(:graph=>false).single_record
232
+ if r = naked.ungraphed.single_record
212
233
  r.values.first
213
234
  end
214
235
  end
@@ -245,5 +266,31 @@ module Sequel
245
266
  m
246
267
  end
247
268
  end
269
+
270
+ private
271
+
272
+ # Return a plain symbol given a potentially qualified or aliased symbol,
273
+ # specifying the symbol that is likely to be used as the hash key
274
+ # for the column when records are returned.
275
+ def hash_key_symbol(s)
276
+ raise(Error, "#{s.inspect} is not a symbol") unless s.is_a?(Symbol)
277
+ _, c, a = split_symbol(s)
278
+ (a || c).to_sym
279
+ end
280
+
281
+ # Return the unaliased part of the identifier. Handles both
282
+ # implicit aliases in symbols, as well as SQL::AliasedExpression
283
+ # objects. Other objects are returned as is.
284
+ def unaliased_identifier(c)
285
+ case c
286
+ when Symbol
287
+ c_table, column, _ = split_symbol(c)
288
+ c_table ? column.to_sym.qualify(c_table) : column.to_sym
289
+ when SQL::AliasedExpression
290
+ c.expression
291
+ else
292
+ c
293
+ end
294
+ end
248
295
  end
249
296
  end
@@ -41,6 +41,11 @@ module Sequel
41
41
  true
42
42
  end
43
43
 
44
+ # Whether modifying joined datasets is supported.
45
+ def supports_modifying_joins?
46
+ false
47
+ end
48
+
44
49
  # Whether the IN/NOT IN operators support multiple columns when an
45
50
  # array of values is given.
46
51
  def supports_multiple_column_in?
@@ -144,8 +144,8 @@ module Sequel
144
144
  #
145
145
  # ds = DB[:items].order(:name).select(:id, :name)
146
146
  # ds.sql #=> "SELECT id,name FROM items ORDER BY name"
147
- # ds.from_self.sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS 't1'"
148
- # ds.from_self(:alias=>:foo).sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS 'foo'"
147
+ # ds.from_self.sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS t1"
148
+ # ds.from_self(:alias=>:foo).sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo"
149
149
  def from_self(opts={})
150
150
  fs = {}
151
151
  @opts.keys.each{|k| fs[k] = nil unless FROM_SELF_KEEP_OPTS.include?(k)}
@@ -370,7 +370,7 @@ module Sequel
370
370
  # Add the dataset to the list of compounds
371
371
  def compound_clone(type, dataset, opts)
372
372
  ds = compound_from_self.clone(:compounds=>Array(@opts[:compounds]).map{|x| x.dup} + [[type, dataset.compound_from_self, opts[:all]]])
373
- opts[:from_self] == false ? ds : ds.from_self
373
+ opts[:from_self] == false ? ds : ds.from_self(opts)
374
374
  end
375
375
 
376
376
  # SQL fragment based on the expr type. See #filter.
@@ -52,6 +52,11 @@ module Sequel
52
52
  a.empty? ? '(NULL)' : "(#{expression_list(a)})"
53
53
  end
54
54
 
55
+ # SQL fragment for BooleanConstants
56
+ def boolean_constant_sql(constant)
57
+ literal(constant)
58
+ end
59
+
55
60
  # SQL fragment for specifying given CaseExpression.
56
61
  def case_expression_sql(ce)
57
62
  sql = '(CASE '
@@ -386,6 +391,11 @@ module Sequel
386
391
  values.map{|r| insert_sql(columns, r)}
387
392
  end
388
393
 
394
+ # SQL fragment for NegativeBooleanConstants
395
+ def negative_boolean_constant_sql(constant)
396
+ "NOT #{boolean_constant_sql(constant)}"
397
+ end
398
+
389
399
  # SQL fragment for the ordered expression, used in the ORDER BY
390
400
  # clause.
391
401
  def ordered_expression_sql(oe)
@@ -643,7 +653,7 @@ module Sequel
643
653
  # for this dataset
644
654
  def check_modification_allowed!
645
655
  raise(InvalidOperation, "Grouped datasets cannot be modified") if opts[:group]
646
- raise(InvalidOperation, "Joined datasets cannot be modified") if (opts[:from].is_a?(Array) && opts[:from].size > 1) || opts[:join]
656
+ raise(InvalidOperation, "Joined datasets cannot be modified") if !supports_modifying_joins? && joined_dataset?
647
657
  end
648
658
 
649
659
  # Prepare an SQL statement by calling all clause methods for the given statement type.
@@ -754,6 +764,11 @@ module Sequel
754
764
  "#{join_type.to_s.gsub('_', ' ').upcase} JOIN"
755
765
  end
756
766
 
767
+ # Whether this dataset is a joined dataset
768
+ def joined_dataset?
769
+ (opts[:from].is_a?(Array) && opts[:from].size > 1) || opts[:join]
770
+ end
771
+
757
772
  # SQL fragment for Array. Treats as an expression if an array of all two pairs, or as a SQL array otherwise.
758
773
  def literal_array(v)
759
774
  Sequel.condition_specifier?(v) ? literal_expression(SQL::BooleanExpression.from_value_pairs(v)) : array_sql(v)
@@ -1058,9 +1073,11 @@ module Sequel
1058
1073
  UPDATE_CLAUSE_METHODS
1059
1074
  end
1060
1075
 
1061
- # SQL fragment specifying the tables from with to delete
1076
+ # SQL fragment specifying the tables from with to delete.
1077
+ # Includes join table if modifying joins is allowed.
1062
1078
  def update_table_sql(sql)
1063
1079
  sql << " #{source_list(@opts[:from])}"
1080
+ select_join_sql(sql) if supports_modifying_joins?
1064
1081
  end
1065
1082
 
1066
1083
  # The SQL fragment specifying the columns and values to SET.
@@ -86,7 +86,12 @@ END_MIG
86
86
  # name and arguments to it to pass to a Schema::Generator to recreate the column.
87
87
  def column_schema_to_generator_opts(name, schema, options)
88
88
  if options[:single_pk] && schema_autoincrementing_primary_key?(schema)
89
- [:primary_key, name]
89
+ type_hash = column_schema_to_ruby_type(schema)
90
+ if type_hash == {:type=>Integer}
91
+ [:primary_key, name]
92
+ else
93
+ [:primary_key, name, type_hash]
94
+ end
90
95
  else
91
96
  col_opts = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
92
97
  type = col_opts.delete(:type)
@@ -507,7 +507,7 @@ module Sequel
507
507
 
508
508
  class_attr_reader :columns, :db, :primary_key, :db_schema
509
509
  class_attr_overridable :raise_on_save_failure, :raise_on_typecast_failure, :strict_param_setting, :typecast_empty_string_to_nil, :typecast_on_assignment, :use_transactions
510
-
510
+
511
511
  # The hash of attribute values. Keys are symbols with the names of the
512
512
  # underlying database columns.
513
513
  attr_reader :values
@@ -607,9 +607,10 @@ module Sequel
607
607
  # If before_destroy returns false, returns false without
608
608
  # deleting the object the the database. Otherwise, deletes
609
609
  # the item from the database and returns self. Uses a transaction
610
- # if use_transactions is true.
611
- def destroy
612
- use_transactions ? db.transaction{_destroy} : _destroy
610
+ # if use_transactions is true or if the :transaction option is given and
611
+ # true.
612
+ def destroy(opts = {})
613
+ checked_save_failure{checked_transaction(opts){_destroy(opts)}}
613
614
  end
614
615
 
615
616
  # Iterates through all of the current values using each.
@@ -721,9 +722,11 @@ module Sequel
721
722
  # * :validate - set to false not to validate the model before saving
722
723
  def save(*columns)
723
724
  opts = columns.last.is_a?(Hash) ? columns.pop : {}
724
- return save_failure(:invalid) if opts[:validate] != false and !valid?
725
- use_transaction = opts.include?(:transaction) ? opts[:transaction] : use_transactions
726
- use_transaction ? db.transaction(opts){_save(columns, opts)} : _save(columns, opts)
725
+ if opts[:validate] != false and !valid?
726
+ raise(ValidationFailed, errors.full_messages.join(', ')) if raise_on_save_failure
727
+ return
728
+ end
729
+ checked_save_failure{checked_transaction(opts){_save(columns, opts)}}
727
730
  end
728
731
 
729
732
  # Saves only changed columns if the object has been modified.
@@ -797,7 +800,7 @@ module Sequel
797
800
  def valid?
798
801
  errors.clear
799
802
  if before_validation == false
800
- save_failure(:validation)
803
+ save_failure(:validation) if raise_on_save_failure
801
804
  return false
802
805
  end
803
806
  validate
@@ -809,7 +812,7 @@ module Sequel
809
812
 
810
813
  # Internal destroy method, separted from destroy to
811
814
  # allow running inside a transaction
812
- def _destroy
815
+ def _destroy(opts)
813
816
  return save_failure(:destroy) if before_destroy == false
814
817
  delete
815
818
  after_destroy
@@ -880,10 +883,30 @@ module Sequel
880
883
  self
881
884
  end
882
885
 
886
+ # Update this instance's dataset with the supplied column hash.
883
887
  def _update(columns)
884
888
  this.update(columns)
885
889
  end
890
+
891
+ # If raise_on_save_failure is false, check for BeforeHookFailed
892
+ # beind raised by yielding and swallow it.
893
+ def checked_save_failure
894
+ if raise_on_save_failure
895
+ yield
896
+ else
897
+ begin
898
+ yield
899
+ rescue BeforeHookFailed
900
+ nil
901
+ end
902
+ end
903
+ end
886
904
 
905
+ # If transactions should be used, wrap the yield in a transaction block.
906
+ def checked_transaction(opts)
907
+ use_transaction?(opts)? db.transaction(opts){yield} : yield
908
+ end
909
+
887
910
  # Default inspection output for the values hash, overwrite to change what #inspect displays.
888
911
  def inspect_values
889
912
  @values.inspect
@@ -891,13 +914,7 @@ module Sequel
891
914
 
892
915
  # Raise an error if raise_on_save_failure is true, return nil otherwise.
893
916
  def save_failure(type)
894
- if raise_on_save_failure
895
- if type == :invalid
896
- raise ValidationFailed, errors.full_messages.join(', ')
897
- else
898
- raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
899
- end
900
- end
917
+ raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
901
918
  end
902
919
 
903
920
  # Set the columns, filtered by the only and except arrays.
@@ -968,6 +985,13 @@ module Sequel
968
985
  set_restricted(hash, only, except)
969
986
  save_changes
970
987
  end
988
+
989
+ # Whether to use a transaction for this action. If the :transaction
990
+ # option is present in the hash, use that, otherwise, fallback to the
991
+ # object's default (if set), or class's default (if not).
992
+ def use_transaction?(opts = {})
993
+ opts.include?(:transaction) ? opts[:transaction] : use_transactions
994
+ end
971
995
  end
972
996
 
973
997
  # Dataset methods are methods that the model class extends its dataset with in