sequel 3.36.1 → 3.37.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 (108) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +13 -0
  3. data/bin/sequel +12 -16
  4. data/doc/advanced_associations.rdoc +36 -67
  5. data/doc/association_basics.rdoc +11 -16
  6. data/doc/release_notes/3.37.0.txt +338 -0
  7. data/doc/schema_modification.rdoc +4 -0
  8. data/lib/sequel/adapters/jdbc/h2.rb +1 -1
  9. data/lib/sequel/adapters/jdbc/postgresql.rb +26 -8
  10. data/lib/sequel/adapters/mysql2.rb +4 -3
  11. data/lib/sequel/adapters/odbc/mssql.rb +2 -2
  12. data/lib/sequel/adapters/postgres.rb +4 -60
  13. data/lib/sequel/adapters/shared/mssql.rb +2 -1
  14. data/lib/sequel/adapters/shared/mysql.rb +0 -5
  15. data/lib/sequel/adapters/shared/postgres.rb +68 -2
  16. data/lib/sequel/adapters/shared/sqlite.rb +17 -1
  17. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +12 -1
  18. data/lib/sequel/adapters/utils/pg_types.rb +76 -0
  19. data/lib/sequel/core.rb +13 -0
  20. data/lib/sequel/database/misc.rb +41 -1
  21. data/lib/sequel/database/schema_generator.rb +23 -10
  22. data/lib/sequel/database/schema_methods.rb +26 -4
  23. data/lib/sequel/dataset/graph.rb +2 -1
  24. data/lib/sequel/dataset/query.rb +62 -2
  25. data/lib/sequel/extensions/_pretty_table.rb +7 -3
  26. data/lib/sequel/extensions/arbitrary_servers.rb +5 -4
  27. data/lib/sequel/extensions/blank.rb +4 -0
  28. data/lib/sequel/extensions/columns_introspection.rb +13 -2
  29. data/lib/sequel/extensions/core_extensions.rb +6 -0
  30. data/lib/sequel/extensions/eval_inspect.rb +158 -0
  31. data/lib/sequel/extensions/inflector.rb +4 -0
  32. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  33. data/lib/sequel/extensions/migration.rb +4 -1
  34. data/lib/sequel/extensions/named_timezones.rb +4 -0
  35. data/lib/sequel/extensions/null_dataset.rb +4 -0
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pg_array.rb +219 -168
  38. data/lib/sequel/extensions/pg_array_ops.rb +7 -2
  39. data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -4
  40. data/lib/sequel/extensions/pg_hstore.rb +3 -1
  41. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -2
  42. data/lib/sequel/extensions/pg_inet.rb +28 -3
  43. data/lib/sequel/extensions/pg_interval.rb +192 -0
  44. data/lib/sequel/extensions/pg_json.rb +21 -9
  45. data/lib/sequel/extensions/pg_range.rb +487 -0
  46. data/lib/sequel/extensions/pg_range_ops.rb +122 -0
  47. data/lib/sequel/extensions/pg_statement_cache.rb +3 -2
  48. data/lib/sequel/extensions/pretty_table.rb +12 -1
  49. data/lib/sequel/extensions/query.rb +4 -0
  50. data/lib/sequel/extensions/query_literals.rb +6 -6
  51. data/lib/sequel/extensions/schema_dumper.rb +39 -38
  52. data/lib/sequel/extensions/select_remove.rb +4 -0
  53. data/lib/sequel/extensions/server_block.rb +3 -2
  54. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  55. data/lib/sequel/extensions/sql_expr.rb +4 -0
  56. data/lib/sequel/extensions/string_date_time.rb +4 -0
  57. data/lib/sequel/extensions/thread_local_timezones.rb +9 -3
  58. data/lib/sequel/extensions/to_dot.rb +4 -0
  59. data/lib/sequel/model/associations.rb +150 -91
  60. data/lib/sequel/plugins/identity_map.rb +2 -2
  61. data/lib/sequel/plugins/list.rb +1 -0
  62. data/lib/sequel/plugins/many_through_many.rb +33 -32
  63. data/lib/sequel/plugins/nested_attributes.rb +11 -3
  64. data/lib/sequel/plugins/rcte_tree.rb +2 -2
  65. data/lib/sequel/plugins/schema.rb +1 -1
  66. data/lib/sequel/sql.rb +14 -14
  67. data/lib/sequel/version.rb +2 -2
  68. data/spec/adapters/mysql_spec.rb +25 -0
  69. data/spec/adapters/postgres_spec.rb +572 -28
  70. data/spec/adapters/sqlite_spec.rb +16 -1
  71. data/spec/core/database_spec.rb +61 -2
  72. data/spec/core/dataset_spec.rb +92 -0
  73. data/spec/core/expression_filters_spec.rb +12 -0
  74. data/spec/extensions/arbitrary_servers_spec.rb +1 -1
  75. data/spec/extensions/boolean_readers_spec.rb +25 -25
  76. data/spec/extensions/eval_inspect_spec.rb +58 -0
  77. data/spec/extensions/json_serializer_spec.rb +0 -6
  78. data/spec/extensions/list_spec.rb +1 -1
  79. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  80. data/spec/extensions/many_through_many_spec.rb +81 -0
  81. data/spec/extensions/nested_attributes_spec.rb +21 -4
  82. data/spec/extensions/pg_array_ops_spec.rb +1 -11
  83. data/spec/extensions/pg_array_spec.rb +181 -90
  84. data/spec/extensions/pg_auto_parameterize_spec.rb +3 -3
  85. data/spec/extensions/pg_hstore_spec.rb +1 -3
  86. data/spec/extensions/pg_inet_spec.rb +6 -1
  87. data/spec/extensions/pg_interval_spec.rb +73 -0
  88. data/spec/extensions/pg_json_spec.rb +5 -9
  89. data/spec/extensions/pg_range_ops_spec.rb +49 -0
  90. data/spec/extensions/pg_range_spec.rb +372 -0
  91. data/spec/extensions/pg_statement_cache_spec.rb +1 -2
  92. data/spec/extensions/query_literals_spec.rb +1 -2
  93. data/spec/extensions/schema_dumper_spec.rb +48 -89
  94. data/spec/extensions/serialization_spec.rb +1 -5
  95. data/spec/extensions/server_block_spec.rb +2 -2
  96. data/spec/extensions/spec_helper.rb +12 -2
  97. data/spec/extensions/split_array_nil_spec.rb +24 -0
  98. data/spec/integration/associations_test.rb +4 -4
  99. data/spec/integration/database_test.rb +2 -2
  100. data/spec/integration/dataset_test.rb +4 -4
  101. data/spec/integration/eager_loader_test.rb +6 -6
  102. data/spec/integration/plugin_test.rb +2 -2
  103. data/spec/integration/spec_helper.rb +2 -2
  104. data/spec/model/association_reflection_spec.rb +5 -0
  105. data/spec/model/associations_spec.rb +156 -49
  106. data/spec/model/eager_loading_spec.rb +137 -2
  107. data/spec/model/model_spec.rb +10 -10
  108. metadata +15 -2
