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
@@ -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