sequel 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +1551 -4
  2. data/README +306 -19
  3. data/Rakefile +84 -56
  4. data/bin/sequel +106 -0
  5. data/doc/cheat_sheet.rdoc +225 -0
  6. data/doc/dataset_filtering.rdoc +182 -0
  7. data/lib/sequel_core.rb +136 -0
  8. data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
  9. data/lib/sequel_core/adapters/ado.rb +80 -0
  10. data/lib/sequel_core/adapters/db2.rb +148 -0
  11. data/lib/sequel_core/adapters/dbi.rb +117 -0
  12. data/lib/sequel_core/adapters/informix.rb +78 -0
  13. data/lib/sequel_core/adapters/jdbc.rb +186 -0
  14. data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
  15. data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
  16. data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
  17. data/lib/sequel_core/adapters/mysql.rb +231 -0
  18. data/lib/sequel_core/adapters/odbc.rb +155 -0
  19. data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/openbase.rb +64 -0
  21. data/lib/sequel_core/adapters/oracle.rb +170 -0
  22. data/lib/sequel_core/adapters/postgres.rb +199 -0
  23. data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
  24. data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
  25. data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
  26. data/lib/sequel_core/adapters/sqlite.rb +138 -0
  27. data/lib/sequel_core/connection_pool.rb +194 -0
  28. data/lib/sequel_core/core_ext.rb +203 -0
  29. data/lib/sequel_core/core_sql.rb +184 -0
  30. data/lib/sequel_core/database.rb +471 -0
  31. data/lib/sequel_core/database/schema.rb +156 -0
  32. data/lib/sequel_core/dataset.rb +457 -0
  33. data/lib/sequel_core/dataset/callback.rb +13 -0
  34. data/lib/sequel_core/dataset/convenience.rb +245 -0
  35. data/lib/sequel_core/dataset/pagination.rb +96 -0
  36. data/lib/sequel_core/dataset/query.rb +41 -0
  37. data/lib/sequel_core/dataset/schema.rb +15 -0
  38. data/lib/sequel_core/dataset/sql.rb +889 -0
  39. data/lib/sequel_core/deprecated.rb +26 -0
  40. data/lib/sequel_core/exceptions.rb +42 -0
  41. data/lib/sequel_core/migration.rb +187 -0
  42. data/lib/sequel_core/object_graph.rb +216 -0
  43. data/lib/sequel_core/pretty_table.rb +71 -0
  44. data/lib/sequel_core/schema.rb +2 -0
  45. data/lib/sequel_core/schema/generator.rb +239 -0
  46. data/lib/sequel_core/schema/sql.rb +325 -0
  47. data/lib/sequel_core/sql.rb +812 -0
  48. data/lib/sequel_model.rb +5 -1
  49. data/lib/sequel_model/association_reflection.rb +3 -8
  50. data/lib/sequel_model/base.rb +15 -10
  51. data/lib/sequel_model/inflector.rb +3 -5
  52. data/lib/sequel_model/plugins.rb +1 -1
  53. data/lib/sequel_model/record.rb +11 -3
  54. data/lib/sequel_model/schema.rb +4 -4
  55. data/lib/sequel_model/validations.rb +6 -1
  56. data/spec/adapters/ado_spec.rb +17 -0
  57. data/spec/adapters/informix_spec.rb +96 -0
  58. data/spec/adapters/mysql_spec.rb +764 -0
  59. data/spec/adapters/oracle_spec.rb +222 -0
  60. data/spec/adapters/postgres_spec.rb +441 -0
  61. data/spec/adapters/spec_helper.rb +7 -0
  62. data/spec/adapters/sqlite_spec.rb +400 -0
  63. data/spec/integration/dataset_test.rb +51 -0
  64. data/spec/integration/eager_loader_test.rb +702 -0
  65. data/spec/integration/schema_test.rb +102 -0
  66. data/spec/integration/spec_helper.rb +44 -0
  67. data/spec/integration/type_test.rb +43 -0
  68. data/spec/rcov.opts +2 -0
  69. data/spec/sequel_core/connection_pool_spec.rb +363 -0
  70. data/spec/sequel_core/core_ext_spec.rb +156 -0
  71. data/spec/sequel_core/core_sql_spec.rb +427 -0
  72. data/spec/sequel_core/database_spec.rb +964 -0
  73. data/spec/sequel_core/dataset_spec.rb +2977 -0
  74. data/spec/sequel_core/expression_filters_spec.rb +346 -0
  75. data/spec/sequel_core/migration_spec.rb +261 -0
  76. data/spec/sequel_core/object_graph_spec.rb +234 -0
  77. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  78. data/spec/sequel_core/schema_generator_spec.rb +122 -0
  79. data/spec/sequel_core/schema_spec.rb +497 -0
  80. data/spec/sequel_core/spec_helper.rb +51 -0
  81. data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
  82. data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
  83. data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
  84. data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
  85. data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
  86. data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
  87. data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
  88. data/spec/sequel_model/inflector_spec.rb +119 -0
  89. data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
  90. data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
  91. data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
  92. data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
  93. data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
  94. data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
  95. data/spec/spec_config.rb +9 -0
  96. data/spec/spec_config.rb.example +10 -0
  97. metadata +110 -37
  98. data/spec/inflector_spec.rb +0 -34