@@ -98,6 +98,10 @@ You've seen this one used already. It's used to create an autoincrementing inte
98
98
 
99
99
  create_table(:a0){primary_key :id}
100
100
 
101
+ If you want an autoincrementing 64-bit integer:
102
+
103
+ create_table(:a0){primary_key :id, :type=>Bignum}
104
+
101
105
  If you want to create a primary key column that doesn't use an autoincrementing integer, you should
102
106
  not use this method. Instead, you should use the :primary_key option to the +column+ method or type
103
107
  method:
@@ -112,7 +112,7 @@ module Sequel
112
112
 
113
113
  # Treat clob as string instead of blob
114
114
  def schema_column_type(db_type)
115
- db_type == 'clob' ? :string : super
115
+ db_type.downcase == 'clob' ? :string : super
116
116
  end
117
117
 
118
118
  # Use BIGINT IDENTITY for identity columns that use bigint, fixes
@@ -45,24 +45,43 @@ module Sequel
45
45
  APOS = Dataset::APOS
46
46
 
47
47
  class ::Sequel::JDBC::Dataset::TYPE_TRANSLATOR
48
- # Convert Java::OrgPostgresqlJdbc4::Jdbc4Array to ruby arrays
49
- def pg_array(v)
50
- _pg_array(v.array)
51
- end
52
-
53
48
  # Convert Java::OrgPostgresqlUtil::PGobject to ruby strings
