colincasey-sequel 2.10.0 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +7 -1
  2. data/doc/advanced_associations.rdoc +614 -0
  3. data/doc/cheat_sheet.rdoc +223 -0
  4. data/doc/dataset_filtering.rdoc +158 -0
  5. data/doc/prepared_statements.rdoc +104 -0
  6. data/doc/release_notes/1.0.txt +38 -0
  7. data/doc/release_notes/1.1.txt +143 -0
  8. data/doc/release_notes/1.3.txt +101 -0
  9. data/doc/release_notes/1.4.0.txt +53 -0
  10. data/doc/release_notes/1.5.0.txt +155 -0
  11. data/doc/release_notes/2.0.0.txt +298 -0
  12. data/doc/release_notes/2.1.0.txt +271 -0
  13. data/doc/release_notes/2.10.0.txt +328 -0
  14. data/doc/release_notes/2.2.0.txt +253 -0
  15. data/doc/release_notes/2.3.0.txt +88 -0
  16. data/doc/release_notes/2.4.0.txt +106 -0
  17. data/doc/release_notes/2.5.0.txt +137 -0
  18. data/doc/release_notes/2.6.0.txt +157 -0
  19. data/doc/release_notes/2.7.0.txt +166 -0
  20. data/doc/release_notes/2.8.0.txt +171 -0
  21. data/doc/release_notes/2.9.0.txt +97 -0
  22. data/doc/schema.rdoc +29 -0
  23. data/doc/sharding.rdoc +113 -0
  24. data/lib/sequel.rb +1 -0
  25. data/lib/sequel_core/adapters/ado.rb +89 -0
  26. data/lib/sequel_core/adapters/db2.rb +143 -0
  27. data/lib/sequel_core/adapters/dbi.rb +112 -0
  28. data/lib/sequel_core/adapters/do/mysql.rb +38 -0
  29. data/lib/sequel_core/adapters/do/postgres.rb +92 -0
  30. data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
  31. data/lib/sequel_core/adapters/do.rb +205 -0
  32. data/lib/sequel_core/adapters/firebird.rb +298 -0
  33. data/lib/sequel_core/adapters/informix.rb +85 -0
  34. data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
  35. data/lib/sequel_core/adapters/jdbc/mysql.rb +66 -0
  36. data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
  37. data/lib/sequel_core/adapters/jdbc/postgresql.rb +113 -0
  38. data/lib/sequel_core/adapters/jdbc/sqlite.rb +43 -0
  39. data/lib/sequel_core/adapters/jdbc.rb +491 -0
  40. data/lib/sequel_core/adapters/mysql.rb +369 -0
  41. data/lib/sequel_core/adapters/odbc.rb +174 -0
  42. data/lib/sequel_core/adapters/openbase.rb +68 -0
  43. data/lib/sequel_core/adapters/oracle.rb +107 -0
  44. data/lib/sequel_core/adapters/postgres.rb +456 -0
  45. data/lib/sequel_core/adapters/shared/ms_access.rb +110 -0
  46. data/lib/sequel_core/adapters/shared/mssql.rb +102 -0
  47. data/lib/sequel_core/adapters/shared/mysql.rb +325 -0
  48. data/lib/sequel_core/adapters/shared/oracle.rb +61 -0
  49. data/lib/sequel_core/adapters/shared/postgres.rb +715 -0
  50. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  51. data/lib/sequel_core/adapters/shared/sqlite.rb +265 -0
  52. data/lib/sequel_core/adapters/sqlite.rb +248 -0
  53. data/lib/sequel_core/connection_pool.rb +258 -0
  54. data/lib/sequel_core/core_ext.rb +217 -0
  55. data/lib/sequel_core/core_sql.rb +202 -0
  56. data/lib/sequel_core/database/schema.rb +164 -0
  57. data/lib/sequel_core/database.rb +691 -0
  58. data/lib/sequel_core/dataset/callback.rb +13 -0
  59. data/lib/sequel_core/dataset/convenience.rb +237 -0
  60. data/lib/sequel_core/dataset/pagination.rb +96 -0
  61. data/lib/sequel_core/dataset/prepared_statements.rb +220 -0
  62. data/lib/sequel_core/dataset/query.rb +41 -0
  63. data/lib/sequel_core/dataset/schema.rb +15 -0
  64. data/lib/sequel_core/dataset/sql.rb +1010 -0
  65. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  66. data/lib/sequel_core/dataset/unsupported.rb +43 -0
  67. data/lib/sequel_core/dataset.rb +511 -0
  68. data/lib/sequel_core/deprecated.rb +26 -0
  69. data/lib/sequel_core/exceptions.rb +44 -0
  70. data/lib/sequel_core/migration.rb +212 -0
  71. data/lib/sequel_core/object_graph.rb +230 -0
  72. data/lib/sequel_core/pretty_table.rb +71 -0
  73. data/lib/sequel_core/schema/generator.rb +320 -0
  74. data/lib/sequel_core/schema/sql.rb +325 -0
  75. data/lib/sequel_core/schema.rb +2 -0
  76. data/lib/sequel_core/sql.rb +887 -0
  77. data/lib/sequel_core/version.rb +11 -0
  78. data/lib/sequel_core.rb +172 -0
  79. data/lib/sequel_model/association_reflection.rb +267 -0
  80. data/lib/sequel_model/associations.rb +499 -0
  81. data/lib/sequel_model/base.rb +523 -0
  82. data/lib/sequel_model/caching.rb +82 -0
  83. data/lib/sequel_model/dataset_methods.rb +26 -0
  84. data/lib/sequel_model/eager_loading.rb +370 -0
  85. data/lib/sequel_model/exceptions.rb +7 -0
  86. data/lib/sequel_model/hooks.rb +101 -0
  87. data/lib/sequel_model/inflector.rb +281 -0
  88. data/lib/sequel_model/plugins.rb +62 -0
  89. data/lib/sequel_model/record.rb +568 -0
  90. data/lib/sequel_model/schema.rb +49 -0
  91. data/lib/sequel_model/validations.rb +429 -0
  92. data/lib/sequel_model.rb +91 -0
  93. data/spec/adapters/ado_spec.rb +46 -0
  94. data/spec/adapters/firebird_spec.rb +376 -0
  95. data/spec/adapters/informix_spec.rb +96 -0
  96. data/spec/adapters/mysql_spec.rb +881 -0
  97. data/spec/adapters/oracle_spec.rb +244 -0
  98. data/spec/adapters/postgres_spec.rb +687 -0
  99. data/spec/adapters/spec_helper.rb +10 -0
  100. data/spec/adapters/sqlite_spec.rb +555 -0
  101. data/spec/integration/dataset_test.rb +134 -0
  102. data/spec/integration/eager_loader_test.rb +696 -0
  103. data/spec/integration/prepared_statement_test.rb +130 -0
  104. data/spec/integration/schema_test.rb +180 -0
  105. data/spec/integration/spec_helper.rb +58 -0
  106. data/spec/integration/type_test.rb +96 -0
  107. data/spec/rcov.opts +6 -0
  108. data/spec/sequel_core/connection_pool_spec.rb +526 -0
  109. data/spec/sequel_core/core_ext_spec.rb +156 -0
  110. data/spec/sequel_core/core_sql_spec.rb +522 -0
  111. data/spec/sequel_core/database_spec.rb +1188 -0
  112. data/spec/sequel_core/dataset_spec.rb +3481 -0
  113. data/spec/sequel_core/expression_filters_spec.rb +363 -0
  114. data/spec/sequel_core/migration_spec.rb +261 -0
  115. data/spec/sequel_core/object_graph_spec.rb +272 -0
  116. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  117. data/spec/sequel_core/schema_generator_spec.rb +167 -0
  118. data/spec/sequel_core/schema_spec.rb +780 -0
  119. data/spec/sequel_core/spec_helper.rb +55 -0
  120. data/spec/sequel_core/version_spec.rb +7 -0
  121. data/spec/sequel_model/association_reflection_spec.rb +93 -0
  122. data/spec/sequel_model/associations_spec.rb +1767 -0
  123. data/spec/sequel_model/base_spec.rb +419 -0
  124. data/spec/sequel_model/caching_spec.rb +215 -0
  125. data/spec/sequel_model/dataset_methods_spec.rb +78 -0
  126. data/spec/sequel_model/eager_loading_spec.rb +1165 -0
  127. data/spec/sequel_model/hooks_spec.rb +485 -0
  128. data/spec/sequel_model/inflector_spec.rb +119 -0
  129. data/spec/sequel_model/model_spec.rb +588 -0
  130. data/spec/sequel_model/plugins_spec.rb +80 -0
  131. data/spec/sequel_model/record_spec.rb +1184 -0
  132. data/spec/sequel_model/schema_spec.rb +90 -0
  133. data/spec/sequel_model/spec_helper.rb +78 -0
  134. data/spec/sequel_model/validations_spec.rb +1067 -0
  135. data/spec/spec.opts +0 -0
  136. data/spec/spec_config.rb.example +10 -0
  137. metadata +177 -3
