sequel 3.21.0 → 3.31.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 (225) hide show
  1. data/CHANGELOG +485 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +37 -19
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +249 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/dataset_filtering.rdoc +8 -0
  8. data/doc/mass_assignment.rdoc +54 -0
  9. data/doc/migration.rdoc +15 -538
  10. data/doc/model_hooks.rdoc +72 -27
  11. data/doc/opening_databases.rdoc +89 -50
  12. data/doc/prepared_statements.rdoc +40 -13
  13. data/doc/reflection.rdoc +8 -2
  14. data/doc/release_notes/3.22.0.txt +39 -0
  15. data/doc/release_notes/3.23.0.txt +172 -0
  16. data/doc/release_notes/3.24.0.txt +420 -0
  17. data/doc/release_notes/3.25.0.txt +88 -0
  18. data/doc/release_notes/3.26.0.txt +88 -0
  19. data/doc/release_notes/3.27.0.txt +82 -0
  20. data/doc/release_notes/3.28.0.txt +304 -0
  21. data/doc/release_notes/3.29.0.txt +459 -0
  22. data/doc/release_notes/3.30.0.txt +135 -0
  23. data/doc/release_notes/3.31.0.txt +146 -0
  24. data/doc/schema_modification.rdoc +547 -0
  25. data/doc/sharding.rdoc +7 -1
  26. data/doc/testing.rdoc +129 -0
  27. data/doc/transactions.rdoc +137 -0
  28. data/doc/virtual_rows.rdoc +2 -2
  29. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  30. data/lib/sequel/adapters/ado.rb +40 -18
  31. data/lib/sequel/adapters/amalgalite.rb +17 -9
  32. data/lib/sequel/adapters/db2.rb +175 -86
  33. data/lib/sequel/adapters/dbi.rb +15 -15
  34. data/lib/sequel/adapters/do/mysql.rb +5 -7
  35. data/lib/sequel/adapters/do/postgres.rb +0 -5
  36. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/do.rb +17 -36
  38. data/lib/sequel/adapters/firebird.rb +27 -208
  39. data/lib/sequel/adapters/ibmdb.rb +448 -0
  40. data/lib/sequel/adapters/informix.rb +6 -23
  41. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  42. data/lib/sequel/adapters/jdbc/db2.rb +45 -0
  43. data/lib/sequel/adapters/jdbc/derby.rb +247 -0
  44. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  45. data/lib/sequel/adapters/jdbc/h2.rb +56 -29
  46. data/lib/sequel/adapters/jdbc/hsqldb.rb +170 -0
  47. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  48. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  49. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  50. data/lib/sequel/adapters/jdbc/mysql.rb +11 -11
  51. data/lib/sequel/adapters/jdbc/oracle.rb +81 -25
  52. data/lib/sequel/adapters/jdbc/postgresql.rb +9 -13
  53. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -6
  54. data/lib/sequel/adapters/jdbc/sqlserver.rb +71 -0
  55. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  56. data/lib/sequel/adapters/jdbc.rb +231 -78
  57. data/lib/sequel/adapters/mock.rb +335 -0
  58. data/lib/sequel/adapters/mysql.rb +82 -184
  59. data/lib/sequel/adapters/mysql2.rb +26 -25
  60. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  61. data/lib/sequel/adapters/odbc/mssql.rb +1 -6
  62. data/lib/sequel/adapters/odbc.rb +28 -9
  63. data/lib/sequel/adapters/openbase.rb +3 -5
  64. data/lib/sequel/adapters/oracle.rb +351 -53
  65. data/lib/sequel/adapters/postgres.rb +194 -33
  66. data/lib/sequel/adapters/shared/access.rb +32 -9
  67. data/lib/sequel/adapters/shared/db2.rb +321 -0
  68. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  69. data/lib/sequel/adapters/shared/informix.rb +53 -0
  70. data/lib/sequel/adapters/shared/mssql.rb +231 -106
  71. data/lib/sequel/adapters/shared/mysql.rb +181 -63
  72. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +231 -41
  74. data/lib/sequel/adapters/shared/postgres.rb +214 -82
  75. data/lib/sequel/adapters/shared/progress.rb +4 -7
  76. data/lib/sequel/adapters/shared/sqlite.rb +201 -57
  77. data/lib/sequel/adapters/sqlite.rb +29 -21
  78. data/lib/sequel/adapters/swift/mysql.rb +12 -7
  79. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  80. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  81. data/lib/sequel/adapters/swift.rb +5 -5
  82. data/lib/sequel/adapters/tinytds.rb +160 -19
  83. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  84. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  85. data/lib/sequel/ast_transformer.rb +190 -0
  86. data/lib/sequel/connection_pool/threaded.rb +3 -2
  87. data/lib/sequel/connection_pool.rb +1 -1
  88. data/lib/sequel/core.rb +46 -5
  89. data/lib/sequel/database/connecting.rb +5 -6
  90. data/lib/sequel/database/dataset.rb +4 -4
  91. data/lib/sequel/database/dataset_defaults.rb +59 -1
  92. data/lib/sequel/database/logging.rb +1 -1
  93. data/lib/sequel/database/misc.rb +104 -19
  94. data/lib/sequel/database/query.rb +153 -58
  95. data/lib/sequel/database/schema_generator.rb +17 -7
  96. data/lib/sequel/database/schema_methods.rb +59 -29
  97. data/lib/sequel/dataset/actions.rb +231 -76
  98. data/lib/sequel/dataset/features.rb +91 -8
  99. data/lib/sequel/dataset/graph.rb +38 -11
  100. data/lib/sequel/dataset/misc.rb +50 -23
  101. data/lib/sequel/dataset/mutation.rb +5 -6
  102. data/lib/sequel/dataset/prepared_statements.rb +48 -27
  103. data/lib/sequel/dataset/query.rb +236 -43
  104. data/lib/sequel/dataset/sql.rb +665 -253
  105. data/lib/sequel/dataset.rb +8 -0
  106. data/lib/sequel/exceptions.rb +4 -0
  107. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  108. data/lib/sequel/extensions/migration.rb +6 -4
  109. data/lib/sequel/extensions/named_timezones.rb +5 -0
  110. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  111. data/lib/sequel/extensions/to_dot.rb +95 -83
  112. data/lib/sequel/model/associations.rb +1020 -337
  113. data/lib/sequel/model/base.rb +414 -132
  114. data/lib/sequel/model/errors.rb +1 -1
  115. data/lib/sequel/model/exceptions.rb +5 -1
  116. data/lib/sequel/model.rb +15 -8
  117. data/lib/sequel/plugins/association_pks.rb +22 -4
  118. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  119. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  120. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  121. data/lib/sequel/plugins/force_encoding.rb +6 -6
  122. data/lib/sequel/plugins/identity_map.rb +114 -7
  123. data/lib/sequel/plugins/many_through_many.rb +65 -7
  124. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  125. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  126. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  127. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  128. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  129. data/lib/sequel/plugins/serialization.rb +88 -44
  130. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  131. data/lib/sequel/plugins/sharding.rb +21 -54
  132. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  133. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  134. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  135. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  136. data/lib/sequel/sql.rb +109 -52
  137. data/lib/sequel/timezones.rb +57 -38
  138. data/lib/sequel/version.rb +1 -1
  139. data/spec/adapters/db2_spec.rb +146 -0
  140. data/spec/adapters/mssql_spec.rb +111 -29
  141. data/spec/adapters/mysql_spec.rb +135 -123
  142. data/spec/adapters/oracle_spec.rb +49 -77
  143. data/spec/adapters/postgres_spec.rb +267 -78
  144. data/spec/adapters/spec_helper.rb +6 -5
  145. data/spec/adapters/sqlite_spec.rb +35 -21
  146. data/spec/core/connection_pool_spec.rb +71 -92
  147. data/spec/core/core_sql_spec.rb +21 -30
  148. data/spec/core/database_spec.rb +788 -512
  149. data/spec/core/dataset_spec.rb +1073 -1053
  150. data/spec/core/expression_filters_spec.rb +159 -42
  151. data/spec/core/mock_adapter_spec.rb +419 -0
  152. data/spec/core/object_graph_spec.rb +86 -114
  153. data/spec/core/schema_generator_spec.rb +3 -3
  154. data/spec/core/schema_spec.rb +298 -38
  155. data/spec/core/spec_helper.rb +6 -48
  156. data/spec/extensions/association_autoreloading_spec.rb +1 -10
  157. data/spec/extensions/association_dependencies_spec.rb +2 -12
  158. data/spec/extensions/association_pks_spec.rb +66 -32
  159. data/spec/extensions/caching_spec.rb +23 -50
  160. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  161. data/spec/extensions/columns_introspection_spec.rb +91 -0
  162. data/spec/extensions/composition_spec.rb +18 -13
  163. data/spec/extensions/dataset_associations_spec.rb +199 -0
  164. data/spec/extensions/defaults_setter_spec.rb +64 -0
  165. data/spec/extensions/hook_class_methods_spec.rb +65 -91
  166. data/spec/extensions/identity_map_spec.rb +114 -22
  167. data/spec/extensions/instance_filters_spec.rb +11 -21
  168. data/spec/extensions/instance_hooks_spec.rb +53 -0
  169. data/spec/extensions/json_serializer_spec.rb +4 -5
  170. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  171. data/spec/extensions/list_spec.rb +17 -39
  172. data/spec/extensions/many_through_many_spec.rb +283 -250
  173. data/spec/extensions/migration_spec.rb +35 -32
  174. data/spec/extensions/named_timezones_spec.rb +23 -3
  175. data/spec/extensions/nested_attributes_spec.rb +97 -88
  176. data/spec/extensions/optimistic_locking_spec.rb +9 -20
  177. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  178. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  179. data/spec/extensions/prepared_statements_spec.rb +53 -0
  180. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  181. data/spec/extensions/pretty_table_spec.rb +1 -6
  182. data/spec/extensions/rcte_tree_spec.rb +41 -43
  183. data/spec/extensions/schema_dumper_spec.rb +5 -8
  184. data/spec/extensions/schema_spec.rb +13 -21
  185. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  186. data/spec/extensions/serialization_spec.rb +72 -43
  187. data/spec/extensions/sharding_spec.rb +66 -140
  188. data/spec/extensions/single_table_inheritance_spec.rb +18 -29
  189. data/spec/extensions/spec_helper.rb +13 -61
  190. data/spec/extensions/sql_expr_spec.rb +20 -60
  191. data/spec/extensions/tactical_eager_loading_spec.rb +9 -19
  192. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  193. data/spec/extensions/timestamps_spec.rb +6 -6
  194. data/spec/extensions/to_dot_spec.rb +4 -7
  195. data/spec/extensions/touch_spec.rb +13 -14
  196. data/spec/extensions/tree_spec.rb +11 -26
  197. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  198. data/spec/extensions/update_primary_key_spec.rb +30 -24
  199. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  200. data/spec/extensions/validation_helpers_spec.rb +16 -35
  201. data/spec/extensions/xml_serializer_spec.rb +16 -4
  202. data/spec/integration/associations_test.rb +537 -21
  203. data/spec/integration/database_test.rb +4 -2
  204. data/spec/integration/dataset_test.rb +461 -65
  205. data/spec/integration/eager_loader_test.rb +19 -21
  206. data/spec/integration/model_test.rb +80 -1
  207. data/spec/integration/plugin_test.rb +304 -116
  208. data/spec/integration/prepared_statement_test.rb +212 -122
  209. data/spec/integration/schema_test.rb +161 -30
  210. data/spec/integration/spec_helper.rb +39 -30
  211. data/spec/integration/timezone_test.rb +38 -12
  212. data/spec/integration/transaction_test.rb +172 -5
  213. data/spec/integration/type_test.rb +17 -3
  214. data/spec/model/association_reflection_spec.rb +201 -7
  215. data/spec/model/associations_spec.rb +865 -662
  216. data/spec/model/base_spec.rb +275 -95
  217. data/spec/model/dataset_methods_spec.rb +7 -27
  218. data/spec/model/eager_loading_spec.rb +677 -676
  219. data/spec/model/hooks_spec.rb +309 -67
  220. data/spec/model/model_spec.rb +222 -168
  221. data/spec/model/plugins_spec.rb +24 -13
  222. data/spec/model/record_spec.rb +381 -218
  223. data/spec/model/spec_helper.rb +13 -71
  224. data/spec/model/validations_spec.rb +11 -0
  225. metadata +115 -73
