sequel 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1551 -4
- data/README +306 -19
- data/Rakefile +84 -56
- data/bin/sequel +106 -0
- data/doc/cheat_sheet.rdoc +225 -0
- data/doc/dataset_filtering.rdoc +182 -0
- data/lib/sequel_core.rb +136 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +54 -0
- data/lib/sequel_core/adapters/ado.rb +80 -0
- data/lib/sequel_core/adapters/db2.rb +148 -0
- data/lib/sequel_core/adapters/dbi.rb +117 -0
- data/lib/sequel_core/adapters/informix.rb +78 -0
- data/lib/sequel_core/adapters/jdbc.rb +186 -0
- data/lib/sequel_core/adapters/jdbc/mysql.rb +55 -0
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +66 -0
- data/lib/sequel_core/adapters/jdbc/sqlite.rb +47 -0
- data/lib/sequel_core/adapters/mysql.rb +231 -0
- data/lib/sequel_core/adapters/odbc.rb +155 -0
- data/lib/sequel_core/adapters/odbc_mssql.rb +106 -0
- data/lib/sequel_core/adapters/openbase.rb +64 -0
- data/lib/sequel_core/adapters/oracle.rb +170 -0
- data/lib/sequel_core/adapters/postgres.rb +199 -0
- data/lib/sequel_core/adapters/shared/mysql.rb +275 -0
- data/lib/sequel_core/adapters/shared/postgres.rb +351 -0
- data/lib/sequel_core/adapters/shared/sqlite.rb +146 -0
- data/lib/sequel_core/adapters/sqlite.rb +138 -0
- data/lib/sequel_core/connection_pool.rb +194 -0
- data/lib/sequel_core/core_ext.rb +203 -0
- data/lib/sequel_core/core_sql.rb +184 -0
- data/lib/sequel_core/database.rb +471 -0
- data/lib/sequel_core/database/schema.rb +156 -0
- data/lib/sequel_core/dataset.rb +457 -0
- data/lib/sequel_core/dataset/callback.rb +13 -0
- data/lib/sequel_core/dataset/convenience.rb +245 -0
- data/lib/sequel_core/dataset/pagination.rb +96 -0
- data/lib/sequel_core/dataset/query.rb +41 -0
- data/lib/sequel_core/dataset/schema.rb +15 -0
- data/lib/sequel_core/dataset/sql.rb +889 -0
- data/lib/sequel_core/deprecated.rb +26 -0
- data/lib/sequel_core/exceptions.rb +42 -0
- data/lib/sequel_core/migration.rb +187 -0
- data/lib/sequel_core/object_graph.rb +216 -0
- data/lib/sequel_core/pretty_table.rb +71 -0
- data/lib/sequel_core/schema.rb +2 -0
- data/lib/sequel_core/schema/generator.rb +239 -0
- data/lib/sequel_core/schema/sql.rb +325 -0
- data/lib/sequel_core/sql.rb +812 -0
- data/lib/sequel_model.rb +5 -1
- data/lib/sequel_model/association_reflection.rb +3 -8
- data/lib/sequel_model/base.rb +15 -10
- data/lib/sequel_model/inflector.rb +3 -5
- data/lib/sequel_model/plugins.rb +1 -1
- data/lib/sequel_model/record.rb +11 -3
- data/lib/sequel_model/schema.rb +4 -4
- data/lib/sequel_model/validations.rb +6 -1
- data/spec/adapters/ado_spec.rb +17 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +764 -0
- data/spec/adapters/oracle_spec.rb +222 -0
- data/spec/adapters/postgres_spec.rb +441 -0
- data/spec/adapters/spec_helper.rb +7 -0
- data/spec/adapters/sqlite_spec.rb +400 -0
- data/spec/integration/dataset_test.rb +51 -0
- data/spec/integration/eager_loader_test.rb +702 -0
- data/spec/integration/schema_test.rb +102 -0
- data/spec/integration/spec_helper.rb +44 -0
- data/spec/integration/type_test.rb +43 -0
- data/spec/rcov.opts +2 -0
- data/spec/sequel_core/connection_pool_spec.rb +363 -0
- data/spec/sequel_core/core_ext_spec.rb +156 -0
- data/spec/sequel_core/core_sql_spec.rb +427 -0
- data/spec/sequel_core/database_spec.rb +964 -0
- data/spec/sequel_core/dataset_spec.rb +2977 -0
- data/spec/sequel_core/expression_filters_spec.rb +346 -0
- data/spec/sequel_core/migration_spec.rb +261 -0
- data/spec/sequel_core/object_graph_spec.rb +234 -0
- data/spec/sequel_core/pretty_table_spec.rb +58 -0
- data/spec/sequel_core/schema_generator_spec.rb +122 -0
- data/spec/sequel_core/schema_spec.rb +497 -0
- data/spec/sequel_core/spec_helper.rb +51 -0
- data/spec/{association_reflection_spec.rb → sequel_model/association_reflection_spec.rb} +6 -6
- data/spec/{associations_spec.rb → sequel_model/associations_spec.rb} +47 -18
- data/spec/{base_spec.rb → sequel_model/base_spec.rb} +2 -1
- data/spec/{caching_spec.rb → sequel_model/caching_spec.rb} +0 -0
- data/spec/{dataset_methods_spec.rb → sequel_model/dataset_methods_spec.rb} +13 -1
- data/spec/{eager_loading_spec.rb → sequel_model/eager_loading_spec.rb} +75 -14
- data/spec/{hooks_spec.rb → sequel_model/hooks_spec.rb} +4 -4
- data/spec/sequel_model/inflector_spec.rb +119 -0
- data/spec/{model_spec.rb → sequel_model/model_spec.rb} +30 -11
- data/spec/{plugins_spec.rb → sequel_model/plugins_spec.rb} +0 -0
- data/spec/{record_spec.rb → sequel_model/record_spec.rb} +47 -6
- data/spec/{schema_spec.rb → sequel_model/schema_spec.rb} +18 -4
- data/spec/{spec_helper.rb → sequel_model/spec_helper.rb} +3 -2
- data/spec/{validations_spec.rb → sequel_model/validations_spec.rb} +37 -17
- data/spec/spec_config.rb +9 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +110 -37
- 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
|