sequel 3.33.0 → 3.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/CHANGELOG +140 -0
  2. data/Rakefile +7 -0
  3. data/bin/sequel +22 -2
  4. data/doc/dataset_basics.rdoc +1 -1
  5. data/doc/mass_assignment.rdoc +3 -1
  6. data/doc/querying.rdoc +28 -4
  7. data/doc/reflection.rdoc +23 -3
  8. data/doc/release_notes/3.34.0.txt +671 -0
  9. data/doc/schema_modification.rdoc +18 -2
  10. data/doc/virtual_rows.rdoc +49 -0
  11. data/lib/sequel/adapters/do/mysql.rb +0 -5
  12. data/lib/sequel/adapters/ibmdb.rb +9 -4
  13. data/lib/sequel/adapters/jdbc.rb +9 -4
  14. data/lib/sequel/adapters/jdbc/h2.rb +8 -2
  15. data/lib/sequel/adapters/jdbc/mysql.rb +0 -5
  16. data/lib/sequel/adapters/jdbc/postgresql.rb +43 -0
  17. data/lib/sequel/adapters/jdbc/sqlite.rb +19 -0
  18. data/lib/sequel/adapters/mock.rb +24 -3
  19. data/lib/sequel/adapters/mysql.rb +29 -50
  20. data/lib/sequel/adapters/mysql2.rb +13 -28
  21. data/lib/sequel/adapters/oracle.rb +8 -2
  22. data/lib/sequel/adapters/postgres.rb +115 -20
  23. data/lib/sequel/adapters/shared/db2.rb +1 -1
  24. data/lib/sequel/adapters/shared/mssql.rb +14 -3
  25. data/lib/sequel/adapters/shared/mysql.rb +59 -11
  26. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +1 -1
  28. data/lib/sequel/adapters/shared/postgres.rb +127 -30
  29. data/lib/sequel/adapters/shared/sqlite.rb +55 -38
  30. data/lib/sequel/adapters/sqlite.rb +9 -3
  31. data/lib/sequel/adapters/swift.rb +2 -2
  32. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  33. data/lib/sequel/adapters/swift/postgres.rb +10 -0
  34. data/lib/sequel/ast_transformer.rb +4 -0
  35. data/lib/sequel/connection_pool.rb +8 -0
  36. data/lib/sequel/connection_pool/sharded_single.rb +5 -0
  37. data/lib/sequel/connection_pool/sharded_threaded.rb +17 -0
  38. data/lib/sequel/connection_pool/single.rb +5 -0
  39. data/lib/sequel/connection_pool/threaded.rb +14 -0
  40. data/lib/sequel/core.rb +24 -3
  41. data/lib/sequel/database/connecting.rb +24 -14
  42. data/lib/sequel/database/dataset_defaults.rb +1 -0
  43. data/lib/sequel/database/misc.rb +16 -25
  44. data/lib/sequel/database/query.rb +20 -2
  45. data/lib/sequel/database/schema_generator.rb +2 -2
  46. data/lib/sequel/database/schema_methods.rb +120 -23
  47. data/lib/sequel/dataset/actions.rb +91 -18
  48. data/lib/sequel/dataset/features.rb +5 -0
  49. data/lib/sequel/dataset/prepared_statements.rb +6 -2
  50. data/lib/sequel/dataset/sql.rb +68 -51
  51. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  52. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +18 -13
  53. data/lib/sequel/extensions/migration.rb +4 -0
  54. data/lib/sequel/extensions/null_dataset.rb +90 -0
  55. data/lib/sequel/extensions/pg_array.rb +460 -0
  56. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  57. data/lib/sequel/extensions/pg_auto_parameterize.rb +174 -0
  58. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  59. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  60. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  61. data/lib/sequel/extensions/pretty_table.rb +5 -71
  62. data/lib/sequel/extensions/query_literals.rb +79 -0
  63. data/lib/sequel/extensions/schema_caching.rb +76 -0
  64. data/lib/sequel/extensions/schema_dumper.rb +227 -31
  65. data/lib/sequel/extensions/select_remove.rb +35 -0
  66. data/lib/sequel/extensions/sql_expr.rb +4 -110
  67. data/lib/sequel/extensions/to_dot.rb +1 -1
  68. data/lib/sequel/model.rb +11 -2
  69. data/lib/sequel/model/associations.rb +35 -7
  70. data/lib/sequel/model/base.rb +159 -36
  71. data/lib/sequel/no_core_ext.rb +2 -0
  72. data/lib/sequel/plugins/caching.rb +25 -18
  73. data/lib/sequel/plugins/composition.rb +1 -1
  74. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  75. data/lib/sequel/plugins/identity_map.rb +11 -3
  76. data/lib/sequel/plugins/instance_filters.rb +10 -0
  77. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  78. data/lib/sequel/plugins/nested_attributes.rb +4 -3
  79. data/lib/sequel/plugins/prepared_statements.rb +3 -1
  80. data/lib/sequel/plugins/prepared_statements_associations.rb +5 -1
  81. data/lib/sequel/plugins/schema.rb +7 -2
  82. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  83. data/lib/sequel/plugins/static_cache.rb +99 -0
  84. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  85. data/lib/sequel/sql.rb +417 -7
  86. data/lib/sequel/version.rb +1 -1
  87. data/spec/adapters/firebird_spec.rb +1 -1
  88. data/spec/adapters/mssql_spec.rb +12 -15
  89. data/spec/adapters/mysql_spec.rb +81 -23
  90. data/spec/adapters/postgres_spec.rb +444 -77
  91. data/spec/adapters/spec_helper.rb +2 -0
  92. data/spec/adapters/sqlite_spec.rb +8 -8
  93. data/spec/core/connection_pool_spec.rb +85 -0
  94. data/spec/core/database_spec.rb +29 -5
  95. data/spec/core/dataset_spec.rb +171 -3
  96. data/spec/core/expression_filters_spec.rb +364 -0
  97. data/spec/core/mock_adapter_spec.rb +17 -3
  98. data/spec/core/schema_spec.rb +133 -0
  99. data/spec/extensions/association_dependencies_spec.rb +13 -13
  100. data/spec/extensions/caching_spec.rb +26 -3
  101. data/spec/extensions/class_table_inheritance_spec.rb +2 -2
  102. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +23 -94
  103. data/spec/extensions/force_encoding_spec.rb +4 -2
  104. data/spec/extensions/hook_class_methods_spec.rb +5 -2
  105. data/spec/extensions/identity_map_spec.rb +17 -0
  106. data/spec/extensions/instance_filters_spec.rb +1 -1
  107. data/spec/extensions/lazy_attributes_spec.rb +2 -2
  108. data/spec/extensions/list_spec.rb +4 -4
  109. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  110. data/spec/extensions/migration_spec.rb +6 -2
  111. data/spec/extensions/nested_attributes_spec.rb +20 -0
  112. data/spec/extensions/null_dataset_spec.rb +85 -0
  113. data/spec/extensions/optimistic_locking_spec.rb +2 -2
  114. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  115. data/spec/extensions/pg_array_spec.rb +196 -0
  116. data/spec/extensions/pg_auto_parameterize_spec.rb +64 -0
  117. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  118. data/spec/extensions/pg_hstore_spec.rb +195 -0
  119. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  120. data/spec/extensions/prepared_statements_spec.rb +4 -0
  121. data/spec/extensions/pretty_table_spec.rb +6 -0
  122. data/spec/extensions/query_literals_spec.rb +168 -0
  123. data/spec/extensions/schema_caching_spec.rb +41 -0
  124. data/spec/extensions/schema_dumper_spec.rb +231 -11
  125. data/spec/extensions/schema_spec.rb +14 -2
  126. data/spec/extensions/select_remove_spec.rb +38 -0
  127. data/spec/extensions/sharding_spec.rb +6 -6
  128. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  129. data/spec/extensions/spec_helper.rb +2 -1
  130. data/spec/extensions/sql_expr_spec.rb +28 -19
  131. data/spec/extensions/static_cache_spec.rb +145 -0
  132. data/spec/extensions/touch_spec.rb +1 -1
  133. data/spec/extensions/typecast_on_load_spec.rb +9 -1
  134. data/spec/integration/associations_test.rb +6 -6
  135. data/spec/integration/database_test.rb +1 -1
  136. data/spec/integration/dataset_test.rb +89 -26
  137. data/spec/integration/migrator_test.rb +2 -3
  138. data/spec/integration/model_test.rb +3 -3
  139. data/spec/integration/plugin_test.rb +85 -22
  140. data/spec/integration/prepared_statement_test.rb +28 -8
  141. data/spec/integration/schema_test.rb +78 -7
  142. data/spec/integration/spec_helper.rb +1 -0
  143. data/spec/integration/timezone_test.rb +1 -1
  144. data/spec/integration/transaction_test.rb +4 -6
  145. data/spec/integration/type_test.rb +2 -2
  146. data/spec/model/associations_spec.rb +94 -8
  147. data/spec/model/base_spec.rb +4 -4
  148. data/spec/model/hooks_spec.rb +2 -2
  149. data/spec/model/model_spec.rb +19 -7
  150. data/spec/model/record_spec.rb +135 -58
  151. data/spec/model/spec_helper.rb +1 -0
  152. metadata +35 -7