54
49
  def pg_object(v)
55
50
  v.to_string
56
51
  end
52
+ end
53
+
54
+ # Handle conversions of PostgreSQL array instances
55
+ class PGArrayConverter
56
+ # Set the method that will return the correct conversion
57
+ # proc for elements of this array.
58
+ def initialize(meth)
59
+ @conversion_proc_method = meth
60
+ @conversion_proc = nil
61
+ end
62
+
63
+ # Convert Java::OrgPostgresqlJdbc4::Jdbc4Array to ruby arrays
64
+ def call(v)
65
+ _pg_array(v.array)
66
+ end
57
67
 
58
68
  private
59
69
 
60
70
  # Handle multi-dimensional Java arrays by recursively mapping them
61
- # to ruby arrays.
71
+ # to ruby arrays of ruby values.
62
72
  def _pg_array(v)
63
73
  v.to_ary.map do |i|
64
74
  if i.respond_to?(:to_ary)
65
75
  _pg_array(i)
76
+ elsif i
77
+ if @conversion_proc.nil?
78
+ @conversion_proc = @conversion_proc_method.call(i)
79
+ end
80
+ if @conversion_proc
81
+ @conversion_proc.call(i)
82
+ else
83
+ i
84
+ end
66
85
  else
67
86
  i
68
87
  end
@@ -70,7 +89,6 @@ module Sequel
70
89
  end
71
90
  end
72
91
 
73
- PG_ARRAY_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:pg_array)
74
92
  PG_OBJECT_METHOD = TYPE_TRANSLATOR_INSTANCE.method(:pg_object)
75
93
 
76
94
  # Add the shared PostgreSQL prepared statement methods
@@ -88,7 +106,7 @@ module Sequel
88
106
  def convert_type_proc(v)
89
107
  case v
90
108
  when Java::OrgPostgresqlJdbc4::Jdbc4Array
91
- PG_ARRAY_METHOD
109
+ PGArrayConverter.new(method(:convert_type_proc))
92
110
  when Java::OrgPostgresqlUtil::PGobject
93
111
  PG_OBJECT_METHOD
94
112
  else
@@ -47,6 +47,7 @@ module Sequel
47
47
  opts[:username] ||= opts[:user]
48
48
  opts[:flags] = ::Mysql2::Client::FOUND_ROWS if ::Mysql2::Client.const_defined?(:FOUND_ROWS)
49
49
  conn = ::Mysql2::Client.new(opts)
50
+ conn.query_options.merge!(:symbolize_keys=>true, :cache_rows=>false)
50
51
 
51
52
  sqls = mysql_connection_setting_sqls
52
53
 
@@ -86,7 +87,7 @@ module Sequel
86
87
  # yield the connection if a block is given.
87
88
  def _execute(conn, sql, opts)
88
89
  begin
89
- r = log_yield((log_sql = opts[:log_sql]) ? sql + log_sql : sql){conn.query(sql, :symbolize_keys => true, :database_timezone => timezone, :application_timezone => Sequel.application_timezone)}
90
+ r = log_yield((log_sql = opts[:log_sql]) ? sql + log_sql : sql){conn.query(sql, :database_timezone => timezone, :application_timezone => Sequel.application_timezone, :cast_booleans => convert_tinyint_to_bool)}
90
91
  if opts[:type] == :select
91
92
  yield r if r
92
93
  elsif block_given?
@@ -149,7 +150,7 @@ module Sequel
149
150
  cols = r.fields
150
151
  @columns = cols2 = cols.map{|c| output_identifier(c.to_s)}
151
152
  cs = cols.zip(cols2)
152
- r.each(:cast_booleans => db.convert_tinyint_to_bool) do |row|
153
+ r.each do |row|
153
154
  h = {}
154
155
  cs.each do |a, b|
155
156
  h[b] = row[a]
@@ -158,7 +159,7 @@ module Sequel
158
159
  end
