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