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,152 @@
1
+ require 'thread'
2
+
3
+ module Sequel
4
+ # A ConnectionPool manages access to database connections by keeping
5
+ # multiple connections and giving threads exclusive access to each
6
+ # connection.
7
+ class ConnectionPool
8
+ attr_reader :mutex
9
+
10
+ # The maximum number of connections.
11
+ attr_reader :max_size
12
+
13
+ # The proc used to create a new connection.
14
+ attr_accessor :connection_proc
15
+
16
+ attr_reader :available_connections, :allocated, :created_count
17
+
18
+ # Constructs a new pool with a maximum size. If a block is supplied, it
19
+ # is used to create new connections as they are needed.
20
+ #
21
+ # pool = ConnectionPool.new(10) {MyConnection.new(opts)}
22
+ #
23
+ # The connection creation proc can be changed at any time by assigning a
24
+ # Proc to pool#connection_proc.
25
+ #
26
+ # pool = ConnectionPool.new(10)
27
+ # pool.connection_proc = proc {MyConnection.new(opts)}
28
+ def initialize(max_size = 4, &block)
29
+ @max_size = max_size
30
+ @mutex = Mutex.new
31
+ @connection_proc = block
32
+
33
+ @available_connections = []
34
+ @allocated = {}
35
+ @created_count = 0
36
+ end
37
+
38
+ # Returns the number of created connections.
39
+ def size
40
+ @created_count
41
+ end
42
+
43
+ # Assigns a connection to the current thread, yielding the connection
44
+ # to the supplied block.
45
+ #
46
+ # pool.hold {|conn| conn.execute('DROP TABLE posts')}
47
+ #
48
+ # Pool#hold is re-entrant, meaning it can be called recursively in
49
+ # the same thread without blocking.
50
+ #
51
+ # If no connection is available, Pool#hold will block until a connection
52
+ # is available.
53
+ def hold
54
+ t = Thread.current
55
+ if (conn = owned_connection(t))
56
+ return yield(conn)
57
+ end
58
+ while !(conn = acquire(t))
59
+ sleep 0.001
60
+ end
61
+ begin
62
+ yield conn
63
+ ensure
64
+ release(t)
65
+ end
66
+ rescue Exception => e
67
+ # if the error is not a StandardError it is converted into RuntimeError.
68
+ raise e.is_a?(StandardError) ? e : e.message
69
+ end
70
+
71
+ # Removes all connection currently available, optionally yielding each
72
+ # connection to the given block. This method has the effect of
73
+ # disconnecting from the database. Once a connection is requested using
74
+ # #hold, the connection pool creates new connections to the database.
75
+ def disconnect(&block)
76
+ @mutex.synchronize do
77
+ @available_connections.each {|c| block[c]} if block
78
+ @available_connections = []
79
+ @created_count = @allocated.size
80
+ end
81
+ end
82
+
83
+ private
84
+ # Returns the connection owned by the supplied thread, if any.
85
+ def owned_connection(thread)
86
+ @mutex.synchronize {@allocated[thread]}
87
+ end
88
+
89
+ # Assigns a connection to the supplied thread, if one is available.
90
+ def acquire(thread)
91
+ @mutex.synchronize do
92
+ if conn = available
93
+ @allocated[thread] = conn
94
+ end
95
+ end
96
+ end
97
+
98
+ # Returns an available connection. If no connection is available,
99
+ # tries to create a new connection.
100
+ def available
101
+ @available_connections.pop || make_new
102
+ end
103
+
104
+ # Creates a new connection if the size of the pool is less than the
105
+ # maximum size.
106
+ def make_new
107
+ if @created_count < @max_size
108
+ @created_count += 1
109
+ @connection_proc ? @connection_proc.call : \
110
+ (raise Error, "No connection proc specified")
111
+ end
112
+ end
113
+
114
+ # Releases the connection assigned to the supplied thread.
115
+ def release(thread)
116
+ @mutex.synchronize do
117
+ @available_connections << @allocated[thread]
118
+ @allocated.delete(thread)
119
+ end
120
+ end
121
+ end
122
+
123
+ # A SingleThreadedPool acts as a replacement for a ConnectionPool for use
124
+ # in single-threaded applications. ConnectionPool imposes a substantial
125
+ # performance penalty, so SingleThreadedPool is used to gain some speed.
126
+ class SingleThreadedPool
127
+ attr_reader :conn
128
+ attr_writer :connection_proc
129
+
130
+ # Initializes the instance with the supplied block as the connection_proc.
131
+ def initialize(&block)
132
+ @connection_proc = block
133
+ end
134
+
135
+ # Yields the connection to the supplied block. This method simulates the
136
+ # ConnectionPool#hold API.
137
+ def hold
138
+ @conn ||= @connection_proc.call
139
+ yield @conn
140
+ rescue Exception => e
141
+ # if the error is not a StandardError it is converted into RuntimeError.
142
+ raise e.is_a?(StandardError) ? e : e.message
143
+ end
144
+
145
+ # Disconnects from the database. Once a connection is requested using
146
+ # #hold, the connection is reestablished.
147
+ def disconnect(&block)
148
+ block[@conn] if block && @conn
149
+ @conn = nil
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,59 @@
1
+ # Enumerable extensions.
2
+ module Enumerable
3
+ # Invokes the specified method for each item, along with the supplied
4
+ # arguments.
5
+ def send_each(sym, *args)
6
+ each {|i| i.send(sym, *args)}
7
+ end
8
+ end
9
+
10
+ # Range extensions
11
+ class Range
12
+ # Returns the interval between the beginning and end of the range.
13
+ def interval
14
+ last - first
15
+ end
16
+ end
17
+
18
+ # Object extensions
19
+ class Object
20
+ def is_one_of?(*classes)
21
+ classes.each {|c| return c if is_a?(c)}
22
+ nil
23
+ end
24
+ end
25
+
26
+ module Sequel
27
+ # Facilitates time calculations by providing methods to convert from larger
28
+ # time units to seconds, and to convert relative time intervals to absolute
29
+ # ones. This module duplicates some of the functionality provided by Rails'
30
+ # ActiveSupport::CoreExtensions::Numeric::Time module.
31
+ module NumericExtensions
32
+ MINUTE = 60
33
+ HOUR = 3600
34
+ DAY = 86400
35
+ WEEK = DAY * 7
36
+
37
+ # Converts self from minutes to seconds
38
+ def minutes; self * MINUTE; end; alias_method :minute, :minutes
39
+ # Converts self from hours to seconds
40
+ def hours; self * HOUR; end; alias_method :hour, :hours
41
+ # Converts self from days to seconds
42
+ def days; self * DAY; end; alias_method :day, :days
43
+ # Converts self from weeks to seconds
44
+ def weeks; self * WEEK; end; alias_method :week, :weeks
45
+
46
+ # Returns the time at now - self.
47
+ def ago(t = Time.now); t - self; end
48
+ alias_method :before, :ago
49
+
50
+ # Returns the time at now + self.
51
+ def from_now(t = Time.now); t + self; end
52
+ alias_method :since, :from_now
53
+
54
+ # Extends the Numeric class with numeric extensions.
55
+ def self.enable
56
+ Numeric.send(:include, self)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,191 @@
1
+ # Array extensions
2
+ class Array
3
+ # Concatenates an array of strings into an SQL string. ANSI SQL and C-style
4
+ # comments are removed, as well as excessive white-space.
5
+ def to_sql
6
+ map {|l| (l =~ /^(.*)--/ ? $1 : l).chomp}.join(' '). \
7
+ gsub(/\/\*.*\*\//, '').gsub(/\s+/, ' ').strip
8
+ end
9
+ end
10
+
11
+ module Sequel
12
+ # LiteralString is used to represent literal SQL expressions. An
13
+ # LiteralString is copied verbatim into an SQL statement. Instances of
14
+ # LiteralString can be created by calling String#expr.
15
+ class LiteralString < ::String
16
+ end
17
+ end
18
+
19
+ # String extensions
20
+ class String
21
+ # Converts a string into an SQL string by removing comments.
22
+ # See also Array#to_sql.
23
+ def to_sql
24
+ split($/).to_sql
25
+ end
26
+
27
+ # Splits a string into separate SQL statements, removing comments
28
+ # and excessive white-space.
29
+ def split_sql
30
+ to_sql.split(';').map {|s| s.strip}
31
+ end
32
+
33
+ # Converts a string into an LiteralString, in order to override string
34
+ # literalization, e.g.:
35
+ #
36
+ # DB[:items].filter(:abc => 'def').sql #=>
37
+ # "SELECT * FROM items WHERE (abc = 'def')"
38
+ #
39
+ # DB[:items].filter(:abc => 'def'.lit).sql #=>
40
+ # "SELECT * FROM items WHERE (abc = def)"
41
+ #
42
+ def lit
43
+ Sequel::LiteralString.new(self)
44
+ end
45
+
46
+ alias_method :expr, :lit
47
+
48
+ # Converts a string into a Time object.
49
+ def to_time
50
+ begin
51
+ Time.parse(self)
52
+ rescue Exception => e
53
+ raise Sequel::Error::InvalidValue, "Invalid time value '#{self}' (#{e.message})"
54
+ end
55
+ # Why does Time.parse('0000-00-00') bork and not return nil or some such?
56
+ end
57
+ end
58
+
59
+
60
+ module Sequel
61
+ module SQL
62
+ class Expression
63
+ def lit; self; end
64
+ end
65
+
66
+ class ColumnExpr < Expression
67
+ attr_reader :l, :op, :r
68
+ def initialize(l, op, r = nil); @l, @op, @r = l, op, r; end
69
+
70
+ def to_s(ds)
71
+ @r ? \
72
+ "#{ds.literal(@l)} #{@op} #{ds.literal(@r)}" : \
73
+ "#{ds.literal(@l)} #{@op}"
74
+ end
75
+ end
76
+
77
+ class QualifiedColumnRef < Expression
78
+ def initialize(t, c); @t, @c = t, c; end
79
+
80
+ def to_s(ds)
81
+ "#{@t}.#{ds.literal(@c)}"
82
+ end
83
+ end
84
+
85
+ class Function < Expression
86
+ def initialize(f, *args); @f, @args = f, args; end
87
+
88
+ def to_s(ds)
89
+ args = @args.empty? ? '' : ds.literal(@args)
90
+ "#{@f}(#{args})"
91
+ end
92
+ end
93
+
94
+ class Subscript < Expression
95
+ def initialize(f, sub); @f, @sub = f, sub; end
96
+ def |(sub)
97
+ unless Array === sub
98
+ sub = [sub]
99
+ end
100
+ Subscript.new(@f, @sub << sub)
101
+ end
102
+
103
+ COMMA_SEPARATOR = ", ".freeze
104
+
105
+ def to_s(ds)
106
+ "#{@f}[#{@sub.join(COMMA_SEPARATOR)}]"
107
+ end
108
+ end
109
+
110
+ class ColumnAll < Expression
111
+ def initialize(t); @t = t; end
112
+ def to_s(ds); "#{@t}.*"; end
113
+ end
114
+
115
+ module ColumnMethods
116
+ AS = 'AS'.freeze
117
+ DESC = 'DESC'.freeze
118
+ ASC = 'ASC'.freeze
119
+
120
+ def as(a); ColumnExpr.new(self, AS, a); end
121
+ alias_method :AS, :as
122
+
123
+ def desc; ColumnExpr.new(self, DESC); end
124
+ alias_method :DESC, :desc
125
+
126
+ def asc; ColumnExpr.new(self, ASC); end
127
+ alias_method :ASC, :asc
128
+
129
+ def all; Sequel::SQL::ColumnAll.new(self); end
130
+ alias_method :ALL, :all
131
+
132
+ def cast_as(t)
133
+ if t.is_a?(Symbol)
134
+ t = t.to_s.lit
135
+ end
136
+ Sequel::SQL::Function.new(:cast, self.as(t))
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ class Object
143
+ include Sequel::SQL::ColumnMethods
144
+ end
145
+
146
+ class Symbol
147
+ def [](*args); Sequel::SQL::Function.new(self, *args); end
148
+ def |(sub)
149
+ unless Array === sub
150
+ sub = [sub]
151
+ end
152
+ Sequel::SQL::Subscript.new(self, sub)
153
+ end
154
+
155
+ COLUMN_REF_RE1 = /^(\w+)__(\w+)___(\w+)/.freeze
156
+ COLUMN_REF_RE2 = /^(\w+)___(\w+)$/.freeze
157
+ COLUMN_REF_RE3 = /^(\w+)__(\w+)$/.freeze
158
+
159
+ # Converts a symbol into a column name. This method supports underscore
160
+ # notation in order to express qualified (two underscores) and aliased
161
+ # (three underscores) columns:
162
+ #
163
+ # ds = DB[:items]
164
+ # :abc.to_column_ref(ds) #=> "abc"
165
+ # :abc___a.to_column_ref(ds) #=> "abc AS a"
166
+ # :items__abc.to_column_ref(ds) #=> "items.abc"
167
+ # :items__abc___a.to_column_ref(ds) #=> "items.abc AS a"
168
+ #
169
+ def to_column_ref(ds)
170
+ case s = to_s
171
+ when COLUMN_REF_RE1
172
+ "#{$1}.#{ds.quote_column_ref($2)} AS #{ds.quote_column_ref($3)}"
173
+ when COLUMN_REF_RE2
174
+ "#{ds.quote_column_ref($1)} AS #{ds.quote_column_ref($2)}"
175
+ when COLUMN_REF_RE3
176
+ "#{$1}.#{ds.quote_column_ref($2)}"
177
+ else
178
+ ds.quote_column_ref(s)
179
+ end
180
+ end
181
+
182
+ # Converts missing method calls into functions on columns, if the
183
+ # method name is made of all upper case letters.
184
+ def method_missing(sym)
185
+ if ((s = sym.to_s) =~ /^([A-Z]+)$/)
186
+ Sequel::SQL::Function.new(s.downcase, self)
187
+ else
188
+ super
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,433 @@
1
+ require 'uri'
2
+
3
+ module Sequel
4
+ # A Database object represents a virtual connection to a database.
5
+ # The Database class is meant to be subclassed by database adapters in order
6
+ # to provide the functionality needed for executing queries.
7
+ class Database
8
+ attr_reader :opts, :pool
9
+ attr_accessor :logger
10
+
11
+ # Constructs a new instance of a database connection with the specified
12
+ # options hash.
13
+ #
14
+ # Sequel::Database is an abstract class that is not useful by itself.
15
+ def initialize(opts = {}, &block)
16
+ Model.database_opened(self)
17
+ @opts = opts
18
+
19
+ # Determine if the DB is single threaded or multi threaded
20
+ @single_threaded = opts[:single_threaded] || @@single_threaded
21
+ # Construct pool
22
+ if @single_threaded
23
+ @pool = SingleThreadedPool.new(&block)
24
+ else
25
+ @pool = ConnectionPool.new(opts[:max_connections] || 4, &block)
26
+ end
27
+ @pool.connection_proc = block || proc {connect}
28
+
29
+ @logger = opts[:logger]
30
+ end
31
+
32
+ # Connects to the database. This method should be overriden by descendants.
33
+ def connect
34
+ raise NotImplementedError, "#connect should be overriden by adapters"
35
+ end
36
+
37
+ # Disconnects from the database. This method should be overriden by
38
+ # descendants.
39
+ def disconnect
40
+ raise NotImplementedError, "#disconnect should be overriden by adapters"
41
+ end
42
+
43
+ # Returns true if the database is using a multi-threaded connection pool.
44
+ def multi_threaded?
45
+ !@single_threaded
46
+ end
47
+
48
+ # Returns true if the database is using a single-threaded connection pool.
49
+ def single_threaded?
50
+ @single_threaded
51
+ end
52
+
53
+ # Returns the URI identifying the database.
54
+ def uri
55
+ uri = URI::Generic.new(
56
+ self.class.adapter_scheme.to_s,
57
+ nil,
58
+ @opts[:host],
59
+ @opts[:port],
60
+ nil,
61
+ "/#{@opts[:database]}",
62
+ nil,
63
+ nil,
64
+ nil
65
+ )
66
+ uri.user = @opts[:user]
67
+ uri.password = @opts[:password] if uri.user
68
+ uri.to_s
69
+ end
70
+ alias url uri # Because I don't care much for the semantic difference.
71
+
72
+ # Returns a blank dataset
73
+ def dataset
74
+ ds = Sequel::Dataset.new(self)
75
+ end
76
+
77
+ # Fetches records for an arbitrary SQL statement. If a block is given,
78
+ # it is used to iterate over the records:
79
+ #
80
+ # DB.fetch('SELECT * FROM items') {|r| p r}
81
+ #
82
+ # If a block is not given, the method returns a dataset instance:
83
+ #
84
+ # DB.fetch('SELECT * FROM items').print
85
+ #
86
+ # Fetch can also perform parameterized queries for protection against SQL
87
+ # injection:
88
+ #
89
+ # DB.fetch('SELECT * FROM items WHERE name = ?', my_name).print
90
+ #
91
+ # A short-hand form for Database#fetch is Database#[]:
92
+ #
93
+ # DB['SELECT * FROM items'].each {|r| p r}
94
+ #
95
+ def fetch(sql, *args, &block)
96
+ ds = dataset
97
+ sql = sql.gsub('?') {|m| ds.literal(args.shift)}
98
+ if block
99
+ ds.fetch_rows(sql, &block)
100
+ else
101
+ ds.opts[:sql] = sql
102
+ ds
103
+ end
104
+ end
105
+ alias_method :>>, :fetch
106
+
107
+ # Converts a query block into a dataset. For more information see
108
+ # Dataset#query.
109
+ def query(&block)
110
+ dataset.query(&block)
111
+ end
112
+
113
+ # Returns a new dataset with the from method invoked. If a block is given,
114
+ # it is used as a filter on the dataset.
115
+ def from(*args, &block)
116
+ ds = dataset.from(*args)
117
+ block ? ds.filter(&block) : ds
118
+ end
119
+
120
+ # Returns a new dataset with the select method invoked.
121
+ def select(*args); dataset.select(*args); end
122
+
123
+ # Returns a dataset from the database. If the first argument is a string,
124
+ # the method acts as an alias for Database#fetch, returning a dataset for
125
+ # arbitrary SQL:
126
+ #
127
+ # DB['SELECT * FROM items WHERE name = ?', my_name].print
128
+ #
129
+ # Otherwise, the dataset returned has its from option set to the given
130
+ # arguments:
131
+ #
132
+ # DB[:items].sql #=> "SELECT * FROM items"
133
+ #
134
+ def [](*args)
135
+ (String === args.first) ? fetch(*args) : from(*args)
136
+ end
137
+
138
+ # Raises a Sequel::Error::NotImplemented. This method is overriden in descendants.
139
+ def execute(sql)
140
+ raise NotImplementedError, "#execute should be overriden by adapters"
141
+ end
142
+
143
+ # Executes the supplied SQL statement. The SQL can be supplied as a string
144
+ # or as an array of strings. If an array is give, comments and excessive
145
+ # white space are removed. See also Array#to_sql.
146
+ def <<(sql); execute((Array === sql) ? sql.to_sql : sql); end
147
+
148
+ # Acquires a database connection, yielding it to the passed block.
149
+ def synchronize(&block)
150
+ @pool.hold(&block)
151
+ end
152
+
153
+ # Returns true if there is a database connection
154
+ def test_connection
155
+ @pool.hold {|conn|}
156
+ true
157
+ end
158
+
159
+ # include Dataset::SQL
160
+ include Schema::SQL
161
+
162
+ # default serial primary key definition. this should be overriden for each adapter.
163
+ def serial_primary_key_options
164
+ {:primary_key => true, :type => :integer, :auto_increment => true}
165
+ end
166
+
167
+ # Creates a table. The easiest way to use this method is to provide a
168
+ # block:
169
+ # DB.create_table :posts do
170
+ # primary_key :id, :serial
171
+ # column :title, :text
172
+ # column :content, :text
173
+ # index :title
174
+ # end
175
+ def create_table(name, &block)
176
+ g = Schema::Generator.new(self, &block)
177
+ create_table_sql_list(name, *g.create_info).each {|sql| execute(sql)}
178
+ end
179
+
180
+ # Forcibly creates a table. If the table already exists it is dropped.
181
+ def create_table!(name, &block)
182
+ drop_table(name) rescue nil
183
+ create_table(name, &block)
184
+ end
185
+
186
+ # Drops one or more tables corresponding to the given table names.
187
+ def drop_table(*names)
188
+ names.each {|n| execute(drop_table_sql(n))}
189
+ end
190
+
191
+ # Renames a table:
192
+ #
193
+ # DB.tables #=> [:items]
194
+ # DB.rename_table :items, :old_items
195
+ # DB.tables #=> [:old_items]
196
+ def rename_table(*args)
197
+ execute(rename_table_sql(*args))
198
+ end
199
+
200
+ # Alters the given table with the specified block. Here are the currently
201
+ # available operations:
202
+ #
203
+ # DB.alter_table :items do
204
+ # add_column :category, :text, :default => 'ruby'
205
+ # drop_column :category
206
+ # rename_column :cntr, :counter
207
+ # set_column_type :value, :float
208
+ # set_column_default :value, :float
209
+ # add_index [:group, :category]
210
+ # drop_index [:group, :category]
211
+ # end
212
+ #
213
+ # Note that #add_column accepts all the options available for column
214
+ # definitions using create_table, and #add_index accepts all the options
215
+ # available for index definition.
216
+ def alter_table(name, &block)
217
+ g = Schema::AlterTableGenerator.new(self, &block)
218
+ alter_table_sql_list(name, g.operations).each {|sql| execute(sql)}
219
+ end
220
+
221
+ # Adds a column to the specified table. This method expects a column name,
222
+ # a datatype and optionally a hash with additional constraints and options:
223
+ #
224
+ # DB.add_column :items, :name, :text, :unique => true, :null => false
225
+ # DB.add_column :items, :category, :text, :default => 'ruby'
226
+ def add_column(table, *args)
227
+ alter_table(table) {add_column(*args)}
228
+ end
229
+
230
+ # Removes a column from the specified table:
231
+ #
232
+ # DB.drop_column :items, :category
233
+ def drop_column(table, *args)
234
+ alter_table(table) {drop_column(*args)}
235
+ end
236
+
237
+ # Renames a column in the specified table. This method expects the current
238
+ # column name and the new column name:
239
+ #
240
+ # DB.rename_column :items, :cntr, :counter
241
+ def rename_column(table, *args)
242
+ alter_table(table) {rename_column(*args)}
243
+ end
244
+
245
+ # Set the data type for the given column in the given table:
246
+ #
247
+ # DB.set_column_type :items, :price, :float
248
+ def set_column_type(table, *args)
249
+ alter_table(table) {set_column_type(*args)}
250
+ end
251
+
252
+ # Sets the default value for the given column in the given table:
253
+ #
254
+ # DB.set_column_default :items, :category, 'perl!'
255
+ def set_column_default(table, *args)
256
+ alter_table(table) {set_column_default(*args)}
257
+ end
258
+
259
+ # Adds an index to a table for the given columns:
260
+ #
261
+ # DB.add_index :posts, :title
262
+ # DB.add_index :posts, [:author, :title], :unique => true
263
+ def add_index(table, *args)
264
+ alter_table(table) {add_index(*args)}
265
+ end
266
+
267
+ # Removes an index for the given table and column/s:
268
+ #
269
+ # DB.drop_index :posts, :title
270
+ # DB.drop_index :posts, [:author, :title]
271
+ def drop_index(table, *args)
272
+ alter_table(table) {drop_index(*args)}
273
+ end
274
+
275
+ # Returns true if the given table exists.
276
+ def table_exists?(name)
277
+ if respond_to?(:tables)
278
+ tables.include?(name.to_sym)
279
+ else
280
+ from(name).first && true
281
+ end
282
+ rescue
283
+ false
284
+ end
285
+
286
+ # Creates a view based on a dataset or an SQL string:
287
+ #
288
+ # DB.create_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
289
+ # DB.create_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
290
+ def create_view(name, source)
291
+ source = source.sql if source.is_a?(Dataset)
292
+ execute("CREATE VIEW #{name} AS #{source}")
293
+ end
294
+
295
+ # Creates a view, replacing it if it already exists:
296
+ #
297
+ # DB.create_or_replace_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
298
+ # DB.create_or_replace_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
299
+ def create_or_replace_view(name, source)
300
+ source = source.sql if source.is_a?(Dataset)
301
+ execute("CREATE OR REPLACE VIEW #{name} AS #{source}")
302
+ end
303
+
304
+ # Drops a view:
305
+ #
306
+ # DB.drop_view(:cheap_items)
307
+ def drop_view(name)
308
+ execute("DROP VIEW #{name}")
309
+ end
310
+
311
+ SQL_BEGIN = 'BEGIN'.freeze
312
+ SQL_COMMIT = 'COMMIT'.freeze
313
+ SQL_ROLLBACK = 'ROLLBACK'.freeze
314
+
315
+ # A simple implementation of SQL transactions. Nested transactions are not
316
+ # supported - calling #transaction within a transaction will reuse the
317
+ # current transaction. May be overridden for databases that support nested
318
+ # transactions.
319
+ def transaction
320
+ @pool.hold do |conn|
321
+ @transactions ||= []
322
+ if @transactions.include? Thread.current
323
+ return yield(conn)
324
+ end
325
+ conn.execute(SQL_BEGIN)
326
+ begin
327
+ @transactions << Thread.current
328
+ result = yield(conn)
329
+ conn.execute(SQL_COMMIT)
330
+ result
331
+ rescue => e
332
+ conn.execute(SQL_ROLLBACK)
333
+ raise e unless Error::Rollback === e
334
+ ensure
335
+ @transactions.delete(Thread.current)
336
+ end
337
+ end
338
+ end
339
+
340
+ @@adapters = Hash.new
341
+
342
+ # Sets the adapter scheme for the Database class. Call this method in
343
+ # descendnants of Database to allow connection using a URL. For example the
344
+ # following:
345
+ # class DB2::Database < Sequel::Database
346
+ # set_adapter_scheme :db2
347
+ # ...
348
+ # end
349
+ # would allow connection using:
350
+ # Sequel.open('db2://user:password@dbserver/mydb')
351
+ def self.set_adapter_scheme(scheme)
352
+ @scheme = scheme
353
+ @@adapters[scheme.to_sym] = self
354
+ end
355
+
356
+ # Returns the scheme for the Database class.
357
+ def self.adapter_scheme
358
+ @scheme
359
+ end
360
+
361
+ # Converts a uri to an options hash. These options are then passed
362
+ # to a newly created database object.
363
+ def self.uri_to_options(uri)
364
+ {
365
+ :user => uri.user,
366
+ :password => uri.password,
367
+ :host => uri.host,
368
+ :port => uri.port,
369
+ :database => (uri.path =~ /\/(.*)/) && ($1)
370
+ }
371
+ end
372
+
373
+ def self.adapter_class(scheme)
374
+ adapter_name = scheme.to_s =~ /\-/ ? scheme.to_s.gsub('-', '_').to_sym : scheme.to_sym
375
+ scheme = scheme.to_sym
376
+
377
+ if (klass = @@adapters[scheme]).nil?
378
+ # attempt to load the adapter file
379
+ begin
380
+ require File.join(File.dirname(__FILE__), "adapters/#{scheme}")
381
+ rescue LoadError => e
382
+ raise Error::AdapterNotFound, "Could not load #{scheme} adapter:\n #{e.message}"
383
+ end
384
+
385
+ # make sure we actually loaded the adapter
386
+ if (klass = @@adapters[scheme]).nil?
387
+ raise Error::AdapterNotFound, "Could not load #{scheme} adapter"
388
+ end
389
+ end
390
+ return klass
391
+ end
392
+
393
+ # call-seq:
394
+ # Sequel::Database.connect(conn_string)
395
+ # Sequel::Database.connect(opts)
396
+ # Sequel.connect(conn_string)
397
+ # Sequel.connect(opts)
398
+ # Sequel.open(conn_string)
399
+ # Sequel.open(opts)
400
+ #
401
+ # Creates a new database object based on the supplied connection string
402
+ # and or options. If a URI is used, the URI scheme determines the database
403
+ # class used, and the rest of the string specifies the connection options.
404
+ # For example:
405
+ #
406
+ # DB = Sequel.open 'sqlite:///blog.db'
407
+ #
408
+ # The second form of this method takes an options:
409
+ #
410
+ # DB = Sequel.open :adapter => :sqlite, :database => 'blog.db'
411
+ def self.connect(conn_string, opts = nil)
412
+ if conn_string.is_a?(String)
413
+ uri = URI.parse(conn_string)
414
+ scheme = uri.scheme
415
+ scheme = :dbi if scheme =~ /^dbi-(.+)/
416
+ c = adapter_class(scheme)
417
+ c.new(c.uri_to_options(uri).merge(opts || {}))
418
+ else
419
+ opts = conn_string.merge(opts || {})
420
+ c = adapter_class(opts[:adapter])
421
+ c.new(opts)
422
+ end
423
+ end
424
+
425
+ @@single_threaded = false
426
+
427
+ # Sets the default single_threaded mode for new databases.
428
+ def self.single_threaded=(value)
429
+ @@single_threaded = value
430
+ end
431
+ end
432
+ end
433
+