159
160
  else
160
161
  @columns = r.fields
161
- r.each(:cast_booleans => db.convert_tinyint_to_bool){|h| yield h}
162
+ r.each{|h| yield h}
162
163
  end
163
164
  end
164
165
  self
@@ -16,8 +16,8 @@ module Sequel
16
16
  log_yield(sql){conn.do(sql)}
17
17
  begin
18
18
  s = log_yield(LAST_INSERT_ID_SQL){conn.run(LAST_INSERT_ID_SQL)}
19
- if (rows = s.fetch_all) and (row = rows.first)
20
- Integer(row.first)
19
+ if (rows = s.fetch_all) and (row = rows.first) and (v = row.first)
20
+ Integer(v)
21
21
  end
22
22
  ensure
23
23
  s.drop if s
@@ -1,4 +1,5 @@
1
1
  Sequel.require 'adapters/shared/postgres'
2
+ Sequel.require 'adapters/utils/pg_types'
2
3
 
3
4
  begin
4
5
  require 'pg'
@@ -87,68 +88,11 @@ module Sequel
87
88
  Dataset::NON_SQL_OPTIONS << :cursor
88
89
  module Postgres
89
90
  CONVERTED_EXCEPTIONS << PGError
90
-
91
- NAN = 0.0/0.0
92
- PLUS_INFINITY = 1.0/0.0
93
- MINUS_INFINITY = -1.0/0.0
94
- NAN_STR = 'NaN'.freeze
95
- PLUS_INFINITY_STR = 'Infinity'.freeze
96
- MINUS_INFINITY_STR = '-Infinity'.freeze
97
- TRUE_STR = 't'.freeze
98
- DASH_STR = '-'.freeze
99
-
100
- TYPE_TRANSLATOR = tt = Class.new do
101
- def boolean(s) s == TRUE_STR end
91
+
92
+ PG_TYPES[17] = Class.new do
102
93
  def bytea(s) ::Sequel::SQL::Blob.new(Adapter.unescape_bytea(s)) end
103
- def integer(s) s.to_i end
104
- def float(s)
105
- case s
106
- when NAN_STR
107
- NAN
108
- when PLUS_INFINITY_STR
109
- PLUS_INFINITY
110
- when MINUS_INFINITY_STR
111
- MINUS_INFINITY
112
- else
113
- s.to_f
114
- end
115
- end
116
- def date(s) ::Date.new(*s.split(DASH_STR).map{|x| x.to_i}) end
117
- end.new
118
-
119
- # Hash with type name strings/symbols and callable values for converting PostgreSQL types.
120
- # Non-builtin types that don't have fixed numbers should use this to register
121
- # conversion procs.
122
- PG_NAMED_TYPES = {} unless defined?(PG_NAMED_TYPES)
123
-
124
- # Hash with integer keys and callable values for converting PostgreSQL types.
125
- PG_TYPES = {} unless defined?(PG_TYPES)
126
-
127
- {
128
- [16] => tt.method(:boolean),
129
- [17] => tt.method(:bytea),
130
- [20, 21, 22, 23, 26] => tt.method(:integer),
131
- [700, 701] => tt.method(:float),
132
- [790, 1700] => ::BigDecimal.method(:new),
133
- [1083, 1266] => ::Sequel.method(:string_to_time),
134
- }.each do |k,v|
135
- k.each{|n| PG_TYPES[n] = v}
136
- end
137
-
138
- class << self
139
- # As an optimization, Sequel sets the date style to ISO, so that PostgreSQL provides
140
- # the date in a known format that Sequel can parse faster. This can be turned off
141
- # if you require a date style other than ISO.
142
- attr_reader :use_iso_date_format
143
- end
94
+ end.new.method(:bytea)
144
95
 
145
- # Modify the type translator for the date type depending on the value given.
146
- def self.use_iso_date_format=(v)
147
- PG_TYPES[1082] = v ? TYPE_TRANSLATOR.method(:date) : Sequel.method(:string_to_date)
148
- @use_iso_date_format = v
149
- end
150
- self.use_iso_date_format = true
151
-
152
96
  # PGconn subclass for connection specific methods used with the
