sequel 3.21.0 → 3.29.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 (200) hide show
  1. data/CHANGELOG +413 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +23 -16
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +210 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/mass_assignment.rdoc +54 -0
  8. data/doc/migration.rdoc +15 -538
  9. data/doc/model_hooks.rdoc +72 -27
  10. data/doc/opening_databases.rdoc +86 -50
  11. data/doc/prepared_statements.rdoc +40 -13
  12. data/doc/reflection.rdoc +8 -2
  13. data/doc/release_notes/3.22.0.txt +39 -0
  14. data/doc/release_notes/3.23.0.txt +172 -0
  15. data/doc/release_notes/3.24.0.txt +420 -0
  16. data/doc/release_notes/3.25.0.txt +88 -0
  17. data/doc/release_notes/3.26.0.txt +88 -0
  18. data/doc/release_notes/3.27.0.txt +82 -0
  19. data/doc/release_notes/3.28.0.txt +304 -0
  20. data/doc/release_notes/3.29.0.txt +459 -0
  21. data/doc/schema_modification.rdoc +547 -0
  22. data/doc/sharding.rdoc +7 -1
  23. data/doc/testing.rdoc +115 -0
  24. data/doc/transactions.rdoc +137 -0
  25. data/doc/virtual_rows.rdoc +2 -2
  26. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  27. data/lib/sequel/adapters/ado.rb +40 -18
  28. data/lib/sequel/adapters/amalgalite.rb +15 -7
  29. data/lib/sequel/adapters/db2.rb +175 -86
  30. data/lib/sequel/adapters/dbi.rb +15 -15
  31. data/lib/sequel/adapters/do/mysql.rb +0 -5
  32. data/lib/sequel/adapters/do/postgres.rb +0 -5
  33. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  34. data/lib/sequel/adapters/do.rb +17 -36
  35. data/lib/sequel/adapters/firebird.rb +27 -208
  36. data/lib/sequel/adapters/ibmdb.rb +448 -0
  37. data/lib/sequel/adapters/informix.rb +6 -23
  38. data/lib/sequel/adapters/jdbc/as400.rb +5 -31
  39. data/lib/sequel/adapters/jdbc/db2.rb +44 -0
  40. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  41. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  42. data/lib/sequel/adapters/jdbc/h2.rb +26 -17
  43. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  44. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  45. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  46. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  47. data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
  48. data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
  49. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
  50. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  51. data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
  52. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  53. data/lib/sequel/adapters/jdbc.rb +107 -38
  54. data/lib/sequel/adapters/mock.rb +315 -0
  55. data/lib/sequel/adapters/mysql.rb +78 -182
  56. data/lib/sequel/adapters/mysql2.rb +24 -23
  57. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  58. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  59. data/lib/sequel/adapters/odbc.rb +28 -9
  60. data/lib/sequel/adapters/openbase.rb +2 -4
  61. data/lib/sequel/adapters/oracle.rb +349 -51
  62. data/lib/sequel/adapters/postgres.rb +169 -21
  63. data/lib/sequel/adapters/shared/access.rb +21 -6
  64. data/lib/sequel/adapters/shared/db2.rb +288 -0
  65. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  66. data/lib/sequel/adapters/shared/informix.rb +45 -0
  67. data/lib/sequel/adapters/shared/mssql.rb +108 -65
  68. data/lib/sequel/adapters/shared/mysql.rb +56 -13
  69. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  70. data/lib/sequel/adapters/shared/oracle.rb +185 -34
  71. data/lib/sequel/adapters/shared/postgres.rb +107 -58
  72. data/lib/sequel/adapters/shared/progress.rb +0 -6
  73. data/lib/sequel/adapters/shared/sqlite.rb +168 -41
  74. data/lib/sequel/adapters/sqlite.rb +27 -18
  75. data/lib/sequel/adapters/swift/mysql.rb +9 -5
  76. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  77. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  78. data/lib/sequel/adapters/swift.rb +5 -5
  79. data/lib/sequel/adapters/tinytds.rb +152 -17
  80. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
  81. data/lib/sequel/ast_transformer.rb +190 -0
  82. data/lib/sequel/connection_pool/threaded.rb +3 -2
  83. data/lib/sequel/connection_pool.rb +1 -1
  84. data/lib/sequel/core.rb +46 -5
  85. data/lib/sequel/database/connecting.rb +5 -6
  86. data/lib/sequel/database/dataset.rb +4 -4
  87. data/lib/sequel/database/dataset_defaults.rb +59 -1
  88. data/lib/sequel/database/logging.rb +1 -1
  89. data/lib/sequel/database/misc.rb +100 -19
  90. data/lib/sequel/database/query.rb +153 -58
  91. data/lib/sequel/database/schema_generator.rb +8 -5
  92. data/lib/sequel/database/schema_methods.rb +59 -29
  93. data/lib/sequel/dataset/actions.rb +194 -57
  94. data/lib/sequel/dataset/features.rb +81 -8
  95. data/lib/sequel/dataset/graph.rb +8 -7
  96. data/lib/sequel/dataset/misc.rb +50 -23
  97. data/lib/sequel/dataset/mutation.rb +5 -6
  98. data/lib/sequel/dataset/prepared_statements.rb +32 -15
  99. data/lib/sequel/dataset/query.rb +223 -42
  100. data/lib/sequel/dataset/sql.rb +58 -61
  101. data/lib/sequel/dataset.rb +8 -0
  102. data/lib/sequel/exceptions.rb +4 -0
  103. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  104. data/lib/sequel/extensions/migration.rb +6 -4
  105. data/lib/sequel/extensions/named_timezones.rb +5 -0
  106. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  107. data/lib/sequel/extensions/to_dot.rb +95 -83
  108. data/lib/sequel/model/associations.rb +997 -331
  109. data/lib/sequel/model/base.rb +386 -129
  110. data/lib/sequel/model/errors.rb +1 -1
  111. data/lib/sequel/model/exceptions.rb +5 -1
  112. data/lib/sequel/model.rb +15 -8
  113. data/lib/sequel/plugins/association_pks.rb +22 -4
  114. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  115. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  116. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  117. data/lib/sequel/plugins/force_encoding.rb +6 -6
  118. data/lib/sequel/plugins/identity_map.rb +114 -7
  119. data/lib/sequel/plugins/many_through_many.rb +65 -7
  120. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  121. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  122. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  123. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  124. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  125. data/lib/sequel/plugins/serialization.rb +6 -1
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  127. data/lib/sequel/plugins/sharding.rb +12 -25
  128. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  129. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  130. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  131. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  132. data/lib/sequel/sql.rb +107 -51
  133. data/lib/sequel/timezones.rb +44 -35
  134. data/lib/sequel/version.rb +1 -1
  135. data/spec/adapters/db2_spec.rb +146 -0
  136. data/spec/adapters/mssql_spec.rb +65 -29
  137. data/spec/adapters/mysql_spec.rb +122 -123
  138. data/spec/adapters/oracle_spec.rb +48 -76
  139. data/spec/adapters/postgres_spec.rb +213 -61
  140. data/spec/adapters/spec_helper.rb +6 -5
  141. data/spec/adapters/sqlite_spec.rb +35 -21
  142. data/spec/core/connection_pool_spec.rb +71 -92
  143. data/spec/core/core_sql_spec.rb +20 -31
  144. data/spec/core/database_spec.rb +729 -508
  145. data/spec/core/dataset_spec.rb +980 -1044
  146. data/spec/core/expression_filters_spec.rb +159 -42
  147. data/spec/core/mock_adapter_spec.rb +378 -0
  148. data/spec/core/object_graph_spec.rb +48 -114
  149. data/spec/core/schema_generator_spec.rb +3 -3
  150. data/spec/core/schema_spec.rb +298 -38
  151. data/spec/core/spec_helper.rb +6 -48
  152. data/spec/extensions/association_pks_spec.rb +38 -0
  153. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  154. data/spec/extensions/columns_introspection_spec.rb +91 -0
  155. data/spec/extensions/dataset_associations_spec.rb +199 -0
  156. data/spec/extensions/defaults_setter_spec.rb +64 -0
  157. data/spec/extensions/identity_map_spec.rb +162 -0
  158. data/spec/extensions/instance_hooks_spec.rb +71 -0
  159. data/spec/extensions/many_through_many_spec.rb +195 -20
  160. data/spec/extensions/migration_spec.rb +17 -17
  161. data/spec/extensions/named_timezones_spec.rb +22 -2
  162. data/spec/extensions/nested_attributes_spec.rb +4 -0
  163. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  164. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  165. data/spec/extensions/prepared_statements_spec.rb +72 -0
  166. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  167. data/spec/extensions/schema_dumper_spec.rb +2 -2
  168. data/spec/extensions/schema_spec.rb +13 -21
  169. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  170. data/spec/extensions/serialization_spec.rb +5 -8
  171. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  172. data/spec/extensions/spec_helper.rb +7 -1
  173. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  174. data/spec/extensions/to_dot_spec.rb +3 -5
  175. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  176. data/spec/extensions/xml_serializer_spec.rb +16 -4
  177. data/spec/integration/associations_test.rb +522 -21
  178. data/spec/integration/database_test.rb +4 -2
  179. data/spec/integration/dataset_test.rb +375 -62
  180. data/spec/integration/eager_loader_test.rb +19 -21
  181. data/spec/integration/model_test.rb +80 -1
  182. data/spec/integration/plugin_test.rb +304 -116
  183. data/spec/integration/prepared_statement_test.rb +200 -120
  184. data/spec/integration/schema_test.rb +161 -30
  185. data/spec/integration/spec_helper.rb +39 -30
  186. data/spec/integration/timezone_test.rb +38 -12
  187. data/spec/integration/transaction_test.rb +172 -5
  188. data/spec/integration/type_test.rb +17 -3
  189. data/spec/model/association_reflection_spec.rb +174 -7
  190. data/spec/model/associations_spec.rb +849 -661
  191. data/spec/model/base_spec.rb +255 -95
  192. data/spec/model/dataset_methods_spec.rb +7 -27
  193. data/spec/model/eager_loading_spec.rb +640 -676
  194. data/spec/model/hooks_spec.rb +309 -67
  195. data/spec/model/model_spec.rb +207 -167
  196. data/spec/model/plugins_spec.rb +24 -13
  197. data/spec/model/record_spec.rb +321 -218
  198. data/spec/model/spec_helper.rb +13 -71
  199. data/spec/model/validations_spec.rb +11 -0
  200. metadata +95 -38
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  # ---------------------
4
- # :section: User Methods relating to SQL Creation
4
+ # :section: 3 - User Methods relating to SQL Creation
5
5
  # These are methods you can call to see what SQL will be generated by the dataset.
