sequel_core 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CHANGELOG +1003 -0
  2. data/COPYING +18 -0
  3. data/README +81 -0
  4. data/Rakefile +176 -0
  5. data/bin/sequel +41 -0
  6. data/lib/sequel_core.rb +59 -0
  7. data/lib/sequel_core/adapters/adapter_skeleton.rb +68 -0
  8. data/lib/sequel_core/adapters/ado.rb +100 -0
  9. data/lib/sequel_core/adapters/db2.rb +158 -0
  10. data/lib/sequel_core/adapters/dbi.rb +126 -0
  11. data/lib/sequel_core/adapters/informix.rb +87 -0
  12. data/lib/sequel_core/adapters/jdbc.rb +108 -0
  13. data/lib/sequel_core/adapters/mysql.rb +269 -0
  14. data/lib/sequel_core/adapters/odbc.rb +145 -0
  15. data/lib/sequel_core/adapters/odbc_mssql.rb +93 -0
  16. data/lib/sequel_core/adapters/openbase.rb +90 -0
  17. data/lib/sequel_core/adapters/oracle.rb +99 -0
  18. data/lib/sequel_core/adapters/postgres.rb +519 -0
  19. data/lib/sequel_core/adapters/sqlite.rb +192 -0
  20. data/lib/sequel_core/array_keys.rb +296 -0
  21. data/lib/sequel_core/connection_pool.rb +152 -0
  22. data/lib/sequel_core/core_ext.rb +59 -0
  23. data/lib/sequel_core/core_sql.rb +191 -0
  24. data/lib/sequel_core/database.rb +433 -0
  25. data/lib/sequel_core/dataset.rb +409 -0
  26. data/lib/sequel_core/dataset/convenience.rb +321 -0
  27. data/lib/sequel_core/dataset/sequelizer.rb +354 -0
  28. data/lib/sequel_core/dataset/sql.rb +586 -0
  29. data/lib/sequel_core/exceptions.rb +45 -0
  30. data/lib/sequel_core/migration.rb +191 -0
  31. data/lib/sequel_core/model.rb +8 -0
  32. data/lib/sequel_core/pretty_table.rb +73 -0
  33. data/lib/sequel_core/schema.rb +8 -0
  34. data/lib/sequel_core/schema/schema_generator.rb +131 -0
  35. data/lib/sequel_core/schema/schema_sql.rb +131 -0
  36. data/lib/sequel_core/worker.rb +58 -0
  37. data/spec/adapters/informix_spec.rb +139 -0
  38. data/spec/adapters/mysql_spec.rb +330 -0
  39. data/spec/adapters/oracle_spec.rb +130 -0
  40. data/spec/adapters/postgres_spec.rb +189 -0
  41. data/spec/adapters/sqlite_spec.rb +345 -0
  42. data/spec/array_keys_spec.rb +679 -0
  43. data/spec/connection_pool_spec.rb +356 -0
  44. data/spec/core_ext_spec.rb +67 -0
  45. data/spec/core_sql_spec.rb +301 -0
  46. data/spec/database_spec.rb +812 -0
  47. data/spec/dataset_spec.rb +2381 -0
  48. data/spec/migration_spec.rb +261 -0
  49. data/spec/pretty_table_spec.rb +66 -0
  50. data/spec/rcov.opts +4 -0
  51. data/spec/schema_generator_spec.rb +86 -0
  52. data/spec/schema_spec.rb +230 -0
  53. data/spec/sequelizer_spec.rb +448 -0
  54. data/spec/spec.opts +5 -0
  55. data/spec/spec_helper.rb +44 -0
  56. data/spec/worker_spec.rb +96 -0
  57. 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,8 @@
1
+ module Sequel
2
+ class Model
3
+ def self.database_opened(db)
4
+ @db = db if (self == Model) && !@db
5
+ end
6
+ end
7
+ end
8
+
@@ -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,8 @@
1
+ module Sequel
2
+ module Schema
3
+ end
4
+ end
5
+
6
+ require File.join(File.dirname(__FILE__), 'schema/schema_sql')
7
+ require File.join(File.dirname(__FILE__), 'schema/schema_generator')
8
+
@@ -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
+