153
97
  # pg, postgres, or postgres-pr driver.
154
98
  class Adapter < ::PGconn
@@ -316,7 +316,7 @@ module Sequel
316
316
  DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with delete from output from2 where')
317
317
  INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with insert into columns output values')
318
318
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns into from lock join where group having order compounds')
319
- UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with update table set output from where')
319
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with update limit table set output from where')
320
320
  NOLOCK = ' WITH (NOLOCK)'.freeze
321
321
  UPDLOCK = ' WITH (UPDLOCK)'.freeze
322
322
  WILDCARD = LiteralString.new('*').freeze
@@ -690,6 +690,7 @@ module Sequel
690
690
  end
691
691
  end
692
692
  end
693
+ alias update_limit_sql select_limit_sql
693
694
 
694
695
  # Support different types of locking styles
695
696
  def select_lock_sql(sql)
@@ -369,11 +369,6 @@ module Sequel
369
369
  end
370
370
  end
371
371
 
372
- # MySQL treats integer primary keys as autoincrementing.
373
- def schema_autoincrementing_primary_key?(schema)
374
- super and schema[:db_type] =~ /int/io
375
- end
376
-
377
372
  # Use the MySQL specific DESCRIBE syntax to get a table description.
378
373
  def schema_parse_table(table_name, opts)
379
374
  m = output_identifier_meth(opts[:dataset])
@@ -50,6 +50,43 @@ module Sequel
50
50
  attr_accessor :force_standard_strings
51
51
  end
52
52
 
53
+ class CreateTableGenerator < Sequel::Schema::Generator
54
+ # Add an exclusion constraint when creating the table. elements should be
55
+ # an array of 2 element arrays, with the first element being the column or
56
+ # expression the exclusion constraint is applied to, and the second element
57
+ # being the operator to use for the column/expression to check for exclusion.
58
+ #
59
+ # Example:
60
+ #
61
+ # exclusion_constraint([[:col1, '&&'], [:col2, '=']])
62
+ # # EXCLUDE USING gist (col1 WITH &&, col2 WITH =)
63
+ #
64
+ # Options supported:
65
+ #
66
+ # :name :: Name the constraint with the given name (useful if you may
67
+ # need to drop the constraint later)
68
+ # :using :: Override the index_method for the exclusion constraint (defaults to gist).
69
+ # :where :: Create a partial exclusion constraint, which only affects
70
+ # a subset of table rows, value should be a filter expression.
71
+ def exclude(elements, opts={})
72
+ constraints << {:type => :exclude, :elements => elements}.merge(opts)
73
+ end
74
+ end
75
+
76
+ class AlterTableGenerator < Sequel::Schema::AlterTableGenerator
77
+ # Adds an exclusion constraint to an existing table, see
78
+ # CreateTableGenerator#exclude.
79
+ def add_exclusion_constraint(elements, opts={})
80
+ @operations << {:op => :add_constraint, :type => :exclude, :elements => elements}.merge(opts)
81
+ end
82
+
83
+ # Validate the constraint with the given name, which should have
84
+ # been added previously with NOT VALID.
85
+ def validate_constraint(name)
86
+ @operations << {:op => :validate_constraint, :name => name}
87
+ end
88
+ end
89
+
53
90
  # Methods shared by Database instances that connect to PostgreSQL.
54
91
  module DatabaseMethods
55
92
  EXCLUDE_SCHEMAS = /pg_*|information_schema/i
@@ -444,7 +481,12 @@ module Sequel
444
481
 
445
482
  private
446
483
 
447
- # Handle :using option for set_column_type op.
484
+ # Use a PostgreSQL-specific alter table generator
485
+ def alter_table_generator_class
486
+ Postgres::AlterTableGenerator
487
+ end
488
+
489
+ # Handle :using option for set_column_type op, and the :validate_constraint op.
448
490
  def alter_table_sql(table, op)
449
491
  case op[:op]
450
492
  when :set_column_type
@@ -455,6 +497,8 @@ module Sequel
455
497
  s << literal(using)
456
498
  end
457
499
  s