@@ -241,16 +241,6 @@ module Sequel
241
241
  self << drop_language_sql(name, opts)
242
242
  end
243
243
 
244
- # Remove the cached entries for primary keys and sequences when dropping a table.
245
- def drop_table(*names)
246
- names.each do |name|
247
- name = quote_schema_table(name)
248
- @primary_keys.delete(name)
249
- @primary_key_sequences.delete(name)
250
- end
251
- super
252
- end
253
-
254
244
  # Drops a trigger from the database. Arguments:
255
245
  # * table : table from which to drop the trigger
256
246
  # * name : name of the trigger to drop
@@ -294,6 +284,16 @@ module Sequel
294
284
  dataset.from(:pg_class).join(:pg_locks, :relation=>:relfilenode).select(:pg_class__relname, Sequel::SQL::ColumnAll.new(:pg_locks))
295
285
  end
296
286
 
287
+ # Notifies the given channel. See the PostgreSQL NOTIFY documentation. Options:
288
+ #
289
+ # :payload :: The payload string to use for the NOTIFY statement. Only supported
290
+ # in PostgreSQL 9.0+.
291
+ # :server :: The server to which to send the NOTIFY statement, if the sharding support
292
+ # is being used.
293
+ def notify(channel, opts={})
294
+ execute_ddl("NOTIFY #{channel}#{", #{literal(opts[:payload].to_s)}" if opts[:payload]}", opts)
295
+ end
296
+
297
297
  # Return primary key for the given table.
298
298
  def primary_key(table, opts={})
299
299
  quoted_table = quote_schema_table(table)
@@ -345,8 +345,11 @@ module Sequel
345
345
  (conn.server_version rescue nil) if conn.respond_to?(:server_version)
346
346
  end
347
347
  unless @server_version
348
- m = /PostgreSQL (\d+)\.(\d+)(?:(?:rc\d+)|\.(\d+))?/.match(fetch('SELECT version()').single_value)
349
- @server_version = (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
348
+ @server_version = if m = /PostgreSQL (\d+)\.(\d+)(?:(?:rc\d+)|\.(\d+))?/.match(fetch('SELECT version()').single_value)
349
+ (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
350
+ else
351
+ 0
352
+ end
350
353
  end
351
354
  @server_version
352
355
  end
@@ -358,6 +361,11 @@ module Sequel
358
361
  @supports_prepared_transactions = self['SHOW max_prepared_transactions'].get.to_i > 0
359
362
  end
360
363
 
364
+ # PostgreSQL supports CREATE TABLE IF NOT EXISTS on 9.1+
365
+ def supports_create_table_if_not_exists?
366
+ server_version >= 90100
367
+ end
368
+
361
369
  # PostgreSQL supports savepoints
362
370
  def supports_savepoints?
363
371
  true
@@ -368,30 +376,24 @@ module Sequel
368
376
  true
369
377
  end
370
378
 
371
- # Whether the given table exists in the database
379
+ # Array of symbols specifying table names in the current database.
380
+ # The dataset used is yielded to the block if one is provided,
381
+ # otherwise, an array of symbols of table names is returned.
372
382
  #
373
383
  # Options:
374
384
  # * :schema - The schema to search (default_schema by default)
375
385
  # * :server - The server to use
376
- def table_exists?(table, opts={})
377
- im = input_identifier_meth
378
- schema, table = schema_and_table(table)
379
- opts[:schema] ||= schema
380
- tables(opts){|ds| !ds.first(:relname=>im.call(table)).nil?}
386
+ def tables(opts={}, &block)
387
+ pg_class_relname('r', opts, &block)
381
388
  end
382
-
383
- # Array of symbols specifying table names in the current database.
384
- # The dataset used is yielded to the block if one is provided,
385
- # otherwise, an array of symbols of table names is returned.
389
+
390
+ # Array of symbols specifying view names in the current database.
386
391
  #
387
392
  # Options:
388
393
  # * :schema - The schema to search (default_schema by default)
389
394
  # * :server - The server to use
390
- def tables(opts={})
391
- ds = metadata_dataset.from(:pg_class).filter(:relkind=>'r').select(:relname).exclude(SQL::StringExpression.like(:relname, SYSTEM_TABLE_REGEXP)).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
392
- ds = filter_schema(ds, opts)
393
- m = output_identifier_meth
394
- block_given? ? yield(ds) : ds.map{|r| m.call(r[:relname])}
395
+ def views(opts={})
396
+ pg_class_relname('v', opts)
395
397
  end
396
398
 
397
399
  private
@@ -399,8 +401,8 @@ module Sequel
399
401
  # If the :prepare option is given and we aren't in a savepoint,
400
402
  # prepare the transaction for a two-phase commit.
401
403
  def commit_transaction(conn, opts={})
402
- if opts[:prepare] && Thread.current[:sequel_transaction_depth] <= 1
403
- log_connection_execute(conn, "PREPARE TRANSACTION #{literal(opts[:prepare])}")
404
+ if (s = opts[:prepare]) && @transactions[conn][:savepoint_level] <= 1
405
+ log_connection_execute(conn, "PREPARE TRANSACTION #{literal(s)}")
404
406
  else
405
407
  super
406
408
  end
@@ -454,11 +456,6 @@ module Sequel
454
456
  "DROP LANGUAGE#{' IF EXISTS' if opts[:if_exists]} #{name}#{' CASCADE' if opts[:cascade]}"
455
457
  end
456
458
 
457
- # Always CASCADE the table drop
458
- def drop_table_sql(name)
459
- "DROP TABLE #{quote_schema_table(name)} CASCADE"
460
- end
461
-
462
459
  # SQL for dropping a trigger from the database.
463
460
  def drop_trigger_sql(table, name, opts={})
464
461
  "DROP TRIGGER#{' IF EXISTS' if opts[:if_exists]} #{name} ON #{quote_schema_table(table)}#{' CASCADE' if opts[:cascade]}"
@@ -499,7 +496,7 @@ module Sequel
499
496
  filter = " WHERE #{filter_expr(filter)}" if filter
500
497
  case index_type
501
498
  when :full_text
502
- expr = "(to_tsvector(#{literal(index[:language] || 'simple')}, #{dataset.send(:full_text_string_join, cols)}))"
499
+ expr = "(to_tsvector(#{literal(index[:language] || 'simple')}, #{literal(dataset.send(:full_text_string_join, cols))}))"
503
500
  index_type = :gin
504
501
  when :spatial
505
502
  index_type = :gist
@@ -540,12 +537,29 @@ module Sequel
540
537
  conn.execute(sql)
541
538
  end
542
539
 
540
+ # Backbone of the tables and views support.
541
+ def pg_class_relname(type, opts)
542
+ ds = metadata_dataset.from(:pg_class).filter(:relkind=>type).select(:relname).exclude(SQL::StringExpression.like(:relname, SYSTEM_TABLE_REGEXP)).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
543
+ ds = filter_schema(ds, opts)
544
+ m = output_identifier_meth
545
+ block_given? ? yield(ds) : ds.map{|r| m.call(r[:relname])}
546
+ end
547
+
543
548
  # Use a dollar sign instead of question mark for the argument
544
549
  # placeholder.
545
550
  def prepared_arg_placeholder
546
551
  PREPARED_ARG_PLACEHOLDER
547
552
  end
548
553
 
554
+ # Remove the cached entries for primary keys and sequences when a table is
555
+ # changed.
556
+ def remove_cached_schema(table)
557
+ tab = quote_schema_table(table)
558
+ @primary_keys.delete(tab)
559
+ @primary_key_sequences.delete(tab)
560
+ super
561
+ end
562
+
549
563
  # SQL DDL statement for renaming a table. PostgreSQL doesn't allow you to change a table's schema in
550
564
  # a rename table operation, so speciying a new schema in new_name will not have an effect.
551
565
  def rename_table_sql(name, new_name)
@@ -560,13 +574,14 @@ module Sequel
560
574
 
561
575
  # The dataset used for parsing table schemas, using the pg_* system catalogs.
562
576
  def schema_parse_table(table_name, opts)
563
- m = output_identifier_meth
564
- m2 = input_identifier_meth
577
+ m = output_identifier_meth(opts[:dataset])
578
+ m2 = input_identifier_meth(opts[:dataset])
565
579
  ds = metadata_dataset.select(:pg_attribute__attname___name,
566
580
  SQL::Function.new(:format_type, :pg_type__oid, :pg_attribute__atttypmod).as(:db_type),
567
581
  SQL::Function.new(:pg_get_expr, :pg_attrdef__adbin, :pg_class__oid).as(:default),
568
582
  SQL::BooleanExpression.new(:NOT, :pg_attribute__attnotnull).as(:allow_null),
569
- SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(:pg_attribute__attnum => SQL::Function.new(:ANY, :pg_index__indkey)), false).as(:primary_key)).
583
+ SQL::Function.new(:COALESCE, SQL::BooleanExpression.from_value_pairs(:pg_attribute__attnum => SQL::Function.new(:ANY, :pg_index__indkey)), false).as(:primary_key),
584
+ :pg_namespace__nspname).
570
585
  from(:pg_class).
571
586
  join(:pg_attribute, :attrelid=>:oid).
572
587
  join(:pg_type, :oid=>:atttypid).
@@ -578,7 +593,16 @@ module Sequel
578
593
  filter(:pg_class__relname=>m2.call(table_name)).
579
594
  order(:pg_attribute__attnum)
580
595
  ds = filter_schema(ds, opts)
596
+ current_schema = nil
581
597
  ds.map do |row|
598
+ sch = row.delete(:nspname)
599
+ if current_schema
600
+ if sch != current_schema
601
+ raise Error, "columns from tables in two separate schema were returned (please specify a schema): #{current_schema.inspect}, #{sch.inspect}"
602
+ end
603
+ else
604
+ current_schema = sch
605
+ end
582
606
  row[:default] = nil if blank_object?(row[:default])
583
607
  row[:type] = schema_column_type(row[:db_type])
584
608
  [m.call(row.delete(:name)), row]
@@ -627,35 +651,59 @@ module Sequel
627
651
  BOOL_FALSE = 'false'.freeze
628
652
  BOOL_TRUE = 'true'.freeze
629
653
  COMMA_SEPARATOR = ', '.freeze
630
- DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from using where')
654
+ DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'delete from using where')
655
+ DELETE_CLAUSE_METHODS_91 = Dataset.clause_methods(:delete, %w'with delete from using where returning')
631
656
  EXCLUSIVE = 'EXCLUSIVE'.freeze
