sequel 3.6.0 → 3.7.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.
@@ -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