500
+ when :validate_constraint
501
+ "ALTER TABLE #{quote_schema_table(table)} VALIDATE CONSTRAINT #{quote_identifier(op[:name])}"
458
502
  else
459
503
  super
460
504
  end
@@ -499,6 +543,23 @@ module Sequel
499
543
  sqls
500
544
  end
501
545
 
546
+ # Handle exclusion constraints.
547
+ def constraint_definition_sql(constraint)
548
+ case constraint[:type]
549
+ when :exclude
550
+ elements = constraint[:elements].map{|c, op| "#{literal(c)} WITH #{op}"}.join(', ')
551
+ "#{"CONSTRAINT #{quote_identifier(constraint[:name])} " if constraint[:name]}EXCLUDE USING #{constraint[:using]||'gist'} (#{elements})#{" WHERE #{filter_expr(constraint[:where])}" if constraint[:where]}"
552
+ when :foreign_key
553
+ sql = super
554
+ if constraint[:not_valid]
555
+ sql << " NOT VALID"
556
+ end
557
+ sql
558
+ else
559
+ super
560
+ end
561
+ end
562
+
502
563
  # SQL statement to create database function.
503
564
  def create_function_sql(name, definition, opts={})
504
565
  args = opts[:args]
@@ -530,6 +591,11 @@ module Sequel
530
591
  "CREATE SCHEMA #{quote_identifier(name)}"
531
592
  end
532
593
 
594
+ # Use a PostgreSQL-specific create table generator
595
+ def create_table_generator_class
596
+ Postgres::CreateTableGenerator
597
+ end
598
+
533
599
  # SQL for creating a database trigger.
534
600
  def create_trigger_sql(table, name, function, opts={})
535
601
  events = opts[:events] ? Array(opts[:events]) : [:insert, :update, :delete]
@@ -642,7 +708,7 @@ module Sequel
642
708
  # PostgreSQL's autoincrementing primary keys are of type integer or bigint
643
709
  # using a nextval function call as a default.
644
710
  def schema_autoincrementing_primary_key?(schema)
645
- super and schema[:db_type] =~ /\A(?:integer|bigint)\z/io and schema[:default]=~/\Anextval/io
711
+ super && schema[:default] =~ /\Anextval/io
646
712
  end
647
713
 
648
714
  # The dataset used for parsing table schemas, using the pg_* system catalogs.
@@ -383,7 +383,7 @@ module Sequel
383
383
 
384
384
  # SQLite treats integer primary keys as autoincrementing (alias of rowid).
385
385
  def schema_autoincrementing_primary_key?(schema)
386
- super and schema[:db_type].downcase == 'integer'
386
+ super && schema[:db_type].downcase == 'integer'
387
387
  end
388
388
 
389
389
  # SQLite supports schema parsing using the table_info PRAGMA, so
@@ -428,6 +428,22 @@ module Sequel
428
428
  DOUBLE_BACKTICK = '``'.freeze
429
429
  BLOB_START = "X'".freeze
430
430
  HSTAR = "H*".freeze
431
+ DATE_OPEN = "date(".freeze
432
+ DATETIME_OPEN = "datetime(".freeze
433
+
434
+ def cast_sql_append(sql, expr, type)
435
+ if type == Time or type == DateTime
436
+ sql << DATETIME_OPEN
437
+ literal_append(sql, expr)
438
+ sql << PAREN_CLOSE
439
+ elsif type == Date
440
+ sql << DATE_OPEN
441
+ literal_append(sql, expr)
442
+ sql << PAREN_CLOSE
443
+ else
444
+ super
445
+ end
446
+ end
431
447
 
432
448
  # SQLite does not support pattern matching via regular expressions.
433
449
  # SQLite is case insensitive (depending on pragma), so use LIKE for
@@ -41,7 +41,12 @@ module Sequel
41
41
  # requires an order.
42
42
  def select_sql
43
43
  return super unless o = @opts[:offset]
44
- raise(Error, "#{db.database_type} requires an order be provided if using an offset") unless order = @opts[:order]
44
+
45
+ order = @opts[:order] || default_offset_order
46
+ if order.nil? || order.empty?
47
+ raise(Error, "#{db.database_type} requires an order be provided if using an offset")
48
+ end
49
+
45
50
  dsa1 = dataset_alias(1)
