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.
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
+