632
657
  EXPLAIN = 'EXPLAIN '.freeze
633
658
  EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
634
659
  FOR_SHARE = ' FOR SHARE'.freeze
660
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'insert into columns values')
661
+ INSERT_CLAUSE_METHODS_82 = Dataset.clause_methods(:insert, %w'insert into columns values returning')
662
+ INSERT_CLAUSE_METHODS_91 = Dataset.clause_methods(:insert, %w'with insert into columns values returning')
635
663
  LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
636
664
  NULL = LiteralString.new('NULL').freeze
637
665
  PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
638
666
  QUERY_PLAN = 'QUERY PLAN'.to_sym
639
667
  ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
640
668
  ROW_SHARE = 'ROW SHARE'.freeze
641
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit lock')
642
- SELECT_CLAUSE_METHODS_84 = Dataset.clause_methods(:select, %w'with distinct columns from join where group having window compounds order limit lock')
669
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit lock')
670
+ SELECT_CLAUSE_METHODS_84 = Dataset.clause_methods(:select, %w'with select distinct columns from join where group having window compounds order limit lock')
643
671
  SHARE = 'SHARE'.freeze
644
672
  SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
645
673
  SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
646
674
  SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
647
- UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set from where')
675
+ UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'update table set from where')
676
+ UPDATE_CLAUSE_METHODS_91 = Dataset.clause_methods(:update, %w'with update table set from where returning')
677
+ SPACE = Dataset::SPACE
678
+ FROM = Dataset::FROM
679
+ APOS = Dataset::APOS
680
+ APOS_RE = Dataset::APOS_RE
681
+ DOUBLE_APOS = Dataset::DOUBLE_APOS
682
+ PAREN_OPEN = Dataset::PAREN_OPEN
683
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
684
+ COMMA = Dataset::COMMA
685
+ AS = Dataset::AS
686
+ XOR_OP = ' # '.freeze
687
+ CRLF = "\r\n".freeze
688
+ BLOB_RE = /[\000-\037\047\134\177-\377]/n.freeze
689
+ WINDOW = " WINDOW ".freeze
690
+ EMPTY_STRING = ''.freeze
648
691
 