46
51
  rn = row_number_column
47
52
  sql = @opts[:append_sql] || ''
@@ -57,6 +62,12 @@ module Sequel
57
62
 
58
63
  private
59
64
 
65
+ # The default order to use for datasets with offsets, if no order is defined.
66
+ # By default, orders by all of the columns in the dataset.
67
+ def default_offset_order
68
+ clone(:append_sql=>'').columns
69
+ end
70
+
60
71
  # This emulation adds an extra row number column that should be
61
72
  # eliminated.
62
73
  def offset_returns_row_number_column?
@@ -0,0 +1,76 @@
1
+ module Sequel
2
+ module Postgres
3
+ NAN = 0.0/0.0
4
+ PLUS_INFINITY = 1.0/0.0
5
+ MINUS_INFINITY = -1.0/0.0
6
+ NAN_STR = 'NaN'.freeze
7
+ PLUS_INFINITY_STR = 'Infinity'.freeze
8
+ MINUS_INFINITY_STR = '-Infinity'.freeze
9
+ TRUE_STR = 't'.freeze
10
+ DASH_STR = '-'.freeze
11
+
12
+ TYPE_TRANSLATOR = tt = Class.new do
13
+ def boolean(s) s == TRUE_STR end
14
+ def integer(s) s.to_i end
15
+ def float(s)
16
+ case s
17
+ when NAN_STR
18
+ NAN
19
+ when PLUS_INFINITY_STR
20
+ PLUS_INFINITY
21
+ when MINUS_INFINITY_STR
22
+ MINUS_INFINITY
23
+ else
24
+ s.to_f
25
+ end
26
+ end
27
+ def date(s) ::Date.new(*s.split(DASH_STR).map{|x| x.to_i}) end
28
+ def bytea(str)
29
+ str = if str =~ /\A\\x/
30
+ # PostgreSQL 9.0+ bytea hex format
31
+ str[2..-1].gsub(/(..)/){|s| s.to_i(16).chr}
32
+ else
33
+ # Historical PostgreSQL bytea escape format
34
+ str.gsub(/\\(\\|'|[0-3][0-7][0-7])/) {|s|
35
+ if s.size == 2 then s[1,1] else s[1,3].oct.chr end
36
+ }
37
+ end
38
+ ::Sequel::SQL::Blob.new(str)
39
+ end
40
+ end.new
41
+
42
+ # Hash with type name strings/symbols and callable values for converting PostgreSQL types.
43
+ # Non-builtin types that don't have fixed numbers should use this to register
44
+ # conversion procs.
45
+ PG_NAMED_TYPES = {} unless defined?(PG_NAMED_TYPES)
46
+
47
+ # Hash with integer keys and callable values for converting PostgreSQL types.
48
+ PG_TYPES = {} unless defined?(PG_TYPES)
49
+
50
+ {
51
+ [16] => tt.method(:boolean),
52
+ [17] => tt.method(:bytea),
53
+ [20, 21, 23, 26] => tt.method(:integer),
54
+ [700, 701] => tt.method(:float),
55
+ [1700] => ::BigDecimal.method(:new),
56
+ [1083, 1266] => ::Sequel.method(:string_to_time),
57
+ [1184, 1114] => ::Sequel.method(:database_to_application_timestamp),
58
+ }.each do |k,v|
59
+ k.each{|n| PG_TYPES[n] = v}
60
+ end
61
+
62
+ class << self
63
+ # As an optimization, Sequel sets the date style to ISO, so that PostgreSQL provides
64
+ # the date in a known format that Sequel can parse faster. This can be turned off
65
+ # if you require a date style other than ISO.
66
+ attr_reader :use_iso_date_format
67
+ end
68
+
69
+ # Modify the type translator for the date type depending on the value given.
70
+ def self.use_iso_date_format=(v)
71
+ PG_TYPES[1082] = v ? TYPE_TRANSLATOR.method(:date) : Sequel.method(:string_to_date)
72
+ @use_iso_date_format = v
73
+ end
74
+ self.use_iso_date_format = true
75
+ end
76
+ end