@@ -0,0 +1,26 @@
1
+ module Sequel
2
+ # This module makes it easy to add deprecation functionality to other classes.
3
+ module Deprecation # :nodoc:
4
+ # This sets the output stream for the deprecation messages. Set it to an IO
5
+ # (or any object that responds to puts) and it will call puts on that
6
+ # object with the deprecation message. Set to nil to ignore deprecation messages.
7
+ def self.deprecation_message_stream=(file)
8
+ @dms = file
9
+ end
10
+
11
+ # Set this to true to print tracebacks with every deprecation message,
12
+ # so you can see exactly where in your code the deprecated methods are
13
+ # being called.
14
+ def self.print_tracebacks=(pt)
15
+ @pt = pt
16
+ end
17
+
18
+ # Puts the messages unaltered to the deprecation message stream
19
+ def self.deprecate(message)
20
+ if @dms
21
+ @dms.puts(message)
22
+ caller.each{|c| @dms.puts(c)} if @pt
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ module Sequel
2
+ # Represents an error raised in Sequel code.
3
+ class Error < ::StandardError
4
+
5
+ # Raised when Sequel is unable to load a specified adapter.
6
+ class AdapterNotFound < Error ; end
7
+
8
+ # Raise when an invalid expression is encountered inside a block filter.
9
+ class InvalidExpression < Error; end
10
+
11
+ # Represents an Invalid filter.
12
+ class InvalidFilter < Error ; end
13
+
14
+ # Represents an invalid join type.
15
+ class InvalidJoinType < Error ; end
16
+
17
+ # Raised on an invalid operation.
18
+ class InvalidOperation < Error; end
19
+
20
+ # Error raised when an invalid statement is executed.
21
+ class InvalidStatement < Error; end
22
+
23
+ # Represents an Invalid transform.
24
+ class InvalidTransform < Error ; end
25
+
26
+ # Represents an invalid value stored in the database.
27
+ class InvalidValue < Error ; end
28
+
29
+ # Represents an attempt to performing filter operations when no filter has been specified yet.
30
+ class NoExistingFilter < Error ; end
31
+
32
+ # There was an error while waiting on a connection from the connection pool
33
+ class PoolTimeoutError < Error ; end
34
+
35
+ # Rollback is a special error used to rollback a transactions.
36
+ # A transaction block will catch this error and won't pass further up the stack.
37
+ class Rollback < Error ; end
38
+
39
+ # Should be raised inside a worker loop to tell it to stop working.
40
+ class WorkerStop < RuntimeError ; end
41
+ end
42
+ end
@@ -0,0 +1,187 @@
1
+ module Sequel
2
+ # The Migration class describes a database migration that can be reversed.
3
+ # The migration looks very similar to ActiveRecord (Rails) migrations, e.g.:
4
+ #
5
+ # class CreateSessions < Sequel::Migration
6
+ # def up
7
+ # create_table :sessions do
8
+ # primary_key :id
9
+ # varchar :session_id, :size => 32, :unique => true
10
+ # timestamp :created_at
11
+ # text :data
12
+ # end
13
+ # end
14
+ #
15
+ # def down
16
+ # execute 'DROP TABLE sessions'
17
+ # end
18
+ # end
19
+ #
20
+ # To apply a migration to a database, you can invoke the #apply with
21
+ # the target database instance and the direction :up or :down, e.g.:
22
+ #
23
+ # DB = Sequel.open ('sqlite://mydb')
24
+ # CreateSessions.apply(DB, :up)
25
+ #
26
+ # See Sequel::Schema::Generator for the syntax to use for creating tables,
27
+ # and Sequel::Schema::AlterTableGenerator for the syntax to use when
28
+ # altering existing tables.
29
+ class Migration
30
+ # Creates a new instance of the migration and sets the @db attribute.
31
+ def initialize(db)
32
+ @db = db
33
+ end
34
+
35
+ # Applies the migration to the supplied database in the specified
36
+ # direction.
37
+ def self.apply(db, direction)
38
+ obj = new(db)
39
+ case direction
40
+ when :up
41
+ obj.up
42
+ when :down
43
+ obj.down
44
+ else
45
+ raise ArgumentError, "Invalid migration direction specified (#{direction.inspect})"
46
+ end
47
+ end
48
+
49
+ # Returns the list of Migration descendants.
50
+ def self.descendants
51
+ @descendants ||= []
52
+ end
53
+
54
+ # Adds the new migration class to the list of Migration descendants.
55
+ def self.inherited(base)
56
+ descendants << base
57
+ end
58
+
59
+ # The default down action does nothing
60
+ def down
61
+ end
62
+
63
+ # Intercepts method calls intended for the database and sends them along.
64
+ def method_missing(method_sym, *args, &block)
65
+ @db.send(method_sym, *args, &block)
66
+ end
67
+
68
+ # The default up action does nothing
69
+ def up
70
+ end
71
+ end
72
+
73
+ # The Migrator module performs migrations based on migration files in a
74
+ # specified directory. The migration files should be named using the
75
+ # following pattern (in similar fashion to ActiveRecord migrations):
76
+ #
77
+ # <version>_<title>.rb
78
+ #
79
+ # For example, the following files are considered migration files:
80
+ #
81
+ # 001_create_sessions.rb
82
+ # 002_add_data_column.rb
83
+ # ...
84
+ #
85
+ # The migration files should contain one or more migration classes based
86
+ # on Sequel::Migration.
87
+ #
88
+ # To apply a migration, the #apply method must be invoked with the database
89
+ # instance, the directory of migration files and the target version. If
90
+ # no current version is supplied, it is read from the database. The migrator
91
+ # automatically creates a schema_info table in the database to keep track
92
+ # of the current migration version. If no migration version is stored in the
93
+ # database, the version is considered to be 0. If no target version is
94
+ # specified, the database is migrated to the latest version available in the
95
+ # migration directory.
96
+ #
97
+ # For example, to migrate the database to the latest version:
98
+ #
99
+ # Sequel::Migrator.apply(DB, '.')
100
+ #
101
+ # To migrate the database from version 1 to version 5:
102
+ #
103
+ # Sequel::Migrator.apply(DB, '.', 5, 1)
104
+ module Migrator
105
+ MIGRATION_FILE_PATTERN = /\A\d+_.+\.rb\z/.freeze
106
+
107
+ # Migrates the supplied database in the specified directory from the
108
+ # current version to the target version. If no current version is
109
+ # supplied, it is extracted from a schema_info table. The schema_info
110
+ # table is automatically created and maintained by the apply function.
111
+ def self.apply(db, directory, target = nil, current = nil)
112
+ # determine current and target version and direction
113
+ current ||= get_current_migration_version(db)
114
+ target ||= latest_migration_version(directory)
115
+ raise Error, "No current version available" if current.nil?
116
+ raise Error, "No target version available" if target.nil?
117
+
118
+ direction = current < target ? :up : :down
119
+
120
+ classes = migration_classes(directory, target, current, direction)
121
+
122
+ db.transaction do
123
+ classes.each {|c| c.apply(db, direction)}
124
+ set_current_migration_version(db, target)
125
+ end
126
+
127
+ target
128
+ end
129
+
130
+ # Gets the current migration version stored in the database. If no version
131
+ # number is stored, 0 is returned.
132
+ def self.get_current_migration_version(db)
133
+ r = schema_info_dataset(db).first
134
+ r ? r[:version] : 0
135
+ end
136
+
137
+ # Returns the latest version available in the specified directory.
138
+ def self.latest_migration_version(directory)
139
+ l = migration_files(directory).last
140
+ l ? File.basename(l).to_i : nil
141
+ end
142
+
143
+ # Returns a list of migration classes filtered for the migration range and
144
+ # ordered according to the migration direction.
145
+ def self.migration_classes(directory, target, current, direction)
146
+ range = direction == :up ?
147
+ (current + 1)..target : (target + 1)..current
148
+
149
+ # Remove class definitions
150
+ Migration.descendants.each do |c|
151
+ Object.send(:remove_const, c.to_s) rescue nil
152
+ end
153
+ Migration.descendants.clear # remove any defined migration classes
154
+
155
+ # load migration files
156
+ migration_files(directory, range).each {|fn| load(fn)}
157
+
158
+ # get migration classes
159
+ classes = Migration.descendants
160
+ classes.reverse! if direction == :down
161
+ classes
162
+ end
163
+
164
+ # Returns any found migration files in the supplied directory.
165
+ def self.migration_files(directory, range = nil)
166
+ files = []
167
+ Dir.new(directory).each do |file|
168
+ files[file.to_i] = File.join(directory, file) if MIGRATION_FILE_PATTERN.match(file)
169
+ end
170
+ filtered = range ? files[range] : files
171
+ filtered ? filtered.compact : []
172
+ end
173
+
174
+ # Returns the dataset for the schema_info table. If no such table
175
+ # exists, it is automatically created.
176
+ def self.schema_info_dataset(db)
177
+ db.create_table(:schema_info) {integer :version} unless db.table_exists?(:schema_info)
178
+ db[:schema_info]
179
+ end
180
+
181
+ # Sets the current migration version stored in the database.
182
+ def self.set_current_migration_version(db, version)
183
+ dataset = schema_info_dataset(db)
184
+ dataset.send(dataset.first ? :update : :<<, :version => version)
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,216 @@
1
+ module Sequel
2
+ class Dataset
3
+ # Allows you to join multiple datasets/tables and have the result set
4
+ # split into component tables.
5
+ #
6
+ # This differs from the usual usage of join, which returns the result set
7
+ # as a single hash. For example:
8
+ #
9
+ # # CREATE TABLE artists (id INTEGER, name TEXT);
10
+ # # CREATE TABLE albums (id INTEGER, name TEXT, artist_id INTEGER);
11
+ # DB[:artists].left_outer_join(:albums, :artist_id=>:id).first
12
+ # => {:id=>(albums.id||artists.id), :name=>(albums.name||artist.names), :artist_id=>albums.artist_id}
13
+ # DB[:artists].graph(:albums, :artist_id=>:id).first
14
+ # => {:artists=>{:id=>artists.id, :name=>artists.name}, :albums=>{:id=>albums.id, :name=>albums.name, :artist_id=>albums.artist_id}}
15
+ #
16
+ # Using a join such as left_outer_join, the attribute names that are shared between
17
+ # the tables are combined in the single return hash. You can get around that by
18
+ # using .select with correct aliases for all of the columns, but it is simpler to
19
+ # use graph and have the result set split for you. In addition, graph respects
20
+ # any row_proc or transform attributes of the current dataset and the datasets
21
+ # you use with graph.
22
+ #
23
+ # If you are graphing a table and all columns for that table are nil, this
24
+ # indicates that no matching rows existed in the table, so graph will return nil
25
+ # instead of a hash with all nil values:
26
+ #
27
+ # # If the artist doesn't have any albums
28
+ # DB[:artists].graph(:albums, :artist_id=>:id).first
29
+ # => {:artists=>{:id=>artists.id, :name=>artists.name}, :albums=>nil}
30
+ #
31
+ # Arguments:
32
+ # * dataset - Can be a symbol (specifying a table), another dataset,
33
+ # or an object that responds to .dataset and yields a symbol or a dataset
34
+ # * join_conditions - Any condition(s) allowed by join_table.
35
+ # * options - A hash of graph options. The following options are currently used:
36
+ # * :table_alias - The alias to use for the table. If not specified, doesn't
37
+ # alias the table. You will get an error if the the alias (or table) name is
38
+ # used more than once.
39
+ # * :join_type - The type of join to use (passed to join_table). Defaults to
40
+ # :left_outer.
41
+ # * :select - An array of columns to select. When not used, selects
42
+ # all columns in the given dataset. When set to false, selects no
43
+ # columns and is like simply joining the tables, though graph keeps
44
+ # some metadata about join that makes it important to use graph instead
45
+ # of join.
46
+ # * block - A block that is passed to join_table.
47
+ def graph(dataset, join_conditions = nil, options = {}, &block)
48
+ # Allow the use of a model, dataset, or symbol as the first argument
49
+ # Find the table name/dataset based on the argument
50
+ dataset = dataset.dataset if dataset.respond_to?(:dataset)
51
+ case dataset
52
+ when Symbol
53
+ table = dataset
54
+ dataset = @db[dataset]
55
+ when ::Sequel::Dataset
56
+ table = dataset.first_source
57
+ else
58
+ raise Error, "The dataset argument should be a symbol, dataset, or model"
59
+ end
60
+
61
+ # Raise Sequel::Error with explanation that the table alias has been used
62
+ raise_alias_error = lambda do
63
+ raise(Error, "this #{options[:table_alias] ? 'alias' : 'table'} has already been been used, please specify " \
64
+ "#{options[:table_alias] ? 'a different alias' : 'an alias via the :table_alias option'}")
65
+ end
66
+
67
+ # Only allow table aliases that haven't been used
68
+ table_alias = options[:table_alias] || table
69
+ raise_alias_error.call if @opts[:graph] && @opts[:graph][:table_aliases] && @opts[:graph][:table_aliases].include?(table_alias)
70
+
71
+ # Join the table early in order to avoid cloning the dataset twice
72
+ ds = join_table(options[:join_type] || :left_outer, table, join_conditions, table_alias, &block)
73
+ opts = ds.opts
74
+
75
+ # Whether to include the table in the result set
76
+ add_table = options[:select] == false ? false : true
77
+ # Whether to add the columns to the list of column aliases
78
+ add_columns = !ds.opts.include?(:graph_aliases)
79
+
80
+ # Setup the initial graph data structure if it doesn't exist
81
+ unless graph = opts[:graph]
82
+ master = ds.first_source
83
+ raise_alias_error.call if master == table_alias
84
+ # Master hash storing all .graph related information
85
+ graph = opts[:graph] = {}
86
+ # Associates column aliases back to tables and columns
87
+ column_aliases = graph[:column_aliases] = {}
88
+ # Associates table alias (the master is never aliased)
89
+ table_aliases = graph[:table_aliases] = {master=>self}
90
+ # Keep track of the alias numbers used
91
+ ca_num = graph[:column_alias_num] = Hash.new(0)
92
+ # All columns in the master table are never
93
+ # aliased, but are not included if set_graph_aliases
94
+ # has been used.
95
+ if add_columns
96
+ select = opts[:select] = []
97
+ columns.each do |column|
98
+ column_aliases[column] = [master, column]
99
+ select.push(column.qualify(master))
100
+ end
101
+ end
102
+ end
103
+
104
+ # Add the table alias to the list of aliases
105
+ # Even if it isn't been used in the result set,
106
+ # we add a key for it with a nil value so we can check if it
107
+ # is used more than once
108
+ table_aliases = graph[:table_aliases]
109
+ table_aliases[table_alias] = add_table ? dataset : nil
110
+
111
+ # Add the columns to the selection unless we are ignoring them
112
+ if add_table && add_columns
113
+ select = opts[:select]
114
+ column_aliases = graph[:column_aliases]
115
+ ca_num = graph[:column_alias_num]
116
+ # Which columns to add to the result set
117
+ cols = options[:select] || dataset.columns
118
+ # If the column hasn't been used yet, don't alias it.
119
+ # If it has been used, try table_column.
120
+ # If that has been used, try table_column_N
121
+ # using the next value of N that we know hasn't been
122
+ # used
123
+ cols.each do |column|
124
+ col_alias, identifier = if column_aliases[column]
125
+ column_alias = :"#{table_alias}_#{column}"
126
+ if column_aliases[column_alias]
127
+ column_alias_num = ca_num[column_alias]
128
+ column_alias = :"#{column_alias}_#{column_alias_num}"
129
+ ca_num[column_alias] += 1
130
+ end
131
+ [column_alias, column.qualify(table_alias).as(column_alias)]
132
+ else
133
+ [column, column.qualify(table_alias)]
134
+ end
135
+ column_aliases[col_alias] = [table_alias, column]
136
+ select.push(identifier)
137
+ end
138
+ end
139
+ ds
140
+ end
141
+
142
+ # This allows you to manually specify the graph aliases to use
143
+ # when using graph. You can use it to only select certain
144
+ # columns, and have those columns mapped to specific aliases
145
+ # in the result set. This is the equivalent of .select for a
146
+ # graphed dataset, and must be used instead of .select whenever
147
+ # graphing is used. Example:
148
+ #
149
+ # DB[:artists].graph(:albums, :artist_id=>:id).set_graph_aliases(:artist_name=>[:artists, :name], :album_name=>[:albums, :name]).first
150
+ # => {:artists=>{:name=>artists.name}, :albums=>{:name=>albums.name}}
151
+ #
152
+ # Arguments:
153
+ # * graph_aliases - Should be a hash with keys being symbols of
154
+ # column aliases, and values being arrays with two symbol elements.
155
+ # The first element of the array should be the table alias,
156
+ # and the second should be the actual column name.
157
+ def set_graph_aliases(graph_aliases)
158
+ cols = graph_aliases.collect do |col_alias, tc|
159
+ identifier = tc[1].qualify(tc[0])
160
+ identifier = identifier.as(col_alias) unless tc[1] == col_alias
161
+ identifier
162
+ end
163
+ ds = select(*cols)
164
+ ds.opts[:graph_aliases] = graph_aliases
165
+ ds
166
+ end
167
+
168
+ private
169
+
170
+ # Fetch the rows, split them into component table parts,
171
+ # tranform and run the row_proc on each part (if applicable),
172
+ # and yield a hash of the parts.
173
+ def graph_each(opts, &block)
174
+ # Reject tables with nil datasets, as they are excluded from
175
+ # the result set
176
+ datasets = @opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
177
+ # Get just the list of table aliases into a local variable, for speed
178
+ table_aliases = datasets.collect{|ta,ds| ta}
179
+ # Get an array of arrays, one for each dataset, with
180
+ # the necessary information about each dataset, for speed
181
+ datasets = datasets.collect do |ta, ds|
182
+ [ta, ds, ds.instance_variable_get(:@transform), ds.row_proc]
183
+ end
184
+ # Use the manually set graph aliases, if any, otherwise
185
+ # use the ones automatically created by .graph
186
+ column_aliases = @opts[:graph_aliases] || @opts[:graph][:column_aliases]
187
+ fetch_rows(select_sql(opts)) do |r|
188
+ graph = {}
189
+ # Create the sub hashes, one per table
190
+ table_aliases.each{|ta| graph[ta]={}}
191
+ # Split the result set based on the column aliases
192
+ # If there are columns in the result set that are
193
+ # not in column_aliases, they are ignored
194
+ column_aliases.each do |col_alias, tc|
195
+ ta, column = tc
196
+ graph[ta][column] = r[col_alias]
197
+ end
198
+ # For each dataset, transform and run the row
199
+ # row_proc if applicable
200
+ datasets.each do |ta,ds,tr,rp|
201
+ g = graph[ta]
202
+ graph[ta] = if g.values.any?
203
+ g = ds.transform_load(g) if tr
204
+ g = rp[g] if rp
205
+ g
206
+ else
207
+ nil
208
+ end
209
+ end
210
+
211
+ yield graph
212
+ end
213
+ self
214
+ end
215
+ end
216
+ end