sequel 3.33.0 → 3.34.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 (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