649
692
  # Shared methods for prepared statements when used with PostgreSQL databases.
650
693
  module PreparedStatementMethods
651
694
  # Override insert action to use RETURNING if the server supports it.
695
+ def run(&block)
696
+ if @prepared_type == :insert && supports_insert_select?
697
+ fetch_rows(prepared_sql){|r| return r.values.first}
698
+ else
699
+ super
700
+ end
701
+ end
702
+
652
703
  def prepared_sql
653
704
  return @prepared_sql if @prepared_sql
705
+ @opts[:returning] = insert_pk if @prepared_type == :insert && supports_insert_select?
654
706
  super
655
- if @prepared_type == :insert and !@opts[:disable_insert_returning] and server_version >= 80200
656
- @prepared_sql = insert_returning_pk_sql(*@prepared_modify_values)
657
- meta_def(:insert_returning_pk_sql){|*args| prepared_sql}
658
- end
659
707
  @prepared_sql
660
708
  end
661
709
  end
@@ -677,10 +725,16 @@ module Sequel
677
725
 
678
726
  # Handle converting the ruby xor operator (^) into the
679
727
  # PostgreSQL xor operator (#).
680
- def complex_expression_sql(op, args)
728
+ def complex_expression_sql_append(sql, op, args)
681
729
  case op