@@ -0,0 +1,212 @@
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
+ # String :session_id, :size => 32, :unique => true
10
+ # DateTime :created_at
11
+ # text :data
12
+ # end
13
+ # end
14
+ #
15
+ # def down
16
+ # # You can use raw SQL if you need to
17
+ # self << 'DROP TABLE sessions'
18
+ # end
19
+ # end
20
+ #
21
+ # class AlterItems < Sequel::Migration
22
+ # def up
23
+ # alter_table :items do
24
+ # add_column :category, String, :default => 'ruby'
25
+ # end
26
+ # end
27
+ #
28
+ # def down
29
+ # alter_table :items do
30
+ # drop_column :category
31
+ # end
32
+ # end
33
+ # end
34
+ #
35
+ # To apply a migration to a database, you can invoke the #apply with
36
+ # the target database instance and the direction :up or :down, e.g.:
37
+ #
38
+ # DB = Sequel.open ('sqlite://mydb')
39
+ # CreateSessions.apply(DB, :up)
40
+ #
41
+ # See Sequel::Schema::Generator for the syntax to use for creating tables,
42
+ # and Sequel::Schema::AlterTableGenerator for the syntax to use when
43
+ # altering existing tables. Migrations act as a proxy for the database
44
+ # given in #apply, so inside #down and #up, you can act as though self
45
+ # refers to the database. So you can use any of the Sequel::Database
46
+ # instance methods directly.
47
+ class Migration
48
+ # Creates a new instance of the migration and sets the @db attribute.
49
+ def initialize(db)
50
+ @db = db
51
+ end
52
+
53
+ # Applies the migration to the supplied database in the specified
54
+ # direction.
55
+ def self.apply(db, direction)
56
+ obj = new(db)
57
+ case direction
58
+ when :up
59
+ obj.up
60
+ when :down
61
+ obj.down
62
+ else
63
+ raise ArgumentError, "Invalid migration direction specified (#{direction.inspect})"
64
+ end
65
+ end
66
+
67
+ # Returns the list of Migration descendants.
68
+ def self.descendants
69
+ @descendants ||= []
70
+ end
71
+
72
+ # Adds the new migration class to the list of Migration descendants.
73
+ def self.inherited(base)
74
+ descendants << base
75
+ end
76
+
77
+ # The default down action does nothing
78
+ def down
79
+ end
80
+
81
+ # Intercepts method calls intended for the database and sends them along.
82
+ def method_missing(method_sym, *args, &block)
83
+ @db.send(method_sym, *args, &block)
84
+ end
85
+
86
+ # The default up action does nothing
87
+ def up
88
+ end
89
+ end
90
+
91
+ # The Migrator module performs migrations based on migration files in a
92
+ # specified directory. The migration files should be named using the
93
+ # following pattern (in similar fashion to ActiveRecord migrations):
94
+ #
95
+ # <version>_<title>.rb
96
+ #
97
+ # For example, the following files are considered migration files:
98
+ #
99
+ # 001_create_sessions.rb
100
+ # 002_add_data_column.rb
101
+ # ...
102
+ #
103
+ # The migration files should contain one or more migration classes based
104
+ # on Sequel::Migration.
105
+ #
106
+ # Migrations are generally run via the sequel command line tool,
107
+ # using the -m and -M switches. The -m switch specifies the migration
108
+ # directory, and the -M switch specifies the version to which to migrate.
109
+ #
110
+ # You can apply migrations using the Migrator API, as well (this is necessary
111
+ # if you want to specify the version from which to migrate in addition to the version
112
+ # to which to migrate).
113
+ # To apply a migration, the #apply method must be invoked with the database
114
+ # instance, the directory of migration files and the target version. If
115
+ # no current version is supplied, it is read from the database. The migrator
116
+ # automatically creates a schema_info table in the database to keep track
117
+ # of the current migration version. If no migration version is stored in the
118
+ # database, the version is considered to be 0. If no target version is
119
+ # specified, the database is migrated to the latest version available in the
120
+ # migration directory.
121
+ #
122
+ # For example, to migrate the database to the latest version:
123
+ #
124
+ # Sequel::Migrator.apply(DB, '.')
125
+ #
126
+ # To migrate the database from version 1 to version 5:
127
+ #
128
+ # Sequel::Migrator.apply(DB, '.', 5, 1)
129
+ module Migrator
130
+ MIGRATION_FILE_PATTERN = /\A\d+_.+\.rb\z/.freeze
131
+
132
+ # Migrates the supplied database in the specified directory from the
133
+ # current version to the target version. If no current version is
134
+ # supplied, it is extracted from a schema_info table. The schema_info
135
+ # table is automatically created and maintained by the apply function.
136
+ def self.apply(db, directory, target = nil, current = nil)
137
+ # determine current and target version and direction
138
+ current ||= get_current_migration_version(db)
139
+ target ||= latest_migration_version(directory)
140
+ raise Error, "No current version available" if current.nil?
141
+ raise Error, "No target version available" if target.nil?
142
+
143
+ direction = current < target ? :up : :down
144
+
145
+ classes = migration_classes(directory, target, current, direction)
146
+
147
+ db.transaction do
148
+ classes.each {|c| c.apply(db, direction)}
149
+ set_current_migration_version(db, target)
150
+ end
151
+
152
+ target
153
+ end
154
+
155
+ # Gets the current migration version stored in the database. If no version
156
+ # number is stored, 0 is returned.
157
+ def self.get_current_migration_version(db)
158
+ r = schema_info_dataset(db).first
159
+ r ? r[:version] : 0
160
+ end
161
+
162
+ # Returns the latest version available in the specified directory.
163
+ def self.latest_migration_version(directory)
164
+ l = migration_files(directory).last
165
+ l ? File.basename(l).to_i : nil
166
+ end
167
+
168
+ # Returns a list of migration classes filtered for the migration range and
169
+ # ordered according to the migration direction.
170
+ def self.migration_classes(directory, target, current, direction)
171
+ range = direction == :up ?
172
+ (current + 1)..target : (target + 1)..current
173
+
174
+ # Remove class definitions
175
+ Migration.descendants.each do |c|
176
+ Object.send(:remove_const, c.to_s) rescue nil
177
+ end
178
+ Migration.descendants.clear # remove any defined migration classes
179
+
180
+ # load migration files
181
+ migration_files(directory, range).each {|fn| load(fn)}
182
+
183
+ # get migration classes
184
+ classes = Migration.descendants
185
+ classes.reverse! if direction == :down
186
+ classes
187
+ end
188
+
189
+ # Returns any found migration files in the supplied directory.
190
+ def self.migration_files(directory, range = nil)
191
+ files = []
192
+ Dir.new(directory).each do |file|
193
+ files[file.to_i] = File.join(directory, file) if MIGRATION_FILE_PATTERN.match(file)
194
+ end
195
+ filtered = range ? files[range] : files
196
+ filtered ? filtered.compact : []
197
+ end
198
+
199
+ # Returns the dataset for the schema_info table. If no such table
200
+ # exists, it is automatically created.
201
+ def self.schema_info_dataset(db)
202
+ db.create_table(:schema_info) {integer :version} unless db.table_exists?(:schema_info)
203
+ db[:schema_info]
204
+ end
205
+
206
+ # Sets the current migration version stored in the database.
207
+ def self.set_current_migration_version(db, version)
208
+ dataset = schema_info_dataset(db)
209
+ dataset.send(dataset.first ? :update : :<<, :version => version)
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,230 @@
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
+ # * :implicit_qualifier - The qualifier of implicit conditions, see #join_table.
37
+ # * :join_type - The type of join to use (passed to join_table). Defaults to
38
+ # :left_outer.
39
+ # * :select - An array of columns to select. When not used, selects
40
+ # all columns in the given dataset. When set to false, selects no
41
+ # columns and is like simply joining the tables, though graph keeps
42
+ # some metadata about join that makes it important to use graph instead
43
+ # of join.
44
+ # * :table_alias - The alias to use for the table. If not specified, doesn't
45
+ # alias the table. You will get an error if the the alias (or table) name is
46
+ # used more than once.
47
+ # * block - A block that is passed to join_table.
48
+ def graph(dataset, join_conditions = nil, options = {}, &block)
49
+ # Allow the use of a model, dataset, or symbol as the first argument
50
+ # Find the table name/dataset based on the argument
51
+ dataset = dataset.dataset if dataset.respond_to?(:dataset)
52
+ case dataset
53
+ when Symbol
54
+ table = dataset
55
+ dataset = @db[dataset]
56
+ when ::Sequel::Dataset
57
+ table = dataset.first_source
58
+ else
59
+ raise Error, "The dataset argument should be a symbol, dataset, or model"
60
+ end
61
+
62
+ # Raise Sequel::Error with explanation that the table alias has been used
63
+ raise_alias_error = lambda do
64
+ raise(Error, "this #{options[:table_alias] ? 'alias' : 'table'} has already been been used, please specify " \
65
+ "#{options[:table_alias] ? 'a different alias' : 'an alias via the :table_alias option'}")
66
+ end
67
+
68
+ # Only allow table aliases that haven't been used
69
+ table_alias = options[:table_alias] || table
70
+ raise_alias_error.call if @opts[:graph] && @opts[:graph][:table_aliases] && @opts[:graph][:table_aliases].include?(table_alias)
71
+
72
+ # Join the table early in order to avoid cloning the dataset twice
73
+ ds = join_table(options[:join_type] || :left_outer, table, join_conditions, :table_alias=>table_alias, :implicit_qualifier=>options[:implicit_qualifier], &block)
74
+ opts = ds.opts
75
+
76
+ # Whether to include the table in the result set
77
+ add_table = options[:select] == false ? false : true
78
+ # Whether to add the columns to the list of column aliases
79
+ add_columns = !ds.opts.include?(:graph_aliases)
80
+
81
+ # Setup the initial graph data structure if it doesn't exist
82
+ unless graph = opts[:graph]
83
+ master = ds.first_source
84
+ raise_alias_error.call if master == table_alias
85
+ # Master hash storing all .graph related information
86
+ graph = opts[:graph] = {}
87
+ # Associates column aliases back to tables and columns
88
+ column_aliases = graph[:column_aliases] = {}
89
+ # Associates table alias (the master is never aliased)
90
+ table_aliases = graph[:table_aliases] = {master=>self}
91
+ # Keep track of the alias numbers used
92
+ ca_num = graph[:column_alias_num] = Hash.new(0)
93
+ # All columns in the master table are never
94
+ # aliased, but are not included if set_graph_aliases
95
+ # has been used.
96
+ if add_columns
97
+ select = opts[:select] = []
98
+ columns.each do |column|
99
+ column_aliases[column] = [master, column]
100
+ select.push(column.qualify(master))
101
+ end
102
+ end
103
+ end
104
+
105
+ # Add the table alias to the list of aliases
106
+ # Even if it isn't been used in the result set,
107
+ # we add a key for it with a nil value so we can check if it
108
+ # is used more than once
109
+ table_aliases = graph[:table_aliases]
110
+ table_aliases[table_alias] = add_table ? dataset : nil
111
+
112
+ # Add the columns to the selection unless we are ignoring them
113
+ if add_table && add_columns
114
+ select = opts[:select]
115
+ column_aliases = graph[:column_aliases]
116
+ ca_num = graph[:column_alias_num]
117
+ # Which columns to add to the result set
118
+ cols = options[:select] || dataset.columns
119
+ # If the column hasn't been used yet, don't alias it.
120
+ # If it has been used, try table_column.
121
+ # If that has been used, try table_column_N
122
+ # using the next value of N that we know hasn't been
123
+ # used
124
+ cols.each do |column|
125
+ col_alias, identifier = if column_aliases[column]
126
+ column_alias = :"#{table_alias}_#{column}"
127
+ if column_aliases[column_alias]
128
+ column_alias_num = ca_num[column_alias]
129
+ column_alias = :"#{column_alias}_#{column_alias_num}"
130
+ ca_num[column_alias] += 1
131
+ end
132
+ [column_alias, column.qualify(table_alias).as(column_alias)]
133
+ else
134
+ [column, column.qualify(table_alias)]
135
+ end
136
+ column_aliases[col_alias] = [table_alias, column]
137
+ select.push(identifier)
138
+ end
139
+ end
140
+ ds
141
+ end
142
+
143
+ # This allows you to manually specify the graph aliases to use
144
+ # when using graph. You can use it to only select certain
145
+ # columns, and have those columns mapped to specific aliases
146
+ # in the result set. This is the equivalent of .select for a
147
+ # graphed dataset, and must be used instead of .select whenever
148
+ # graphing is used. Example:
149
+ #
150
+ # DB[:artists].graph(:albums, :artist_id=>:id).set_graph_aliases(:artist_name=>[:artists, :name], :album_name=>[:albums, :name]).first
151
+ # => {:artists=>{:name=>artists.name}, :albums=>{:name=>albums.name}}
152
+ #
153
+ # Arguments:
154
+ # * graph_aliases - Should be a hash with keys being symbols of
155
+ # column aliases, and values being arrays with two symbol elements.
156
+ # The first element of the array should be the table alias,
157
+ # and the second should be the actual column name.
158
+ def set_graph_aliases(graph_aliases)
159
+ ds = select(*graph_alias_columns(graph_aliases))
160
+ ds.opts[:graph_aliases] = graph_aliases
161
+ ds
162
+ end
163
+
164
+ # Adds the give graph aliases to the list of graph aliases to use,
165
+ # unlike #set_graph_aliases, which replaces the list. See
166
+ # #set_graph_aliases.
167
+ def add_graph_aliases(graph_aliases)
168
+ ds = select_more(*graph_alias_columns(graph_aliases))
169
+ ds.opts[:graph_aliases] = (ds.opts[:graph_aliases] || {}).merge(graph_aliases)
170
+ ds
171
+ end
172
+
173
+ private
174
+
175
+ # Transform the hash of graph aliases to an array of columns
176
+ def graph_alias_columns(graph_aliases)
177
+ graph_aliases.collect do |col_alias, tc|
178
+ identifier = tc[2] || tc[1].qualify(tc[0])
179
+ identifier = SQL::AliasedExpression.new(identifier, col_alias) if tc[2] or tc[1] != col_alias
180
+ identifier
181
+ end
182
+ end
183
+
184
+ # Fetch the rows, split them into component table parts,
185
+ # tranform and run the row_proc on each part (if applicable),
186
+ # and yield a hash of the parts.
187
+ def graph_each(opts, &block)
188
+ # Reject tables with nil datasets, as they are excluded from
189
+ # the result set
190
+ datasets = @opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
191
+ # Get just the list of table aliases into a local variable, for speed
192
+ table_aliases = datasets.collect{|ta,ds| ta}
193
+ # Get an array of arrays, one for each dataset, with
194
+ # the necessary information about each dataset, for speed
195
+ datasets = datasets.collect do |ta, ds|
196
+ [ta, ds, ds.instance_variable_get(:@transform), ds.row_proc]
197
+ end
198
+ # Use the manually set graph aliases, if any, otherwise
199
+ # use the ones automatically created by .graph
200
+ column_aliases = @opts[:graph_aliases] || @opts[:graph][:column_aliases]
201
+ fetch_rows(select_sql(opts)) do |r|
202
+ graph = {}
203
+ # Create the sub hashes, one per table
204
+ table_aliases.each{|ta| graph[ta]={}}
205
+ # Split the result set based on the column aliases
206
+ # If there are columns in the result set that are
207
+ # not in column_aliases, they are ignored
208
+ column_aliases.each do |col_alias, tc|
209
+ ta, column = tc
210
+ graph[ta][column] = r[col_alias]
211
+ end
212
+ # For each dataset, transform and run the row
213
+ # row_proc if applicable
214
+ datasets.each do |ta,ds,tr,rp|
215
+ g = graph[ta]
216
+ graph[ta] = if g.values.any?
217
+ g = ds.transform_load(g) if tr
218
+ g = rp[g] if rp
219
+ g
220
+ else
221
+ nil
222
+ end
223
+ end
224
+
225
+ yield graph
226
+ end
227
+ self
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,71 @@
1
+ module Sequel
2
+ module PrettyTable
3
+ # Prints nice-looking plain-text tables via puts
4
+ #
5
+ # +--+-------+
6
+ # |id|name |
7
+ # |--+-------|
8
+ # |1 |fasdfas|
9
+ # |2 |test |
10
+ # +--+-------+
11
+ def self.print(records, columns = nil) # records is an array of hashes
12
+ columns ||= records.first.keys.sort_by{|x|x.to_s}
13
+ sizes = column_sizes(records, columns)
14
+ sep_line = separator_line(columns, sizes)
15
+
16
+ puts sep_line
17
+ puts header_line(columns, sizes)
18
+ puts sep_line
19
+ records.each {|r| puts data_line(columns, sizes, r)}
20
+ puts sep_line
21
+ end
22
+
23
+ ### Private Module Methods ###
24
+
25
+ # Hash of the maximum size of the value for each column
26
+ def self.column_sizes(records, columns) # :nodoc:
27
+ sizes = Hash.new {0}
28
+ columns.each do |c|
29
+ s = c.to_s.size
30
+ sizes[c.to_sym] = s if s > sizes[c.to_sym]
31
+ end
32
+ records.each do |r|
33
+ columns.each do |c|
34
+ s = r[c].to_s.size
35
+ sizes[c.to_sym] = s if s > sizes[c.to_sym]
36
+ end
37
+ end
38
+ sizes
39
+ end
40
+
41
+ # String for each data line
42
+ def self.data_line(columns, sizes, record) # :nodoc:
43
+ '|' << columns.map {|c| format_cell(sizes[c], record[c])}.join('|') << '|'
44
+ end
45
+
46
+ # Format the value so it takes up exactly size characters
47
+ def self.format_cell(size, v) # :nodoc:
48
+ case v
49
+ when Bignum, Fixnum
50
+ "%#{size}d" % v
51
+ when Float
52
+ "%#{size}g" % v
53
+ else
54
+ "%-#{size}s" % v.to_s
55
+ end
56
+ end
57
+
58
+ # String for header line
59
+ def self.header_line(columns, sizes) # :nodoc:
60
+ '|' << columns.map {|c| "%-#{sizes[c]}s" % c.to_s}.join('|') << '|'
61
+ end
62
+
63
+ # String for separtor line
64
+ def self.separator_line(columns, sizes) # :nodoc:
65
+ '+' << columns.map {|c| '-' * sizes[c]}.join('+') << '+'
66
+ end
67
+
68
+ private_class_method :column_sizes, :data_line, :format_cell, :header_line, :separator_line
69
+ end
70
+ end
71
+