@@ -50,6 +50,7 @@ module Sequel
50
50
  end
51
51
  i = 0
52
52
  _execute(conn, "SET " + args.map {|arg| "@sequel_arg_#{i+=1} = #{literal(arg)}"}.join(", "), opts) unless args.empty?
53
+ opts = opts.merge(:log_sql=>" (#{sql})") if ps.log_sql
53
54
  _execute(conn, "EXECUTE #{ps_name}#{" USING #{(1..i).map{|j| "@sequel_arg_#{j}"}.join(', ')}" unless i == 0}", opts, &block)
54
55
  end
55
56
  end
@@ -94,6 +95,11 @@ module Sequel
94
95
  def execute_dui(sql, opts={}, &block)
95
96
  super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
96
97
  end
98
+
99
+ # Same as execute, explicit due to intricacies of alias and super.
100
+ def execute_insert(sql, opts={}, &block)
101
+ super(prepared_statement_name, {:arguments=>bind_arguments}.merge(opts), &block)
102
+ end
97
103
  end
98
104
 
99
105
  # Methods for MySQL stored procedures using the native driver.
@@ -179,7 +179,7 @@ module Sequel
179
179
  include EmulateOffsetWithRowNumber
180
180
 
181
181
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct columns from join where group having compounds order lock')
182
- ROW_NUMBER_EXPRESSION = 'ROWNUM'.lit.freeze
182
+ ROW_NUMBER_EXPRESSION = LiteralString.new('ROWNUM').freeze
183
183
  SPACE = Dataset::SPACE
184
184
  APOS = Dataset::APOS
185
185
  APOS_RE = Dataset::APOS_RE
@@ -57,7 +57,7 @@ module Sequel
57
57
  attr_writer :db
58
58
 
59
59
  SELECT_CURRVAL = "SELECT currval('%s')".freeze
60
- SELECT_CUSTOM_SEQUENCE = proc do |schema, table| <<-end_sql
60
+ SELECT_CUSTOM_SEQUENCE_SQL = (<<-end_sql
61
61
  SELECT '"' || name.nspname || '".' || CASE
62
62
  WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
63
63
  substr(split_part(def.adsrc, '''', 2),
@@ -71,11 +71,10 @@ module Sequel
71
71
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
72
72
  WHERE cons.contype = 'p'
73
73
  AND def.adsrc ~* 'nextval'
74
- #{"AND name.nspname = '#{schema}'" if schema}
75
- AND t.relname = '#{table}'
76
74
  end_sql
77
- end
78
- SELECT_PK = proc do |schema, table| <<-end_sql
75
+ ).strip.gsub(/\s+/, ' ').freeze
76
+
77
+ SELECT_PK_SQL = (<<-end_sql
79
78
  SELECT pg_attribute.attname
80
79
  FROM pg_class, pg_attribute, pg_index, pg_namespace
81
80
  WHERE pg_class.oid = pg_attribute.attrelid
@@ -83,11 +82,10 @@ module Sequel
83
82
  AND pg_class.oid = pg_index.indrelid
84
83
  AND pg_index.indkey[0] = pg_attribute.attnum
85
84
  AND pg_index.indisprimary = 't'
86
- #{"AND pg_namespace.nspname = '#{schema}'" if schema}
87
- AND pg_class.relname = '#{table}'
88
85
  end_sql
89
- end
90
- SELECT_SERIAL_SEQUENCE = proc do |schema, table| <<-end_sql
86
+ ).strip.gsub(/\s+/, ' ').freeze
87
+
88
+ SELECT_SERIAL_SEQUENCE_SQL = (<<-end_sql
91
89
  SELECT '"' || name.nspname || '".' || seq.relname || ''
92
90
  FROM pg_class seq, pg_attribute attr, pg_depend dep,
93
91
  pg_namespace name, pg_constraint cons
@@ -99,10 +97,8 @@ module Sequel
99
97
  AND attr.attrelid = cons.conrelid
100
98
  AND attr.attnum = cons.conkey[1]
101
99
  AND cons.contype = 'p'
102
- #{"AND name.nspname = '#{schema}'" if schema}
103
- AND seq.relname = '#{table}'
104
100
  end_sql
105
- end
101
+ ).strip.gsub(/\s+/, ' ').freeze
106
102
 
107
103
  # Depth of the current transaction on this connection, used
108
104
  # to implement multi-level transactions with savepoints.
@@ -132,23 +128,30 @@ module Sequel
132
128
  end
133
129
  end
134
130
 
135
- # Get the primary key for the given table.
131
+ # Get the primary key for the given table and schema. Both
132
+ # should be provided as literal SQL strings, with schema
133
+ # optionally nil.
136
134
  def primary_key(schema, table)
137
- sql = SELECT_PK[schema, table]
135
+ sql = "#{SELECT_PK_SQL} AND pg_class.relname = #{table}"
136
+ sql << "AND pg_namespace.nspname = #{schema}" if schema
138
137
  execute(sql) do |r|
139
138
  return single_value(r)
140
139
  end
141
140
  end
142
141
 
143
- # Get the primary key and sequence for the given table.
142
+ # Get the primary key and sequence for the given table and schema.
143
+ # Both should be provided as literal SQL strings, with schema
144
+ # optionally nil.
144
145
  def sequence(schema, table)
145
- sql = SELECT_SERIAL_SEQUENCE[schema, table]
146
+ sql = "#{SELECT_SERIAL_SEQUENCE_SQL} AND seq.relname = #{table}"
147
+ sql << " AND name.nspname = #{schema}" if schema
146
148
  execute(sql) do |r|
147
149
  seq = single_value(r)
148
150
  return seq if seq
149
151
  end
150
152
 
151
- sql = SELECT_CUSTOM_SEQUENCE[schema, table]
153
+ sql = "#{SELECT_CUSTOM_SEQUENCE_SQL} AND t.relname = #{table}"
154
+ sql << " AND name.nspname = #{schema}" if schema
152
155
  execute(sql) do |r|
153
156
  return single_value(r)
154
157
  end
@@ -161,6 +164,7 @@ module Sequel
161
164
  PREPARED_ARG_PLACEHOLDER = LiteralString.new('$').freeze
162
165
  RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session|relation "(.*)" does not exist/.freeze
163
166
  SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
167
+ FOREIGN_KEY_LIST_ON_DELETE_MAP = {'a'.freeze=>:no_action, 'r'.freeze=>:restrict, 'c'.freeze=>:cascade, 'n'.freeze=>:set_null, 'd'.freeze=>:set_default}.freeze
164
168
 
165
169
  # Commit an existing prepared transaction with the given transaction
166
170
  # identifier string.
@@ -266,6 +270,64 @@ module Sequel
266
270
  self << drop_trigger_sql(table, name, opts)
267
271
  end
268
272
 
273
+ # Return full foreign key information using the pg system tables, including
274
+ # :name, :on_delete, :on_update, and :deferrable entries in the hashes.
275
+ def foreign_key_list(table, opts={})
276
+ m = output_identifier_meth
277
+ im = input_identifier_meth
278
+ schema, table = schema_and_table(table)
279
+ range = 0...32
280
+
281
+ base_ds = metadata_dataset.
282
+ where(:cl__relkind=>'r', :co__contype=>'f', :cl__relname=>im.call(table)).
283
+ from(:pg_constraint___co).
284
+ join(:pg_class___cl, :oid=>:conrelid)
285
+
286
+ # We split the parsing into two separate queries, which are merged manually later.
287
+ # This is because PostgreSQL stores both the referencing and referenced columns in
288
+ # arrays, and I don't know a simple way to not create a cross product, as PostgreSQL
289
+ # doesn't appear to have a function that takes an array and element and gives you
290
+ # the index of that element in the array.
291
+
292
+ ds = base_ds.
293
+ join(:pg_attribute___att, :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, :co__conkey)).
294
+ order(:co__conname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:co__conkey, [x]), x]}, 32, :att__attnum)).
295
+ select(:co__conname___name, :att__attname___column, :co__confupdtype___on_update, :co__confdeltype___on_delete,
296
+ SQL::BooleanExpression.new(:AND, :co__condeferrable, :co__condeferred).as(:deferrable))
297
+
298
+ ref_ds = base_ds.
299
+ join(:pg_class___cl2, :oid=>:co__confrelid).
300
+ join(:pg_attribute___att2, :attrelid=>:oid, :attnum=>SQL::Function.new(:ANY, :co__confkey)).
301
+ order(:co__conname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:co__conkey, [x]), x]}, 32, :att2__attnum)).
302
+ select(:co__conname___name, :cl2__relname___table, :att2__attname___refcolumn)
303
+
304
+ # If a schema is given, we only search in that schema, and the returned :table
305
+ # entry is schema qualified as well.
306
+ if schema
307
+ ds.join!(:pg_namespace___nsp, :oid=>:cl__relnamespace).
308
+ where(:nsp___nspname=>im.call(schema))
309
+ ref_ds.join!(:pg_namespace___nsp2, :oid=>:cl2__relnamespace).
310
+ select_more(:nsp2__nspname___schema)
311
+ end
312
+
313
+ h = {}
314
+ fklod_map = FOREIGN_KEY_LIST_ON_DELETE_MAP
315
+ ds.each do |row|
316
+ if r = h[row[:name]]
317
+ r[:columns] << m.call(row[:column])
318
+ else
319
+ h[row[:name]] = {:name=>m.call(row[:name]), :columns=>[m.call(row[:column])], :on_update=>fklod_map[row[:on_update]], :on_delete=>fklod_map[row[:on_delete]], :deferrable=>row[:deferrable]}
320
+ end
321
+ end
322
+ ref_ds.each do |row|
323
+ r = h[row[:name]]
324
+ r[:table] ||= m.call(schema ? SQL::QualifiedIdentifier.new(row[:schema], row[:table]) : row[:table])
325
+ r[:key] ||= []
326
+ r[:key] << m.call(row[:refcolumn])
327
+ end
328
+ h.values
329
+ end
330
+
269
331
  # Use the pg_* system tables to determine indexes on a table
270
332
  def indexes(table, opts={})
271
333
  m = output_identifier_meth
@@ -279,7 +341,7 @@ module Sequel
279
341
  join(:pg_class___indc, :oid=>:indexrelid).
280
342
  join(:pg_attribute___att, :attrelid=>:tab__oid, :attnum=>attnums).
281
343
  filter(:indc__relkind=>'i', :ind__indisprimary=>false, :indexprs=>nil, :indpred=>nil).
282
- order(:indc__relname, range.map{|x| [SQL::Subscript.new(:ind__indkey, [x]), x]}.case(32, :att__attnum)).
344
+ order(:indc__relname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:ind__indkey, [x]), x]}, 32, :att__attnum)).
283
345
  select(:indc__relname___name, :ind__indisunique___unique, :att__attname___column)
284
346
 
285
347
  ds.join!(:pg_namespace___nsp, :oid=>:tab__relnamespace, :nspname=>schema.to_s) if schema
@@ -314,9 +376,9 @@ module Sequel
314
376
  quoted_table = quote_schema_table(table)
315
377
  return @primary_keys[quoted_table] if @primary_keys.include?(quoted_table)
316
378
  @primary_keys[quoted_table] = if conn = opts[:conn]
317
- conn.primary_key(*schema_and_table(table))
379
+ conn.primary_key(*schema_and_table_quoted_strings(table))
318
380
  else
319
- synchronize(opts[:server]){|con| con.primary_key(*schema_and_table(table))}
381
+ synchronize(opts[:server]){|con| con.primary_key(*schema_and_table_quoted_strings(table))}
320
382
  end
321
383
  end
322
384
 
@@ -325,9 +387,9 @@ module Sequel
325
387
  quoted_table = quote_schema_table(table)
326
388
  return @primary_key_sequences[quoted_table] if @primary_key_sequences.include?(quoted_table)
327
389
  @primary_key_sequences[quoted_table] = if conn = opts[:conn]
328
- conn.sequence(*schema_and_table(table))
390
+ conn.sequence(*schema_and_table_quoted_strings(table))
329
391
  else
330
- synchronize(opts[:server]){|con| con.sequence(*schema_and_table(table))}
392
+ synchronize(opts[:server]){|con| con.sequence(*schema_and_table_quoted_strings(table))}
331
393
  end
332
394
  end
333
395
 
@@ -337,7 +399,7 @@ module Sequel
337
399
  pk = SQL::Identifier.new(primary_key(table))
338
400
  return unless seq = primary_key_sequence(table)
339
401
  db = self
340
- seq_ds = db.from(seq.lit)
402
+ seq_ds = db.from(LiteralString.new(seq))
341
403
  get{setval(seq, db[table].select{coalesce(max(pk)+seq_ds.select{:increment_by}, seq_ds.select(:min_value))}, false)}
342
404
  end
343
405
 
@@ -366,6 +428,7 @@ module Sequel
366
428
  0
367
429
  end
368
430
  end
431
+ warn 'Sequel support for PostgreSQL <8.2 is deprecated and will be removed in 3.35.0' if @server_version < 80200
369
432
  @server_version
370
433
  end
371
434
 
@@ -381,6 +444,11 @@ module Sequel
381
444
  server_version >= 90100
382
445
  end
383
446
 
447
+ # PostgreSQL supports DROP TABLE IF EXISTS on 8.2+
448
+ def supports_drop_table_if_exists?
449
+ server_version >= 80200
450
+ end
451
+
384
452
  # PostgreSQL supports savepoints
385
453
  def supports_savepoints?
386
454
  true
@@ -402,6 +470,13 @@ module Sequel
402
470
  pg_class_relname('r', opts, &block)
403
471
  end
404
472
 
473
+ # Check whether the given type name string/symbol (e.g. :hstore) is supported by
474
+ # the database.
475
+ def type_supported?(type)
476
+ @supported_types ||= {}
477
+ @supported_types.fetch(type){@supported_types[type] = (from(:pg_type).filter(:typtype=>'b', :typname=>type.to_s).count > 0)}
478
+ end
479
+
405
480
  # Array of symbols specifying view names in the current database.
406
481
  #
407
482
  # Options:
@@ -521,7 +596,7 @@ module Sequel
521
596
  filter = " WHERE #{filter_expr(filter)}" if filter
522
597
  case index_type
523
598
  when :full_text
524
- expr = "(to_tsvector(#{literal(index[:language] || 'simple')}, #{literal(dataset.send(:full_text_string_join, cols))}))"
599
+ expr = "(to_tsvector(#{literal(index[:language] || 'simple')}::regconfig, #{literal(dataset.send(:full_text_string_join, cols))}))"
525
600
  index_type = :gin
526
601
  when :spatial
527
602
  index_type = :gist
@@ -591,6 +666,13 @@ module Sequel
591
666
  "ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_identifier(schema_and_table(new_name).last)}"
592
667
  end
593
668
 
669
+ # Split the table into a schema and table, and return the values as quoted strings for usage
670
+ # in querying the system tables.
671
+ def schema_and_table_quoted_strings(table)
672
+ schema, table = schema_and_table(table)
673
+ [(literal(schema) if schema), literal(table)]
674
+ end
675
+
594
676
  # PostgreSQL's autoincrementing primary keys are of type integer or bigint
595
677
  # using a nextval function call as a default.
596
678
  def schema_autoincrementing_primary_key?(schema)
@@ -677,6 +759,7 @@ module Sequel
677
759
  BOOL_TRUE = 'true'.freeze
678
760
  COMMA_SEPARATOR = ', '.freeze
679
761
  DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'delete from using where')
762
+ DELETE_CLAUSE_METHODS_82 = Dataset.clause_methods(:delete, %w'delete from using where returning')
680
763
  DELETE_CLAUSE_METHODS_91 = Dataset.clause_methods(:delete, %w'with delete from using where returning')
681
764
  EXCLUSIVE = 'EXCLUSIVE'.freeze
682
765
  EXPLAIN = 'EXPLAIN '.freeze
@@ -698,6 +781,7 @@ module Sequel
698
781
  SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
699
782
  SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
700
783
  UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update table set from where')
784
+ UPDATE_CLAUSE_METHODS_82 = Dataset.clause_methods(:update, %w'update table set from where returning')
701
785
  UPDATE_CLAUSE_METHODS_91 = Dataset.clause_methods(:update, %w'with update table set from where returning')
702
786
  SPACE = Dataset::SPACE
703
787
  FROM = Dataset::FROM
@@ -717,7 +801,7 @@ module Sequel
717
801
  # Shared methods for prepared statements when used with PostgreSQL databases.
718
802
  module PreparedStatementMethods
719
803
  # Override insert action to use RETURNING if the server supports it.
720
- def run(&block)
804
+ def run
721
805
  if @prepared_type == :insert && supports_insert_select?
722
806
  fetch_rows(prepared_sql){|r| return r.values.first}
723
807
  else
@@ -743,7 +827,7 @@ module Sequel
743
827
  mod.def_mutation_method(:disable_insert_returning)
744
828
  end
745
829
 
746
- # Return the results of an ANALYZE query as a string
830
+ # Return the results of an EXPLAIN ANALYZE query as a string
747
831
  def analyze
748
832
  explain(:analyze=>true)
749
833
  end
@@ -767,6 +851,7 @@ module Sequel
767
851
 
768
852
  # Disable the use of INSERT RETURNING, even if the server supports it
769
853
  def disable_insert_returning
854
+ warn("disable_insert_returning is deprecated and will be removed in Sequel 3.35.0")
770
855
  clone(:disable_insert_returning=>true)
771
856
  end
772
857
 
@@ -785,11 +870,11 @@ module Sequel
785
870
  def full_text_search(cols, terms, opts = {})
786
871
  lang = opts[:language] || 'simple'
787
872
  terms = terms.join(' | ') if terms.is_a?(Array)
788
- filter("to_tsvector(?, ?) @@ to_tsquery(?, ?)", lang, full_text_string_join(cols), lang, terms)
873
+ filter("to_tsvector(?::regconfig, ?) @@ to_tsquery(?::regconfig, ?)", lang, full_text_string_join(cols), lang, terms)
789
874
  end
790
875
 
791
876
  # Insert given values into the database.
792
- def insert(*values, &block)
877
+ def insert(*values)
793
878
  if @opts[:returning]
794
879
  super
795
880
  elsif !@opts[:sql] && supports_insert_select?
@@ -904,7 +989,13 @@ module Sequel
904
989
 
905
990
  # PostgreSQL allows deleting from joined datasets
906
991
  def delete_clause_methods
907
- server_version >= 90100 ? DELETE_CLAUSE_METHODS_91 : DELETE_CLAUSE_METHODS
992
+ if (sv = server_version) >= 90100
993
+ DELETE_CLAUSE_METHODS_91
994
+ elsif sv >= 80200
995
+ DELETE_CLAUSE_METHODS_82
996
+ else
997
+ DELETE_CLAUSE_METHODS
998
+ end
908
999
  end
909
1000
 
910
1001
  # Only include the primary table in the main delete clause
@@ -1035,7 +1126,13 @@ module Sequel
1035
1126
 
1036
1127
  # PostgreSQL splits the main table from the joined tables
1037
1128
  def update_clause_methods
1038
- server_version >= 90100 ? UPDATE_CLAUSE_METHODS_91 : UPDATE_CLAUSE_METHODS
1129
+ if (sv = server_version) >= 90100
1130
+ UPDATE_CLAUSE_METHODS_91
1131
+ elsif sv >= 80200
1132
+ UPDATE_CLAUSE_METHODS_82
1133
+ else
1134
+ UPDATE_CLAUSE_METHODS
1135
+ end
1039
1136
  end
1040
1137
 
1041
1138
  # Use FROM to specify additional tables in an update query
@@ -59,22 +59,33 @@ module Sequel
59
59
  pragma_set(:foreign_keys, !!value ? 'on' : 'off') if sqlite_version >= 30619
60
60
  end
61
61
 
62
+ # Return the array of foreign key info hashes using the foreign_key_list PRAGMA,
63
+ # including information for the :on_update and :on_delete entries.
64
+ def foreign_key_list(table, opts={})
65
+ m = output_identifier_meth
66
+ h = {}
67
+ metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
68
+ if r = h[row[:id]]
69
+ r[:columns] << m.call(row[:from])
70
+ r[:key] << m.call(row[:to]) if r[:key]
71
+ else
72
+ h[row[:id]] = {:columns=>[m.call(row[:from])], :table=>m.call(row[:table]), :key=>([m.call(row[:to])] if row[:to]), :on_update=>on_delete_sql_to_sym(row[:on_update]), :on_delete=>on_delete_sql_to_sym(row[:on_delete])}
73
+ end
74
+ end
75
+ h.values
76
+ end
77
+
62
78
  # Use the index_list and index_info PRAGMAs to determine the indexes on the table.
63
79
  def indexes(table, opts={})
64
80
  m = output_identifier_meth
65
81
  im = input_identifier_meth
66
82
  indexes = {}
67
- begin
68
- metadata_dataset.with_sql("PRAGMA index_list(?)", im.call(table)).each do |r|
69
- next if r[:name] =~ PRIMARY_KEY_INDEX_RE
70
- indexes[m.call(r[:name])] = {:unique=>r[:unique].to_i==1}
71
- end
72
- rescue Sequel::DatabaseError
73
- nil
74
- else
75
- indexes.each do |k, v|
76
- v[:columns] = metadata_dataset.with_sql("PRAGMA index_info(?)", im.call(k)).map(:name).map{|x| m.call(x)}
77
- end
83
+ metadata_dataset.with_sql("PRAGMA index_list(?)", im.call(table)).each do |r|
84
+ next if r[:name] =~ PRIMARY_KEY_INDEX_RE
85
+ indexes[m.call(r[:name])] = {:unique=>r[:unique].to_i==1}
86
+ end
87
+ indexes.each do |k, v|
88
+ v[:columns] = metadata_dataset.with_sql("PRAGMA index_info(?)", im.call(k)).map(:name).map{|x| m.call(x)}
78
89
  end
79
90
  indexes
80
91
  end
@@ -226,7 +237,7 @@ module Sequel
226
237
  when :primary_key
227
238
  duplicate_table(table){|columns| columns.each{|s| s[:primary_key] = nil}}
228
239
  when :foreign_key
229
- duplicate_table(table){|columns| columns.each{|s| s[:table] = nil}}
240
+ duplicate_table(table, :no_foreign_keys=>true)
230
241
  when :unique
231
242
  duplicate_table(table)
232
243
  else
@@ -283,20 +294,6 @@ module Sequel
283
294
  nono= Array(opts[:except]).compact.map{|n| n.to_s}
284
295
  cols.reject!{|c| nono.include? c[:name] }
285
296
  end
286
-
287
- begin
288
- metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
289
- c = cols.find {|co| co[:name] == row[:from] } or next
290
- c[:table] = row[:table]
291
- c[:key] = row[:to]
292
- c[:on_update] = on_delete_sql_to_sym(row[:on_update])
293
- c[:on_delete] = on_delete_sql_to_sym(row[:on_delete])
294
- # is there any way to get deferrable status?
295
- end
296
- rescue Sequel::DatabaseError
297
- # Doesn't work correctly on some versions of JDBC SQLite,
298
- # giving a "query does not return ResultSet" error.
299
- end
300
297
  cols
301
298
  end
302
299
 
@@ -319,6 +316,20 @@ module Sequel
319
316
  def_columns.each{|c| c[:primary_key] = false if c[:primary_key]}
320
317
  end
321
318
 
319
+ # If dropping a foreign key constraint, drop all foreign key constraints,
320
+ # as there is no way to determine which one to drop.
321
+ unless opts[:no_foreign_keys]
322
+ fks = foreign_key_list(table)
323
+
324
+ # If dropping a column, if there is a foreign key with that
325
+ # column, don't include it when building a copy of the table.
326
+ if ocp = opts[:old_columns_proc]
327
+ fks.delete_if{|c| ocp.call(c[:columns].dup) != c[:columns]}
328
+ end
329
+
330
+ constraints.concat(fks.each{|h| h[:type] = :foreign_key})
331
+ end
332
+
322
333
  def_columns_str = (def_columns.map{|c| column_definition_sql(c)} + constraints.map{|c| constraint_definition_sql(c)}).join(', ')
323
334
  new_columns = old_columns.dup
324
335
  opts[:new_columns_proc].call(new_columns) if opts[:new_columns_proc]
@@ -399,13 +410,12 @@ module Sequel
399
410
  m = output_identifier_meth
400
411
  metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(filter).map{|r| m.call(r[:name])}
401
412
  end
402
-
403
- # SQLite uses the integer data type even for bignums. This is because they
404
- # are both stored internally as text, and converted when returned from
405
- # the database. Using an integer type instead of bigint makes it more likely
406
- # that software will automatically return the column as an integer.
413
+
414
+ # SQLite only supports AUTOINCREMENT on integer columns, not
415
+ # bigint columns, so use integer instead of bigint for those
416
+ # columns.
407
417
  def type_literal_generic_bignum(column)
408
- :integer
418
+ column[:auto_increment] ? :integer : super
409
419
  end
410
420
  end
411
421
 
@@ -424,6 +434,8 @@ module Sequel
424
434
  NUMERIC = 'NUMERIC'.freeze
425
435
  INTEGER = 'INTEGER'.freeze
426
436
  BACKTICK = '`'.freeze
437
+ BACKTICK_RE = /`/.freeze
438
+ DOUBLE_BACKTICK = '``'.freeze
427
439
  BLOB_START = "X'".freeze
428
440
  HSTAR = "H*".freeze
429
441
 
@@ -470,14 +482,19 @@ module Sequel
470
482
  # Since we want to always return the count of records, add a condition
471
483
  # that is always true and then delete.
472
484
  def delete
473
- @opts[:where] ? super : filter(1=>1).delete
485
+ @opts[:where] ? super : where(1=>1).delete
474
486
  end
475
487
 
476
488
  # Return an array of strings specifying a query explanation for a SELECT of the
477
- # current dataset.
478
- def explain
479
- db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}").
480
- map{|x| "#{x[:addr]}|#{x[:opcode]}|#{(1..5).map{|i| x[:"p#{i}"]}.join('|')}|#{x[:comment]}"}
489
+ # current dataset. Currently, the options are ignore, but it accepts options
490
+ # to be compatible with other adapters.
491
+ def explain(opts=nil)
492
+ # Load the PrettyTable class, needed for explain output
493
+ Sequel.extension(:_pretty_table) unless defined?(Sequel::PrettyTable)
494
+
495
+ ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}")
496
+ rows = ds.all
497
+ Sequel::PrettyTable.string(rows, ds.columns)
481
498
  end
482
499
 
483
500
  # HAVING requires GROUP BY on SQLite
@@ -488,7 +505,7 @@ module Sequel
488
505
 
489
506
  # SQLite uses the nonstandard ` (backtick) for quoting identifiers.
490
507
  def quoted_identifier_append(sql, c)
491
- sql << BACKTICK << c.to_s << BACKTICK
508
+ sql << BACKTICK << c.to_s.gsub(BACKTICK_RE, DOUBLE_BACKTICK) << BACKTICK
492
509
  end
493
510
 
494
511
  # When a qualified column is selected on SQLite and the qualifier