682
730
  when :^
683
- "(#{literal(args.at(0))} # #{literal(args.at(1))})"
731
+ j = XOR_OP
732
+ c = false
733
+ args.each do |a|
734
+ sql << j if c
735
+ literal_append(sql, a)
736
+ c ||= true
737
+ end
684
738
  else
685
739
  super
686
740
  end
@@ -693,7 +747,7 @@ module Sequel
693
747
 
694
748
  # Return the results of an EXPLAIN query as a string
695
749
  def explain(opts={})
696
- with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join("\r\n")
750
+ with_sql((opts[:analyze] ? EXPLAIN_ANALYZE : EXPLAIN) + select_sql).map(QUERY_PLAN).join(CRLF)
697
751
  end
698
752
 
699
753
  # Return a cloned dataset which will use FOR SHARE to lock returned rows.
@@ -705,29 +759,34 @@ module Sequel
705
759
  # in 8.3 by default, and available for earlier versions as an add-on).
706
760
  def full_text_search(cols, terms, opts = {})
707
761
  lang = opts[:language] || 'simple'
708
- filter("to_tsvector(#{literal(lang)}, #{full_text_string_join(cols)}) @@ to_tsquery(#{literal(lang)}, #{literal(Array(terms).join(' | '))})")
762
+ terms = terms.join(' | ') if terms.is_a?(Array)
763
+ filter("to_tsvector(?, ?) @@ to_tsquery(?, ?)", lang, full_text_string_join(cols), lang, terms)
709
764
  end