6
6
  # ---------------------
7
7
 
@@ -62,6 +62,10 @@ module Sequel
62
62
  end
63
63
 
64
64
  columns = columns.map{|k| literal(String === k ? k.to_sym : k)}
65
+ if values.is_a?(Array) && values.empty? && !insert_supports_empty_values?
66
+ columns = [literal(columns().last)]
67
+ values = ['DEFAULT'.lit]
68
+ end
65
69
  clone(:columns=>columns, :values=>values)._insert_sql
66
70
  end
67
71
 
@@ -101,7 +105,7 @@ module Sequel
101
105
  when Array
102
106
  literal_array(v)
103
107
  when Time
104
- literal_time(v)
108
+ v.is_a?(SQLTime) ? literal_sqltime(v) : literal_time(v)
105
109
  when DateTime
106
110
  literal_datetime(v)
107
111
  when Date
@@ -163,7 +167,7 @@ module Sequel
163
167
  end
164
168
 
165
169
  # ---------------------
166
- # :section: Internal Methods relating to SQL Creation
170
+ # :section: 9 - Internal Methods relating to SQL Creation
167
171
  # These methods, while public, are not designed to be used directly by the end user.
168
172
  # ---------------------
169
173
 
@@ -212,7 +216,11 @@ module Sequel
212
216
 
