sequel_core 1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|