710
765
 
711
766
  # Insert given values into the database.
712
- def insert(*values)
713
- if @opts[:sql]
714
- execute_insert(insert_sql(*values))
715
- elsif @opts[:disable_insert_returning] || server_version < 80200
716
- execute_insert(insert_sql(*values), :table=>opts[:from].first, :values=>values.size == 1 ? values.first : values)
767
+ def insert(*values, &block)
768
+ if @opts[:returning]
769
+ super
770
+ elsif !@opts[:sql] && supports_insert_select?
771
+ returning(insert_pk).insert(*values){|r| return r.values.first}
772
+ elsif (f = opts[:from]) && !f.empty?
773
+ v = if values.size == 1
774
+ values.first
775
+ elsif values.size == 2 && values.all?{|v0| v0.is_a?(Array)}
776
+ Hash[*values.first.zip(values.last).flatten]
777
+ else
778
+ values
779
+ end
780
+ execute_insert(insert_sql(*values), :table=>f.first, :values=>v)
717
781
  else
718
- clone(default_server_opts(:sql=>insert_returning_pk_sql(*values))).single_value
782
+ super
719
783
  end
720
784
  end
721
785
 
722
- # Use the RETURNING clause to return the columns listed in returning.
723
- def insert_returning_sql(returning, *values)
724
- "#{insert_sql(*values)} RETURNING #{column_list(Array(returning))}"
725
- end
726
-
727
786
  # Insert a record returning the record inserted
728
787
  def insert_select(*values)
729
- return if opts[:disable_insert_returning] || server_version < 80200
730
- naked.clone(default_server_opts(:sql=>insert_returning_sql(nil, *values))).single_record
788
+ return unless supports_insert_select?
789
+ returning.insert(*values){|r| return r}
731
790
  end
