sequel_core 1.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.
- data/CHANGELOG +1003 -0
- data/COPYING +18 -0
- data/README +81 -0
- data/Rakefile +176 -0
- data/bin/sequel +41 -0
- data/lib/sequel_core.rb +59 -0
- data/lib/sequel_core/adapters/adapter_skeleton.rb +68 -0
- data/lib/sequel_core/adapters/ado.rb +100 -0
- data/lib/sequel_core/adapters/db2.rb +158 -0
- data/lib/sequel_core/adapters/dbi.rb +126 -0
- data/lib/sequel_core/adapters/informix.rb +87 -0
- data/lib/sequel_core/adapters/jdbc.rb +108 -0
- data/lib/sequel_core/adapters/mysql.rb +269 -0
- data/lib/sequel_core/adapters/odbc.rb +145 -0
- data/lib/sequel_core/adapters/odbc_mssql.rb +93 -0
- data/lib/sequel_core/adapters/openbase.rb +90 -0
- data/lib/sequel_core/adapters/oracle.rb +99 -0
- data/lib/sequel_core/adapters/postgres.rb +519 -0
- data/lib/sequel_core/adapters/sqlite.rb +192 -0
- data/lib/sequel_core/array_keys.rb +296 -0
- data/lib/sequel_core/connection_pool.rb +152 -0
- data/lib/sequel_core/core_ext.rb +59 -0
- data/lib/sequel_core/core_sql.rb +191 -0
- data/lib/sequel_core/database.rb +433 -0
- data/lib/sequel_core/dataset.rb +409 -0
- data/lib/sequel_core/dataset/convenience.rb +321 -0
- data/lib/sequel_core/dataset/sequelizer.rb +354 -0
- data/lib/sequel_core/dataset/sql.rb +586 -0
- data/lib/sequel_core/exceptions.rb +45 -0
- data/lib/sequel_core/migration.rb +191 -0
- data/lib/sequel_core/model.rb +8 -0
- data/lib/sequel_core/pretty_table.rb +73 -0
- data/lib/sequel_core/schema.rb +8 -0
- data/lib/sequel_core/schema/schema_generator.rb +131 -0
- data/lib/sequel_core/schema/schema_sql.rb +131 -0
- data/lib/sequel_core/worker.rb +58 -0
- data/spec/adapters/informix_spec.rb +139 -0
- data/spec/adapters/mysql_spec.rb +330 -0
- data/spec/adapters/oracle_spec.rb +130 -0
- data/spec/adapters/postgres_spec.rb +189 -0
- data/spec/adapters/sqlite_spec.rb +345 -0
- data/spec/array_keys_spec.rb +679 -0
- data/spec/connection_pool_spec.rb +356 -0
- data/spec/core_ext_spec.rb +67 -0
- data/spec/core_sql_spec.rb +301 -0
- data/spec/database_spec.rb +812 -0
- data/spec/dataset_spec.rb +2381 -0
- data/spec/migration_spec.rb +261 -0
- data/spec/pretty_table_spec.rb +66 -0
- data/spec/rcov.opts +4 -0
- data/spec/schema_generator_spec.rb +86 -0
- data/spec/schema_spec.rb +230 -0
- data/spec/sequelizer_spec.rb +448 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/worker_spec.rb +96 -0
- metadata +162 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module Sequel
|
2
|
+
# Represents an error raised in Sequel code.
|
3
|
+
class Error < StandardError
|
4
|
+
|
5
|
+
# Rollback is a special error used to rollback a transactions.
|
6
|
+
# A transaction block will catch this error and wont pass further up the stack.
|
7
|
+
class Rollback < Error ; end
|
8
|
+
|
9
|
+
# Represents an invalid value stored in the database.
|
10
|
+
class InvalidValue < Error ; end
|
11
|
+
|
12
|
+
# Represents an Invalid transform.
|
13
|
+
class InvalidTransform < Error ; end
|
14
|
+
|
15
|
+
# Represents an Invalid filter.
|
16
|
+
class InvalidFilter < Error ; end
|
17
|
+
|
18
|
+
class InvalidExpression < Error; end
|
19
|
+
|
20
|
+
# Represents an attempt to performing filter operations when no filter has been specified yet.
|
21
|
+
class NoExistingFilter < Error ; end
|
22
|
+
|
23
|
+
# Represents an invalid join type.
|
24
|
+
class InvalidJoinType < Error ; end
|
25
|
+
|
26
|
+
class WorkerStop < RuntimeError ; end
|
27
|
+
|
28
|
+
# Raised when Sequel is unable to load a specified adapter.
|
29
|
+
class AdapterNotFound < Error ; end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Object extensions
|
34
|
+
class Object
|
35
|
+
# Cancels the current transaction without an error:
|
36
|
+
#
|
37
|
+
# DB.tranaction do
|
38
|
+
# ...
|
39
|
+
# rollback! if failed_to_contact_client
|
40
|
+
# ...
|
41
|
+
# end
|
42
|
+
def rollback!
|
43
|
+
raise Sequel::Error::Rollback
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# The migration code is based on work by Florian Aßmann:
|
2
|
+
# http://code.google.com/p/ruby-sequel/issues/detail?id=23
|
3
|
+
|
4
|
+
module Sequel
|
5
|
+
# The Migration class describes a database migration that can be reversed.
|
6
|
+
# The migration looks very similar to ActiveRecord (Rails) migrations, e.g.:
|
7
|
+
#
|
8
|
+
# class CreateSessions < Sequel::Migration
|
9
|
+
# def up
|
10
|
+
# create_table :sessions do
|
11
|
+
# primary_key :id
|
12
|
+
# varchar :session_id, :size => 32, :unique => true
|
13
|
+
# timestamp :created_at
|
14
|
+
# text :data
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# def down
|
19
|
+
# execute 'DROP TABLE sessions'
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# To apply a migration to a database, you can invoke the #apply with
|
24
|
+
# the target database instance and the direction :up or :down, e.g.:
|
25
|
+
#
|
26
|
+
# DB = Sequel.open ('sqlite:///mydb')
|
27
|
+
# CreateSessions.apply(DB, :up)
|
28
|
+
#
|
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
|
+
# Adds the new migration class to the list of Migration descendants.
|
36
|
+
def self.inherited(base)
|
37
|
+
descendants << base
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the list of Migration descendants.
|
41
|
+
def self.descendants
|
42
|
+
@descendants ||= []
|
43
|
+
end
|
44
|
+
|
45
|
+
def up; end #:nodoc:
|
46
|
+
def down; end #:nodoc:
|
47
|
+
|
48
|
+
# Applies the migration to the supplied database in the specified
|
49
|
+
# direction.
|
50
|
+
def self.apply(db, direction)
|
51
|
+
obj = new(db)
|
52
|
+
case direction
|
53
|
+
when :up
|
54
|
+
obj.up
|
55
|
+
when :down
|
56
|
+
obj.down
|
57
|
+
else
|
58
|
+
raise ArgumentError, "Invalid migration direction specified (#{direction.inspect})"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Intercepts method calls intended for the database and sends them along.
|
63
|
+
def method_missing(method_sym, *args, &block)
|
64
|
+
@db.send method_sym, *args, &block
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# The Migrator module performs migrations based on migration files in a
|
69
|
+
# specified directory. The migration files should be named using the
|
70
|
+
# following pattern (in similar fashion to ActiveRecord migrations):
|
71
|
+
#
|
72
|
+
# <version>_<title>.rb
|
73
|
+
#
|
74
|
+
# For example, the following files are considered migration files:
|
75
|
+
#
|
76
|
+
# 001_create_sessions.rb
|
77
|
+
# 002_add_data_column.rb
|
78
|
+
# ...
|
79
|
+
#
|
80
|
+
# The migration files should contain one or more migration classes based
|
81
|
+
# on Sequel::Migration.
|
82
|
+
#
|
83
|
+
# To apply a migration, the #apply method must be invoked with the database
|
84
|
+
# instance, the directory of migration files and the target version. If
|
85
|
+
# no current version is supplied, it is read from the database. The migrator
|
86
|
+
# automatically creates a schema_info table in the database to keep track
|
87
|
+
# of the current migration version. If no migration version is stored in the
|
88
|
+
# database, the version is considered to be 0. If no target version is
|
89
|
+
# specified, the database is migrated to the latest version available in the
|
90
|
+
# migration directory.
|
91
|
+
#
|
92
|
+
# For example, to migrate the database to the latest version:
|
93
|
+
#
|
94
|
+
# Sequel::Migrator.apply(DB, '.')
|
95
|
+
#
|
96
|
+
# To migrate the database from version 1 to version 5:
|
97
|
+
#
|
98
|
+
# Sequel::Migrator.apply(DB, '.', 5, 1)
|
99
|
+
#
|
100
|
+
module Migrator
|
101
|
+
# Migrates the supplied database in the specified directory from the
|
102
|
+
# current version to the target version. If no current version is
|
103
|
+
# supplied, it is extracted from a schema_info table. The schema_info
|
104
|
+
# table is automatically created and maintained by the apply function.
|
105
|
+
def self.apply(db, directory, target = nil, current = nil)
|
106
|
+
# determine current and target version and direction
|
107
|
+
current ||= get_current_migration_version(db)
|
108
|
+
target ||= latest_migration_version(directory)
|
109
|
+
raise Error, "No current version available" if current.nil?
|
110
|
+
raise Error, "No target version available" if target.nil?
|
111
|
+
|
112
|
+
direction = current < target ? :up : :down
|
113
|
+
|
114
|
+
classes = migration_classes(directory, target, current, direction)
|
115
|
+
|
116
|
+
db.transaction do
|
117
|
+
classes.each {|c| c.apply(db, direction)}
|
118
|
+
set_current_migration_version(db, target)
|
119
|
+
end
|
120
|
+
|
121
|
+
target
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns a list of migration classes filtered for the migration range and
|
125
|
+
# ordered according to the migration direction.
|
126
|
+
def self.migration_classes(directory, target, current, direction)
|
127
|
+
range = direction == :up ?
|
128
|
+
(current + 1)..target : (target + 1)..current
|
129
|
+
|
130
|
+
# Remove class definitions
|
131
|
+
Migration.descendants.each do |c|
|
132
|
+
Object.send(:remove_const, c.to_s) rescue nil
|
133
|
+
end
|
134
|
+
Migration.descendants.clear # remove any defined migration classes
|
135
|
+
|
136
|
+
# load migration files
|
137
|
+
migration_files(directory, range).each {|fn| load(fn)}
|
138
|
+
|
139
|
+
# get migration classes
|
140
|
+
classes = Migration.descendants
|
141
|
+
classes.reverse! if direction == :down
|
142
|
+
classes
|
143
|
+
end
|
144
|
+
|
145
|
+
MIGRATION_FILE_PATTERN = '[0-9][0-9][0-9]_*.rb'.freeze
|
146
|
+
|
147
|
+
# Returns any found migration files in the supplied directory.
|
148
|
+
def self.migration_files(directory, range = nil)
|
149
|
+
pattern = File.join(directory, MIGRATION_FILE_PATTERN)
|
150
|
+
files = Dir[pattern].inject([]) do |m, path|
|
151
|
+
m[File.basename(path).to_i] = path
|
152
|
+
m
|
153
|
+
end
|
154
|
+
filtered = range ? files[range] : files
|
155
|
+
filtered ? filtered.compact : []
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns the latest version available in the specified directory.
|
159
|
+
def self.latest_migration_version(directory)
|
160
|
+
l = migration_files(directory).last
|
161
|
+
l ? File.basename(l).to_i : nil
|
162
|
+
end
|
163
|
+
|
164
|
+
# Gets the current migration version stored in the database. If no version
|
165
|
+
# number is stored, 0 is returned.
|
166
|
+
def self.get_current_migration_version(db)
|
167
|
+
r = schema_info_dataset(db).first
|
168
|
+
r ? r[:version] : 0
|
169
|
+
end
|
170
|
+
|
171
|
+
# Sets the current migration version stored in the database.
|
172
|
+
def self.set_current_migration_version(db, version)
|
173
|
+
dataset = schema_info_dataset(db)
|
174
|
+
if dataset.first
|
175
|
+
dataset.update(:version => version)
|
176
|
+
else
|
177
|
+
dataset << {:version => version}
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns the dataset for the schema_info table. If no such table
|
182
|
+
# exists, it is automatically created.
|
183
|
+
def self.schema_info_dataset(db)
|
184
|
+
unless db.table_exists?(:schema_info)
|
185
|
+
db.create_table(:schema_info) {integer :version}
|
186
|
+
end
|
187
|
+
|
188
|
+
db[:schema_info]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Sequel
|
2
|
+
# Prints nice-looking plain-text tables
|
3
|
+
# +--+-------+
|
4
|
+
# |id|name |
|
5
|
+
# |--+-------|
|
6
|
+
# |1 |fasdfas|
|
7
|
+
# |2 |test |
|
8
|
+
# +--+-------+
|
9
|
+
module PrettyTable
|
10
|
+
def self.records_columns(records)
|
11
|
+
columns = []
|
12
|
+
records.each do |r|
|
13
|
+
if Array === r && (k = r.keys)
|
14
|
+
return k
|
15
|
+
elsif Hash === r
|
16
|
+
r.keys.each {|k| columns << k unless columns.include?(k)}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
columns
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.column_sizes(records, columns)
|
23
|
+
sizes = Hash.new {0}
|
24
|
+
columns.each do |c|
|
25
|
+
s = c.to_s.size
|
26
|
+
sizes[c.to_sym] = s if s > sizes[c.to_sym]
|
27
|
+
end
|
28
|
+
records.each do |r|
|
29
|
+
columns.each do |c|
|
30
|
+
s = r[c].to_s.size
|
31
|
+
sizes[c.to_sym] = s if s > sizes[c.to_sym]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
sizes
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.separator_line(columns, sizes)
|
38
|
+
l = ''
|
39
|
+
'+' + columns.map {|c| '-' * sizes[c]}.join('+') + '+'
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.format_cell(size, v)
|
43
|
+
case v
|
44
|
+
when Bignum, Fixnum
|
45
|
+
"%#{size}d" % v
|
46
|
+
when Float
|
47
|
+
"%#{size}g" % v
|
48
|
+
else
|
49
|
+
"%-#{size}s" % v.to_s
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.data_line(columns, sizes, record)
|
54
|
+
'|' + columns.map {|c| format_cell(sizes[c], record[c])}.join('|') + '|'
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.header_line(columns, sizes)
|
58
|
+
'|' + columns.map {|c| "%-#{sizes[c]}s" % c.to_s}.join('|') + '|'
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.print(records, columns = nil) # records is an array of hashes
|
62
|
+
columns ||= records_columns(records)
|
63
|
+
sizes = column_sizes(records, columns)
|
64
|
+
|
65
|
+
puts separator_line(columns, sizes)
|
66
|
+
puts header_line(columns, sizes)
|
67
|
+
puts separator_line(columns, sizes)
|
68
|
+
records.each {|r| puts data_line(columns, sizes, r)}
|
69
|
+
puts separator_line(columns, sizes)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Sequel
|
2
|
+
module Schema
|
3
|
+
class Generator
|
4
|
+
def initialize(db, &block)
|
5
|
+
@db = db
|
6
|
+
@columns = []
|
7
|
+
@indexes = []
|
8
|
+
@primary_key = nil
|
9
|
+
instance_eval(&block) if block
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(type, name = nil, opts = {})
|
13
|
+
name ? column(name, type, opts) : super
|
14
|
+
end
|
15
|
+
|
16
|
+
def primary_key_name
|
17
|
+
@primary_key ? @primary_key[:name] : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def primary_key(name, *args)
|
21
|
+
@primary_key = @db.serial_primary_key_options.merge({:name => name})
|
22
|
+
|
23
|
+
if opts = args.pop
|
24
|
+
opts = {:type => opts} unless opts.is_a?(Hash)
|
25
|
+
if type = args.pop
|
26
|
+
opts.merge!(:type => type)
|
27
|
+
end
|
28
|
+
@primary_key.merge!(opts)
|
29
|
+
end
|
30
|
+
@primary_key
|
31
|
+
end
|
32
|
+
|
33
|
+
def column(name, type, opts = {})
|
34
|
+
@columns << {:name => name, :type => type}.merge(opts)
|
35
|
+
index(name) if opts[:index]
|
36
|
+
end
|
37
|
+
|
38
|
+
def foreign_key(name, opts = {})
|
39
|
+
@columns << {:name => name, :type => :integer}.merge(opts)
|
40
|
+
index(name) if opts[:index]
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_column?(name)
|
44
|
+
@columns.each {|c| return true if c[:name] == name}
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def index(columns, opts = {})
|
49
|
+
columns = [columns] unless columns.is_a?(Array)
|
50
|
+
@indexes << {:columns => columns}.merge(opts)
|
51
|
+
end
|
52
|
+
|
53
|
+
def unique(columns, opts = {})
|
54
|
+
index(columns, {:unique => true}.merge(opts))
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_info
|
58
|
+
if @primary_key && !has_column?(@primary_key[:name])
|
59
|
+
@columns.unshift(@primary_key)
|
60
|
+
end
|
61
|
+
[@columns, @indexes]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class AlterTableGenerator
|
66
|
+
attr_reader :operations
|
67
|
+
|
68
|
+
def initialize(db, &block)
|
69
|
+
@db = db
|
70
|
+
@operations = []
|
71
|
+
instance_eval(&block) if block
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_column(name, type, opts = {})
|
75
|
+
@operations << { \
|
76
|
+
:op => :add_column, \
|
77
|
+
:name => name, \
|
78
|
+
:type => type \
|
79
|
+
}.merge(opts)
|
80
|
+
end
|
81
|
+
|
82
|
+
def drop_column(name)
|
83
|
+
@operations << { \
|
84
|
+
:op => :drop_column, \
|
85
|
+
:name => name \
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def rename_column(name, new_name, opts = {})
|
90
|
+
@operations << { \
|
91
|
+
:op => :rename_column, \
|
92
|
+
:name => name, \
|
93
|
+
:new_name => new_name \
|
94
|
+
}.merge(opts)
|
95
|
+
end
|
96
|
+
|
97
|
+
def set_column_type(name, type)
|
98
|
+
@operations << { \
|
99
|
+
:op => :set_column_type, \
|
100
|
+
:name => name, \
|
101
|
+
:type => type \
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
def set_column_default(name, default)
|
106
|
+
@operations << { \
|
107
|
+
:op => :set_column_default, \
|
108
|
+
:name => name, \
|
109
|
+
:default => default \
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def add_index(columns, opts = {})
|
114
|
+
columns = [columns] unless columns.is_a?(Array)
|
115
|
+
@operations << { \
|
116
|
+
:op => :add_index, \
|
117
|
+
:columns => columns \
|
118
|
+
}.merge(opts)
|
119
|
+
end
|
120
|
+
|
121
|
+
def drop_index(columns)
|
122
|
+
columns = [columns] unless columns.is_a?(Array)
|
123
|
+
@operations << { \
|
124
|
+
:op => :drop_index, \
|
125
|
+
:columns => columns \
|
126
|
+
}
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|