213
217
  # SQL fragment for BooleanConstants
214
218
  def boolean_constant_sql(constant)
215
- literal(constant)
219
+ if (constant == true || constant == false) && !supports_where_true?
220
+ constant == true ? '(1 = 1)' : '(1 = 0)'
221
+ else
222
+ literal(constant)
223
+ end
216
224
  end
217
225
 
218
226
  # SQL fragment for CaseExpression
@@ -269,6 +277,7 @@ module Sequel
269
277
  literal(op == :IN ? expr : ~expr)
270
278
  else
271
279
  old_vals = vals
280
+ vals = vals.naked if vals.is_a?(Sequel::Dataset)
272
281
  vals = vals.to_a
273
282
  val_cols = old_vals.columns
274
283
  complex_expression_sql(op, [cols, vals.map!{|x| x.values_at(*val_cols)}])
@@ -300,6 +309,8 @@ module Sequel
300
309
  literal(args.at(0))
301
310
  when :'B~'
302
311
  "~#{literal(args.at(0))}"
312
+ when :extract
313
+ "extract(#{args.at(0)} FROM #{literal(args.at(1))})"
303
314
  else
304
315
  raise(InvalidOperation, "invalid operator #{op}")
305
316
  end
@@ -434,7 +445,7 @@ module Sequel
434
445
  when :all
435
446
  "ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING"
436
447
  when :rows
437
- "ROWS UNBOUNDED PRECEDING"
448
+ "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW"
438
449
  when String
439
450
  opts[:frame]
440
451
  else
@@ -547,6 +558,28 @@ module Sequel
547
558
  (columns.nil? || columns.empty?) ? WILDCARD : expression_list(columns)
548
559
  end
549
560
 
561
+ # Yield each two pair of arguments to the block, which should
562
+ # return a string representing the SQL code for those
563
+ # two arguments. If more than 2 arguments are provided, all
564
+ # calls to the block # after the first will have a LiteralString
565
+ # as the first argument, representing the application of the block to
566
+ # the previous arguments.
567
+ def complex_expression_arg_pairs(args)
568
+ case args.length
569
+ when 1
570
+ literal(args.at(0))
571
+ when 2
572
+ yield args.at(0), args.at(1)
573
+ else
574
+ args.inject{|m, a| LiteralString.new(yield(m, a))}
575
+ end
576
+ end
577
+
578
+ # The SQL to use for the dataset used in a UNION/INTERSECT/EXCEPT clause.
579
+ def compound_dataset_sql(ds)
580
+ subselect_sql(ds)
581
+ end
582
+
550
583
  # The alias to use for datasets, takes a number to make sure the name is unique.