732
791
 
733
792
  # Locks all tables in the dataset's FROM clause (but not in JOINs) with
@@ -749,9 +808,17 @@ module Sequel
749
808
  return super if server_version < 80200
750
809
 
751
810
  # postgresql 8.2 introduces support for multi-row insert
752
- [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
811
+ sql = LiteralString.new('VALUES ')
812
+ expression_list_append(sql, values.map{|r| Array(r)})
813
+ [insert_sql(columns, sql)]
753
814
  end
754
815
 
816
+ # PostgreSQL supports using the WITH clause in subqueries if it
817
+ # supports using WITH at all (i.e. on PostgreSQL 8.4+).
818
+ def supports_cte_in_subqueries?
819
+ supports_cte?
820
+ end
821
+
755
822
  # DISTINCT ON is a PostgreSQL extension
756
823
  def supports_distinct_on?
757
824
  true
@@ -762,6 +829,14 @@ module Sequel
762
829
  true
763
830
  end
764
831
 
832
+ def supports_returning?(type)
833
+ if type == :insert
834
+ server_version >= 80200 && !opts[:disable_insert_returning]
835
+ else
836
+ server_version >= 80200
837
+ end
838
+ end
839
+
765
840
  # PostgreSQL supports timezones in literal timestamps
766
841
  def supports_timestamp_timezones?
767
842
  true
@@ -777,16 +852,40 @@ module Sequel
777
852
  clone(:window=>(@opts[:window]||[]) + [[name, SQL::Window.new(opts)]])
778
853
  end
779
854
 
855
+ protected
856
+
857
+ # If returned primary keys are requested, use RETURNING unless already set on the
858
+ # dataset. If RETURNING is already set, use existing returning values. If RETURNING
859
+ # is only set to return a single columns, return an array of just that column.
860
+ # Otherwise, return an array of hashes.
861
+ def _import(columns, values, opts={})
862
+ if server_version >= 80200
863
+ if opts[:return] == :primary_key && !@opts[:returning]
864
+ returning(insert_pk)._import(columns, values, opts)
865
+ elsif @opts[:returning]
866
+ statements = multi_insert_sql(columns, values)
867
+ @db.transaction(opts.merge(:server=>@opts[:server])) do
868
+ statements.map{|st| returning_fetch_rows(st)}
869
+ end.first.map{|v| v.length == 1 ? v.values.first : v}
870
+ else
871
+ super
872
+ end
873
+ else
874
+ super
875
+ end
876
+ end
877
+
780
878
  private
781
879
 
782
880
  # PostgreSQL allows deleting from joined datasets
783
881
  def delete_clause_methods
784
- DELETE_CLAUSE_METHODS
882
+ server_version >= 90100 ? DELETE_CLAUSE_METHODS_91 : DELETE_CLAUSE_METHODS
785
883
  end
786
884
 
787
885
  # Only include the primary table in the main delete clause
788
886
  def delete_from_sql(sql)
789
- sql << " FROM #{source_list(@opts[:from][0..0])}"
887
+ sql << FROM
888
+ source_list_append(sql, @opts[:from][0..0])
790
889
  end
791
890
 
792
891
  # Use USING to specify additional tables in a delete query
@@ -794,26 +893,38 @@ module Sequel
794
893
  join_from_sql(:USING, sql)
795
894
  end
796
895
 
797
- # Use the RETURNING clause to return the primary key of the inserted record, if it exists
798
- def insert_returning_pk_sql(*values)
896
+ # PostgreSQL allows a RETURNING clause.
897
+ def insert_clause_methods
898
+ if server_version >= 90100
899
+ INSERT_CLAUSE_METHODS_91
900
+ elsif server_version >= 80200
901
+ INSERT_CLAUSE_METHODS_82
902
+ else
903
+ INSERT_CLAUSE_METHODS
904
+ end
905
+ end
906
+
907
+ # Return the primary key to use for RETURNING in an INSERT statement
908
+ def insert_pk
799
909
  pk = db.primary_key(opts[:from].first) if opts[:from] && !opts[:from].empty?
800
- insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : NULL, *values)
910
+ pk ? Sequel::SQL::Identifier.new(pk) : NULL
801
911
  end
802
-
912
+
803
913
  # For multiple table support, PostgreSQL requires at least
804
914
  # two from tables, with joins allowed.
805
915
  def join_from_sql(type, sql)
806
916
  if(from = @opts[:from][1..-1]).empty?
807
917
  raise(Error, 'Need multiple FROM tables if updating/deleting a dataset with JOINs') if @opts[:join]
808
918
  else
809
- sql << " #{type} #{source_list(from)}"
919
+ sql << SPACE << type.to_s << SPACE
920
+ source_list_append(sql, from)
810
921
  select_join_sql(sql)
811
922
  end
812
923
  end
813
924
 
814
925
  # Use a generic blob quoting method, hopefully overridden in one of the subadapter methods