551
584
  def dataset_alias(number)
552
585
  :"#{DATASET_ALIAS_BASE_NAME}#{number}"
@@ -573,7 +606,7 @@ module Sequel
573
606
  # database supports them), and override %z to always use a numeric offset
574
607
  # of hours and minutes.
575
608
  def format_timestamp(v)
576
- v2 = Sequel.application_to_database_timestamp(v)
609
+ v2 = db.from_application_timestamp(v)
577
610
  fmt = default_timestamp_format.gsub(/%[Nz]/) do |m|
578
611
  if m == '%N'
579
612
  format_timestamp_usec(v.is_a?(DateTime) ? v.sec_fraction*(RUBY_VERSION < '1.9.0' ? 86400000000 : 1000000) : v.usec) if supports_timestamp_usecs?
@@ -636,6 +669,15 @@ module Sequel
636
669
  end
637
670
  end
638
671
 
672
+ # SQL fragment specifying the values to return.
673
+ def insert_returning_sql(sql)
674
+ if opts.has_key?(:returning)
675
+ sql << " RETURNING #{column_list(Array(opts[:returning]))}"
676
+ end
677
+ end
678
+ alias delete_returning_sql insert_returning_sql
679
+ alias update_returning_sql insert_returning_sql
680
+
639
681
  # SQL fragment specifying a JOIN type, converts underscores to
640
682
  # spaces and upcases.
641
683
  def join_type_sql(join_type)
@@ -722,6 +764,11 @@ module Sequel
722
764
  end
723
765
  end
724
766
 
767
+ # SQL fragment for Sequel::SQLTime, containing just the time part
768
+ def literal_sqltime(v)
769
+ v.strftime("'%H:%M:%S#{format_timestamp_usec(v.usec) if supports_timestamp_usecs?}'")
770
+ end
771
+
725
772
  # SQL fragment for String. Doubles \ and ' by default.
726
773
  def literal_string(v)
727
774
  "'#{v.gsub(/\\/, "\\\\\\\\").gsub(/'/, "''")}'"
@@ -774,59 +821,7 @@ module Sequel
774
821
 
775
822
  # Qualify the given expression e to the given table.
776
823
  def qualified_expression(e, table)
777
- case e
778
- when Symbol
779
- t, column, aliaz = split_symbol(e)
780
- if t
781
- e
782
- elsif aliaz
783
- SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(table, SQL::Identifier.new(column)), aliaz)
784
- else
785
- SQL::QualifiedIdentifier.new(table, e)
786
- end
787
- when Array
788
- e.map{|a| qualified_expression(a, table)}
789
- when Hash
790
- h = {}
791
- e.each{|k,v| h[qualified_expression(k, table)] = qualified_expression(v, table)}
792
- h
793
- when SQL::Identifier
794
- SQL::QualifiedIdentifier.new(table, e)
795
- when SQL::OrderedExpression
796
- SQL::OrderedExpression.new(qualified_expression(e.expression, table), e.descending, :nulls=>e.nulls)
797
- when SQL::AliasedExpression
798
- SQL::AliasedExpression.new(qualified_expression(e.expression, table), e.aliaz)
799
- when SQL::CaseExpression
800
- args = [qualified_expression(e.conditions, table), qualified_expression(e.default, table)]
801
- args << qualified_expression(e.expression, table) if e.expression?
802
- SQL::CaseExpression.new(*args)
803
- when SQL::Cast
804
- SQL::Cast.new(qualified_expression(e.expr, table), e.type)
805
- when SQL::Function
806
- SQL::Function.new(e.f, *qualified_expression(e.args, table))
807
- when SQL::ComplexExpression
808
- SQL::ComplexExpression.new(e.op, *qualified_expression(e.args, table))
809
- when SQL::Subscript
810
- SQL::Subscript.new(qualified_expression(e.f, table), qualified_expression(e.sub, table))
811
- when SQL::WindowFunction
812
- SQL::WindowFunction.new(qualified_expression(e.function, table), qualified_expression(e.window, table))
813
- when SQL::Window
814
- o = e.opts.dup
815
- o[:partition] = qualified_expression(o[:partition], table) if o[:partition]
816
- o[:order] = qualified_expression(o[:order], table) if o[:order]
817
- SQL::Window.new(o)
818
- when SQL::PlaceholderLiteralString
819
- args = if e.args.is_a?(Hash)
820
- h = {}
821
- e.args.each{|k,v| h[k] = qualified_expression(v, table)}
822
- h
823
- else
824
- qualified_expression(e.args, table)
825
- end
826
- SQL::PlaceholderLiteralString.new(e.str, args, e.parens)
827
- else
828
- e
829
- end
824
+ Qualifier.new(self, table).transform(e)
830
825
  end