815
- def literal_blob(v)
816
- "'#{v.gsub(/[\000-\037\047\134\177-\377]/n){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"}}'"
926
+ def literal_blob_append(sql, v)
927
+ sql << APOS << v.gsub(BLOB_RE){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"} << APOS
817
928
  end
818
929
 
819
930
  # PostgreSQL uses FALSE for false values
@@ -822,8 +933,8 @@ module Sequel
822
933
  end
823
934
 
824
935
  # Assume that SQL standard quoting is on, per Sequel's defaults
825
- def literal_string(v)
826
- "'#{v.gsub("'", "''")}'"
936
+ def literal_string_append(sql, v)
937
+ sql << APOS << v.gsub(APOS_RE, DOUBLE_APOS) << APOS
827
938
  end
828
939
 
829
940
  # PostgreSQL uses FALSE for false values
@@ -836,6 +947,14 @@ module Sequel
836
947
  server_version >= 80400 ? SELECT_CLAUSE_METHODS_84 : SELECT_CLAUSE_METHODS
837
948
  end
838
949
 
950
+ # PostgreSQL requires parentheses around compound datasets if they use
951
+ # CTEs, and using them in other places doesn't hurt.
952
+ def compound_dataset_sql_append(sql, ds)
953
+ sql << PAREN_OPEN
954
+ super
955
+ sql << PAREN_CLOSE
956
+ end
957
+
839
958
  # Support FOR SHARE locking when using the :share lock style.
840
959
  def select_lock_sql(sql)
841
960
  @opts[:lock] == :share ? (sql << FOR_SHARE) : super
@@ -843,14 +962,26 @@ module Sequel
843
962
 
844
963
  # SQL fragment for named window specifications
845
964
  def select_window_sql(sql)
846
- sql << " WINDOW #{@opts[:window].map{|name, window| "#{literal(name)} AS #{literal(window)}"}.join(', ')}" if @opts[:window]
965
+ if ws = @opts[:window]
966
+ sql << WINDOW
967
+ c = false
968
+ co = COMMA
969
+ as = AS
970
+ ws.map do |name, window|
971
+ sql << co if c
972
+ literal_append(sql, name)
973
+ sql << as
974
+ literal_append(sql, window)
975
+ c ||= true
976
+ end
977
+ end
847
978
  end
848
979
 
849
980
  # Use WITH RECURSIVE instead of WITH if any of the CTEs is recursive
850
981
  def select_with_sql_base
851
982
  opts[:with].any?{|w| w[:recursive]} ? SQL_WITH_RECURSIVE : super
852
983
  end
853
-
984
+
854
985
  # The version of the database server
855
986
  def server_version
856
987
  db.server_version(@opts[:server])
@@ -858,15 +989,15 @@ module Sequel
858
989
 
859
990
  # Concatenate the expressions with a space in between
860
991
  def full_text_string_join(cols)
861
- cols = Array(cols).map{|x| SQL::Function.new(:COALESCE, x, '')}
862
- cols = cols.zip([' '] * cols.length).flatten
992
+ cols = Array(cols).map{|x| SQL::Function.new(:COALESCE, x, EMPTY_STRING)}
993
+ cols = cols.zip([SPACE] * cols.length).flatten
863
994
  cols.pop
864
- literal(SQL::StringExpression.new(:'||', *cols))
995
+ SQL::StringExpression.new(:'||', *cols)
865
996
  end
866
997
 
867
998
  # PostgreSQL splits the main table from the joined tables
868
999
  def update_clause_methods
869
- UPDATE_CLAUSE_METHODS
1000
+ server_version >= 90100 ? UPDATE_CLAUSE_METHODS_91 : UPDATE_CLAUSE_METHODS
870
1001
  end
871
1002
 
872
1003
  # Use FROM to specify additional tables in an update query
@@ -876,7 +1007,8 @@ module Sequel
876
1007
 
877
1008
  # Only include the primary table in the main update clause
878
1009
  def update_table_sql(sql)
879
- sql << " #{source_list(@opts[:from][0..0])}"
1010
+ sql << SPACE
1011
+ source_list_append(sql, @opts[:from][0..0])
880
1012
  end
881
1013
  end
882
1014
  end
@@ -6,12 +6,6 @@ module Sequel
6
6
  def database_type
7
7
  :progress
8
8
  end
9
-
10
- def dataset(opts = nil)
11
- ds = super
12
- ds.extend(DatasetMethods)
13
- ds
14
- end
15
9
  end
16
10
 
17
11
  module DatasetMethods
@@ -37,7 +31,10 @@ module Sequel
37
31
  # The Progress adapter targets Progress 9, so it silently ignores the option.
38
32
  def select_limit_sql(sql)
39
33
  raise(Error, "OFFSET not supported") if @opts[:offset]
40
- #sql << " TOP #{@opts[:limit]}" if @opts[:limit]
34
+ # if l = @opts[:limit]
35
+ # sql << " TOP "
36
+ # literal_append(sql, l)
37
+ # end
41
38
  end
42
39
  end
43
40
  end