831
826
 
832
827
  # The order of methods to call to build the SELECT SQL statement
@@ -852,8 +847,7 @@ module Sequel
852
847
  def select_compounds_sql(sql)
853
848
  return unless @opts[:compounds]
854
849
  @opts[:compounds].each do |type, dataset, all|
855
- compound_sql = subselect_sql(dataset)
856
- sql << " #{type.to_s.upcase}#{' ALL' if all} #{compound_sql}"
850
+ sql << " #{type.to_s.upcase}#{' ALL' if all} #{compound_dataset_sql(dataset)}"
857
851
  end
858
852
  end
859
853
 
@@ -914,6 +908,9 @@ module Sequel
914
908
  return if !ws || ws.empty?
915
909
  sql.replace("#{select_with_sql_base}#{ws.map{|w| "#{quote_identifier(w[:name])}#{"(#{argument_list(w[:args])})" if w[:args]} AS #{literal_dataset(w[:dataset])}"}.join(COMMA_SEPARATOR)} #{sql}")
916
910
  end
911
+ alias delete_with_sql select_with_sql
912
+ alias insert_with_sql select_with_sql
913
+ alias update_with_sql select_with_sql
917
914
 
918
915
  # The base keyword to use for the SQL WITH clause
919
916
  def select_with_sql_base
@@ -26,6 +26,14 @@ module Sequel
26
26
  extend Metaprogramming
27
27
  include Metaprogramming
28
28
  include Enumerable
29
+ include SQL::AliasMethods
30
+ include SQL::BooleanMethods
31
+ include SQL::CastMethods
32
+ include SQL::ComplexExpressionMethods
33
+ include SQL::InequalityMethods
34
+ include SQL::NumericMethods
35
+ include SQL::OrderMethods
36
+ include SQL::StringMethods
29
37
  end
30
38
 
31
39
  require(%w"query actions features graph prepared_statements misc mutation sql", 'dataset')
@@ -44,6 +44,10 @@ module Sequel
44
44
  # and won't reraise it.
45
45
  class Rollback < Error ; end
46
46
 
47
+ # Exception that occurs when unbinding a dataset that has multiple different values
48
+ # for a given variable.
49
+ class UnbindDuplicate < Error; end
50
+
47
51
  class Error
48
52
  AdapterNotFound = Sequel::AdapterNotFound
49
53
  InvalidOperation = Sequel::InvalidOperation
@@ -0,0 +1,61 @@
1
+ # The columns_introspection extension attempts to introspect the
2
+ # selected columns for a dataset before issuing a query. If it
3
+ # thinks it can guess correctly at the columns the query will use,
4
+ # it will return the columns without issuing a database query.
5
+ # This method is not fool-proof, it's possible that some databases
6
+ # will use column names that Sequel does not expect.
7
+ #
8
+ # To enable this for a single dataset, extend the dataset with
9
+ # Sequel::ColumnIntrospection. To enable this for all datasets, run:
10
+ #
11
+ # Sequel::Dataset.introspect_all_columns
12
+
13
+ module Sequel
14
+ module ColumnsIntrospection
15
+ # Attempt to guess the columns that will be returned
16
+ # if there are columns selected, in order to skip a database
17
+ # query to retrieve the columns. This should work with
18
+ # Symbols, SQL::Identifiers, SQL::QualifiedIdentifiers, and
19
+ # SQL::AliasedExpressions.
20
+ def columns
21
+ return @columns if @columns
22
+ return columns_without_introspection unless cols = opts[:select] and !cols.empty?
23
+ probable_columns = cols.map{|c| probable_column_name(c)}
24
+ if probable_columns.all?
25
+ @columns = probable_columns
26
+ else
27
+ columns_without_introspection
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # Return the probable name of the column, or nil if one
34
+ # cannot be determined.
35
+ def probable_column_name(c)
36
+ case c
37
+ when Symbol
38
+ _, c, a = split_symbol(c)
39
+ (a || c).to_sym
40
+ when SQL::Identifier
41
+ c.value.to_sym
42
+ when SQL::QualifiedIdentifier
43
+ col = c.column
44
+ col.is_a?(SQL::Identifier) ? col.value.to_sym : col.to_sym
45
+ when SQL::AliasedExpression
46
+ a = c.aliaz
47
+ a.is_a?(SQL::Identifier) ? a.value.to_sym : a.to_sym
48
+ end
49
+ end
50
+ end
51
+
52
+ class Dataset
53
+ alias columns_without_introspection columns
54
+
55
+ # Enable column introspection for every dataset.
56
+ def self.introspect_all_columns
57
+ include ColumnsIntrospection
58
+ remove_method(:columns) if instance_methods(false).map{|x| x.to_s}.include?('columns')
59
+ end
60
+ end
61
+ end
@@ -2,6 +2,7 @@
2
2
  # the user to easily group schema changes and migrate the database
3
3
  # to a newer version or revert to a previous version.
4
4
 
5
+ #
5
6
  module Sequel
6
7
  # Sequel's older migration class, available for backward compatibility.
7
8
  # Uses subclasses with up and down instance methods for each migration:
@@ -302,7 +303,7 @@ module Sequel
302
303
  #
303
304
  # Part of the +migration+ extension.
304
305
  class Migrator
305
- MIGRATION_FILE_PATTERN = /\A\d+_.+\.rb\z/i.freeze
306
+ MIGRATION_FILE_PATTERN = /\A(\d+)_.+\.rb\z/i.freeze
306
307
  MIGRATION_SPLITTER = '_'.freeze
307
308
  MINIMUM_TIMESTAMP = 20000101
308
309
 
@@ -375,7 +376,8 @@ module Sequel
375
376
  @db = db
376
377
  @directory = directory
377
378
  @files = get_migration_files
378
- @table = opts[:table] || self.class.const_get(:DEFAULT_SCHEMA_TABLE)
379
+ schema, table = @db.send(:schema_and_table, opts[:table] || self.class.const_get(:DEFAULT_SCHEMA_TABLE))
380
+ @table = schema ? Sequel::SQL::QualifiedIdentifier.new(schema, table) : table
379
381
  @column = opts[:column] || self.class.const_get(:DEFAULT_SCHEMA_COLUMN)
380
382
  @ds = schema_dataset
381
383
  end
@@ -423,7 +425,7 @@ module Sequel
423
425
  @current = opts[:current] || current_migration_version
424
426
 
425
427
  raise(Error, "No current version available") unless current
426
- raise(Error, "No target version available") unless target
428
+ raise(Error, "No target version available, probably because no migration files found or filenames don't follow the migration filename convention") unless target
427
429
 
428
430
  @direction = current < target ? :up : :down
429
431
  @migrations = get_migrations
@@ -588,7 +590,7 @@ module Sequel
588
590
  next unless MIGRATION_FILE_PATTERN.match(file)
589
591
  files << File.join(directory, file)
590
592
  end
591
- files.sort
593
+ files.sort_by{|f| MIGRATION_FILE_PATTERN.match(File.basename(f))[1].to_i}
592
594
  end
593
595
 
594
596
  # Returns tuples of migration, filename, and direction
@@ -21,6 +21,11 @@
21
21
  # Then, before data is stored in the database, it is converted to New
22
22
  # York time. When data is retrieved from the database, it is
23
23
  # converted to Los Angeles time.
24
+ #
25
+ # Note that typecasting from the database timezone to the application
26
+ # timezone when fetching rows is dependent on the database adapter,
27
+ # and only works on adapters where Sequel itself does the conversion.
28
+ # It should work on mysql, postgres, sqlite, ibmdb, and jdbc.
24
29
 
25
30
  require 'tzinfo'
26
31
 
@@ -32,7 +32,7 @@ module Sequel
32
32
  module ThreadLocalTimezones
33
33
  %w'application database typecast'.each do |t|
34
34
  class_eval("def thread_#{t}_timezone=(tz); Thread.current[:#{t}_timezone] = convert_timezone_setter_arg(tz); end", __FILE__, __LINE__)
35
- class_eval(<<END, __FILE__, __LINE__)
35
+ class_eval(<<END, __FILE__, __LINE__ + 1)
36
36
  def #{t}_timezone
37
37
  if tz = Thread.current[:#{t}_timezone]
38
38
  tz unless tz == :nil
@@ -4,109 +4,111 @@
4
4
  # of the dataset's abstract syntax tree.
5
5
 
6
6
  module Sequel
7
- class Dataset
7
+ class ToDot
8
8
  # The option keys that should be included in the dot output.
9
9
  TO_DOT_OPTIONS = [:with, :distinct, :select, :from, :join, :where, :group, :having, :compounds, :order, :limit, :offset, :lock].freeze
10
10
 
11
- # Return a string that can be processed by the +dot+ program (included
12
- # with graphviz) in order to see a visualization of the dataset's
13
- # abstract syntax tree.
14
- def to_dot
15
- i = 0
16
- dot = ["digraph G {", "#{i} [label=\"self\"];"]
17
- _to_dot(dot, "", i, self, i)
18
- dot << "}"
19
- dot.join("\n")
11
+ # Given a +Dataset+, return a string in +dot+ format that will
12
+ # generate a visualization of the dataset.
13
+ def self.output(ds)
14
+ new(ds).output
15
+ end
16
+
17
+ # Given a +Dataset+, parse the internal structure to generate
18
+ # a dataset visualization.
19
+ def initialize(ds)
20
+ @i = 0
21
+ @stack = [@i]
22
+ @dot = ["digraph G {", "0 [label=\"self\"];"]
23
+ v(ds, "")
24
+ @dot << "}"
25
+ end
26
+
27
+ # Output the dataset visualization as a string in +dot+ format.
28
+ def output
29
+ @dot.join("\n")
20
30
  end
21
31
 
22
32
  private
23
33
 
24
- # Internal recursive version that handles all object types understood
25
- # by Sequel. Arguments:
26
- # * dot :: An array of strings representing the lines in the returned
27
- # output. This function just pushes strings onto this array.
28
- # * l :: The transition label from the parent node of the AST to the
29
- # current node.
30
- # * c :: An integer representing the parent node of the AST.
31
- # * e :: The current node of the AST.
32
- # * i :: The integer representing the last created node of the AST.
33
- #
34
- # The basic algorithm is that the +i+ is incremented to get the current
35
- # node's integer. Then the transition from the parent node to the
36
- # current node is added to the +dot+ array. Finally, the current node
37
- # is added to the +dot+ array, and if it is a compound node with children,
38
- # its children are then added by recursively calling this method. The
39
- # return value is the integer representing the last created node.
40
- def _to_dot(dot, l, c, e, i)
41
- i += 1
42
- dot << "#{c} -> #{i} [label=\"#{l}\"];" if l
43
- c = i
34
+ # Add an entry to the +dot+ output with the given label. If +j+
35
+ # is given, it is used directly as the node or transition. Otherwise
36
+ # a node is created for the current object.
37
+ def dot(label, j=nil)
38
+ @dot << "#{j||@i} [label=#{label.to_s.inspect}];"
39
+ end
40
+
41
+ # Recursive method that parses all of Sequel's internal datastructures,
42
+ # adding the appropriate nodes and transitions to the internal +dot+
43
+ # structure.
44
+ def v(e, l)
45
+ @i += 1
46
+ dot(l, "#{@stack.last} -> #{@i}") if l
47
+ @stack.push(@i)
44
48
  case e
45
49
  when LiteralString
46
- dot << "#{i} [label=\"#{e.inspect.gsub('"', '\\"')}.lit\"];"
47
- i
50
+ dot "#{e.inspect}.lit"
48
51
  when Symbol, Numeric, String, Class, TrueClass, FalseClass, NilClass
49
- dot << "#{i} [label=\"#{e.inspect.gsub('"', '\\"')}\"];"
50
- i
52
+ dot e.inspect
51
53
  when Array
52
- dot << "#{i} [label=\"Array\"];"
53
- e.each_with_index do |v, j|
54
- i = _to_dot(dot, j, c, v, i)
54
+ dot "Array"
55
+ e.each_with_index do |val, j|
56
+ v(val, j)
55
57
  end
56
58
  when Hash
57
- dot << "#{i} [label=\"Hash\"];"
58
- e.each do |k, v|
59
- i = _to_dot(dot, k, c, v, i)
59
+ dot "Hash"
60
+ e.each do |k, val|
61
+ v(val, k)
60
62
  end
61
63
  when SQL::ComplexExpression
62
- dot << "#{i} [label=\"ComplexExpression: #{e.op}\"];"
63
- e.args.each_with_index do |v, j|
64
- i = _to_dot(dot, j, c, v, i)
64
+ dot "ComplexExpression: #{e.op}"
65
+ e.args.each_with_index do |val, j|
66
+ v(val, j)
65
67
  end
66
68
  when SQL::Identifier
67
- dot << "#{i} [label=\"Identifier\"];"
68
- i = _to_dot(dot, :value, c, e.value, i)
69
+ dot "Identifier"
70
+ v(e.value, :value)
69
71
  when SQL::QualifiedIdentifier
70
- dot << "#{i} [label=\"QualifiedIdentifier\"];"
71
- i = _to_dot(dot, :table, c, e.table, i)
72
- i = _to_dot(dot, :column, c, e.column, i)
72
+ dot "QualifiedIdentifier"
73
+ v(e.table, :table)
74
+ v(e.column, :column)
73
75
  when SQL::OrderedExpression
74
- dot << "#{i} [label=\"OrderedExpression: #{e.descending ? :DESC : :ASC}#{" NULLS #{e.nulls.to_s.upcase}" if e.nulls}\"];"
75
- i = _to_dot(dot, :expression, c, e.expression, i)
76
+ dot "OrderedExpression: #{e.descending ? :DESC : :ASC}#{" NULLS #{e.nulls.to_s.upcase}" if e.nulls}"
77
+ v(e.expression, :expression)
76
78
  when SQL::AliasedExpression
77
- dot << "#{i} [label=\"AliasedExpression\"];"
78
- i = _to_dot(dot, :expression, c, e.expression, i)
79
- i = _to_dot(dot, :alias, c, e.aliaz, i)
79
+ dot "AliasedExpression"
80
+ v(e.expression, :expression)
81
+ v(e.aliaz, :alias)
80
82
  when SQL::CaseExpression
81
- dot << "#{i} [label=\"CaseExpression\"];"
82
- i = _to_dot(dot, :expression, c, e.expression, i) if e.expression
83
- i = _to_dot(dot, :conditions, c, e.conditions, i)
84
- i = _to_dot(dot, :default, c, e.default, i)
83
+ dot "CaseExpression"
84
+ v(e.expression, :expression) if e.expression
85
+ v(e.conditions, :conditions)
86
+ v(e.default, :default)
85
87
  when SQL::Cast
86
- dot << "#{i} [label=\"Cast\"];"
87
- i = _to_dot(dot, :expr, c, e.expr, i)
88
- i = _to_dot(dot, :type, c, e.type, i)
88
+ dot "Cast"
89
+ v(e.expr, :expr)
90
+ v(e.type, :type)
89
91
  when SQL::Function
90
- dot << "#{i} [label=\"Function: #{e.f}\"];"
91
- e.args.each_with_index do |v, j|
92
- i = _to_dot(dot, j, c, v, i)
92
+ dot "Function: #{e.f}"
93
+ e.args.each_with_index do |val, j|
94
+ v(val, j)
93
95
  end
94
96
  when SQL::Subscript
95
- dot << "#{i} [label=\"Subscript: #{e.f}\"];"
96
- i = _to_dot(dot, :f, c, e.f, i)
97
- i = _to_dot(dot, :sub, c, e.sub, i)
97
+ dot "Subscript"
98
+ v(e.f, :f)
99
+ v(e.sub, :sub)
98
100
  when SQL::WindowFunction
99
- dot << "#{i} [label=\"WindowFunction\"];"
100
- i = _to_dot(dot, :function, c, e.function, i)
101
- i = _to_dot(dot, :window, c, e.window, i)
101
+ dot "WindowFunction"
102
+ v(e.function, :function)
103
+ v(e.window, :window)
102
104
  when SQL::Window
103
- dot << "#{i} [label=\"Window\"];"
104
- i = _to_dot(dot, :opts, c, e.opts, i)
105
+ dot "Window"
106
+ v(e.opts, :opts)
105
107
  when SQL::PlaceholderLiteralString
106
108
  str = e.str
107
109
  str = "(#{str})" if e.parens
108
- dot << "#{i} [label=\"PlaceholderLiteralString: #{str.inspect.gsub('"', '\\"')}\"];"
109
- i = _to_dot(dot, :args, c, e.args, i)
110
+ dot "PlaceholderLiteralString: #{str.inspect}"
111
+ v(e.args, :args)
110
112
  when SQL::JoinClause
111
113
  str = "#{e.join_type.to_s.upcase} JOIN"
112
114
  if e.is_a?(SQL::JoinOnClause)
@@ -114,24 +116,34 @@ module Sequel
114
116
  elsif e.is_a?(SQL::JoinUsingClause)
115
117
  str << " USING"
116
118
  end
117
- dot << "#{i} [label=\"#{str}\"];"
118
- i = _to_dot(dot, :table, c, e.table, i)
119
- i = _to_dot(dot, :alias, c, e.table_alias, i) if e.table_alias
119
+ dot str
120
+ v(e.table, :table)
121
+ v(e.table_alias, :alias) if e.table_alias
120
122
  if e.is_a?(SQL::JoinOnClause)
121
- i = _to_dot(dot, :on, c, e.on, i)
123
+ v(e.on, :on)
122
124
  elsif e.is_a?(SQL::JoinUsingClause)
123
- i = _to_dot(dot, :using, c, e.using, i)
125
+ v(e.using, :using)
124
126
  end
125
127
  when Dataset
126
- dot << "#{i} [label=\"Dataset\"];"
128
+ dot "Dataset"
127
129
  TO_DOT_OPTIONS.each do |k|
128
- next unless e.opts[k]
129
- i = _to_dot(dot, k, c, e.opts[k], i)
130
+ if val = e.opts[k]
131
+ v(val, k.to_s)
132
+ end
130
133
  end
131
134
  else
132
- dot << "#{i} [label=\"Unhandled: #{e.inspect.gsub('"', "''")}\"];"
135
+ dot "Unhandled: #{e.inspect}"
133
136
  end
134
- i
137
+ @stack.pop
138
+ end
139
+ end
140
+
141
+ class Dataset
142
+ # Return a string that can be processed by the +dot+ program (included
143
+ # with graphviz) in order to see a visualization of the dataset's
144
+ # abstract syntax tree.
145
+ def to_dot
146
+ ToDot.output(self)
135
147
  end
136
148
  end
137
149
  end