sequel 2.3.0 → 2.4.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 (43) hide show
  1. data/CHANGELOG +16 -0
  2. data/README +4 -1
  3. data/Rakefile +17 -19
  4. data/doc/prepared_statements.rdoc +104 -0
  5. data/doc/sharding.rdoc +113 -0
  6. data/lib/sequel_core/adapters/ado.rb +24 -17
  7. data/lib/sequel_core/adapters/db2.rb +30 -33
  8. data/lib/sequel_core/adapters/dbi.rb +15 -13
  9. data/lib/sequel_core/adapters/informix.rb +13 -14
  10. data/lib/sequel_core/adapters/jdbc.rb +243 -60
  11. data/lib/sequel_core/adapters/jdbc/mysql.rb +32 -24
  12. data/lib/sequel_core/adapters/jdbc/postgresql.rb +32 -2
  13. data/lib/sequel_core/adapters/jdbc/sqlite.rb +16 -20
  14. data/lib/sequel_core/adapters/mysql.rb +164 -76
  15. data/lib/sequel_core/adapters/odbc.rb +21 -34
  16. data/lib/sequel_core/adapters/openbase.rb +10 -7
  17. data/lib/sequel_core/adapters/oracle.rb +17 -23
  18. data/lib/sequel_core/adapters/postgres.rb +246 -35
  19. data/lib/sequel_core/adapters/shared/mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/shared/mysql.rb +34 -26
  21. data/lib/sequel_core/adapters/shared/postgres.rb +82 -38
  22. data/lib/sequel_core/adapters/shared/sqlite.rb +48 -16
  23. data/lib/sequel_core/adapters/sqlite.rb +141 -44
  24. data/lib/sequel_core/connection_pool.rb +85 -63
  25. data/lib/sequel_core/database.rb +46 -17
  26. data/lib/sequel_core/dataset.rb +21 -40
  27. data/lib/sequel_core/dataset/convenience.rb +3 -3
  28. data/lib/sequel_core/dataset/prepared_statements.rb +218 -0
  29. data/lib/sequel_core/exceptions.rb +0 -12
  30. data/lib/sequel_model/base.rb +1 -2
  31. data/lib/sequel_model/plugins.rb +1 -1
  32. data/spec/adapters/ado_spec.rb +32 -3
  33. data/spec/adapters/mysql_spec.rb +7 -8
  34. data/spec/integration/prepared_statement_test.rb +106 -0
  35. data/spec/sequel_core/connection_pool_spec.rb +105 -3
  36. data/spec/sequel_core/database_spec.rb +41 -3
  37. data/spec/sequel_core/dataset_spec.rb +117 -7
  38. data/spec/sequel_core/spec_helper.rb +2 -2
  39. data/spec/sequel_model/model_spec.rb +0 -6
  40. data/spec/sequel_model/spec_helper.rb +1 -1
  41. metadata +11 -6
  42. data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -54
  43. data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
@@ -0,0 +1,106 @@
1
+ module Sequel
2
+ module MSSQL
3
+ module DatabaseMethods
4
+ AUTO_INCREMENT = 'IDENTITY(1,1)'.freeze
5
+
6
+ def auto_increment_sql
7
+ AUTO_INCREMENT
8
+ end
9
+
10
+ def dataset(opts = nil)
11
+ ds = super
12
+ ds.extend(DatasetMethods)
13
+ ds
14
+ end
15
+ end
16
+
17
+ module DatasetMethods
18
+ def complex_expression_sql(op, args)
19
+ case op
20
+ when :'||'
21
+ super(:+, args)
22
+ else
23
+ super(op, args)
24
+ end
25
+ end
26
+
27
+ def full_text_search(cols, terms, opts = {})
28
+ filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
29
+ end
30
+
31
+ # Allows you to do .nolock on a query
32
+ def nolock
33
+ clone(:with => "(NOLOCK)")
34
+ end
35
+
36
+ # Formats a SELECT statement using the given options and the dataset
37
+ # options.
38
+ def select_sql(opts = nil)
39
+ opts = opts ? @opts.merge(opts) : @opts
40
+
41
+ if sql = opts[:sql]
42
+ return sql
43
+ end
44
+
45
+ # ADD TOP to SELECT string for LIMITS
46
+ if limit = opts[:limit]
47
+ top = "TOP #{limit} "
48
+ raise Error, "Offset not supported" if opts[:offset]
49
+ end
50
+
51
+ columns = opts[:select]
52
+ # We had to reference const WILDCARD with its full path, because
53
+ # the Ruby constant scope rules played against us (it was resolving it
54
+ # as Sequel::Dataset::DatasetMethods::WILDCARD).
55
+ select_columns = columns ? column_list(columns) : Sequel::Dataset::WILDCARD
56
+
57
+ if distinct = opts[:distinct]
58
+ distinct_clause = distinct.empty? ? "DISTINCT" : "DISTINCT ON (#{expression_list(distinct)})"
59
+ sql = "SELECT #{top}#{distinct_clause} #{select_columns}"
60
+ else
61
+ sql = "SELECT #{top}#{select_columns}"
62
+ end
63
+
64
+ if opts[:from]
65
+ sql << " FROM #{source_list(opts[:from])}"
66
+ end
67
+
68
+ # ADD WITH to SELECT string for NOLOCK
69
+ if with = opts[:with]
70
+ sql << " WITH #{with}"
71
+ end
72
+
73
+ if join = opts[:join]
74
+ join.each{|j| sql << literal(j)}
75
+ end
76
+
77
+ if where = opts[:where]
78
+ sql << " WHERE #{literal(where)}"
79
+ end
80
+
81
+ if group = opts[:group]
82
+ sql << " GROUP BY #{expression_list(group)}"
83
+ end
84
+
85
+ if order = opts[:order]
86
+ sql << " ORDER BY #{expression_list(order)}"
87
+ end
88
+
89
+ if having = opts[:having]
90
+ sql << " HAVING #{literal(having)}"
91
+ end
92
+
93
+ if union = opts[:union]
94
+ sql << (opts[:union_all] ? \
95
+ " UNION ALL #{union.sql}" : " UNION #{union.sql}")
96
+ end
97
+
98
+ raise Error, "Intersect not supported" if opts[:intersect]
99
+ raise Error, "Except not supported" if opts[:except]
100
+
101
+ sql
102
+ end
103
+ alias_method :sql, :select_sql
104
+ end
105
+ end
106
+ end
@@ -1,5 +1,7 @@
1
1
  module Sequel
2
2
  module MySQL
3
+ # Methods shared by Database instances that connect to MySQL,
4
+ # currently supported by the native and JDBC adapters.
3
5
  module DatabaseMethods
4
6
  AUTO_INCREMENT = 'AUTO_INCREMENT'.freeze
5
7
  NOT_NULL = Sequel::Schema::SQL::NOT_NULL
@@ -12,6 +14,8 @@ module Sequel
12
14
  UNIQUE = Sequel::Schema::SQL::UNIQUE
13
15
  UNSIGNED = Sequel::Schema::SQL::UNSIGNED
14
16
 
17
+ # Use MySQL specific syntax for rename column, set column type, and
18
+ # drop index cases.
15
19
  def alter_table_sql(table, op)
16
20
  type = type_literal(op[:type])
17
21
  type << '(255)' if type == 'varchar'
@@ -27,10 +31,12 @@ module Sequel
27
31
  end
28
32
  end
29
33
 
34
+ # Use MySQL specific AUTO_INCREMENT text.
30
35
  def auto_increment_sql
31
36
  AUTO_INCREMENT
32
37
  end
33
38
 
39
+ # Handle MySQL specific column syntax (not sure why).
34
40
  def column_definition_sql(column)
35
41
  if column[:type] == :check
36
42
  return constraint_definition_sql(column)
@@ -53,7 +59,8 @@ module Sequel
53
59
  end
54
60
  sql
55
61
  end
56
-
62
+
63
+ # Handle MySQL specific index SQL syntax
57
64
  def index_definition_sql(table_name, index)
58
65
  index_name = index[:name] || default_index_name(table_name, index[:columns])
59
66
  unique = "UNIQUE " if index[:unique]
@@ -69,16 +76,14 @@ module Sequel
69
76
  end
70
77
  end
71
78
 
72
- def serial_primary_key_options
73
- {:primary_key => true, :type => :integer, :auto_increment => true}
74
- end
75
-
79
+ # Get version of MySQL server, used for determined capabilities.
76
80
  def server_version
77
81
  m = /(\d+)\.(\d+)\.(\d+)/.match(get(:version[]))
78
82
  @server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
79
83
  end
80
84
 
81
- # Changes the database in use by issuing a USE statement.
85
+ # Changes the database in use by issuing a USE statement. I would be
86
+ # very careful if I used this.
82
87
  def use(db_name)
83
88
  disconnect
84
89
  @opts[:database] = db_name if self << "USE #{db_name}"
@@ -88,12 +93,17 @@ module Sequel
88
93
 
89
94
  private
90
95
 
96
+ # Always quote identifiers for the schema parser dataset.
91
97
  def schema_ds_dataset
92
98
  ds = schema_utility_dataset.clone
93
99
  ds.quote_identifiers = true
94
100
  ds
95
101
  end
96
102
 
103
+ # Allow other database schema's to be queried using the :database
104
+ # option. Allow all database's schema to be used by setting
105
+ # the :database option to nil. If the database option is not specified,
106
+ # uses the currently connected database.
97
107
  def schema_ds_filter(table_name, opts)
98
108
  filt = super
99
109
  # Restrict it to the given or current database, unless specifically requesting :database = nil
@@ -101,16 +111,20 @@ module Sequel
101
111
  filt
102
112
  end
103
113
 
114
+ # MySQL doesn't support table catalogs, so just join on schema and table name.
104
115
  def schema_ds_join(table_name, opts)
105
116
  [:information_schema__columns, {:table_schema => :table_schema, :table_name => :table_name}, :c]
106
117
  end
107
118
  end
108
119
 
120
+ # Dataset methods shared by datasets that use MySQL databases.
109
121
  module DatasetMethods
110
122
  BOOL_TRUE = '1'.freeze
111
123
  BOOL_FALSE = '0'.freeze
112
124
  COMMA_SEPARATOR = ', '.freeze
113
125
 
126
+ # MySQL specific syntax for LIKE/REGEXP searches, as well as
127
+ # string concatenation.
114
128
  def complex_expression_sql(op, args)
115
129
  case op
116
130
  when :~, :'!~', :'~*', :'!~*', :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
@@ -141,6 +155,7 @@ module Sequel
141
155
  sql
142
156
  end
143
157
 
158
+ # MySQL specific full text search syntax.
144
159
  def full_text_search(cols, terms, opts = {})
145
160
  mode = opts[:boolean] ? " IN BOOLEAN MODE" : ""
146
161
  s = if Array === terms
@@ -160,34 +175,22 @@ module Sequel
160
175
  @opts[:having] = {}
161
176
  x = filter(*cond, &block)
162
177
  end
163
-
178
+
179
+ # MySQL doesn't use the SQL standard DEFAULT VALUES.
164
180
  def insert_default_values_sql
165
181
  "INSERT INTO #{source_list(@opts[:from])} () VALUES ()"
166
182
  end
167
183
 
168
- # Returns a join clause based on the specified join type
169
- # and condition. MySQL's NATURAL join is 'semantically
170
- # equivalent to a JOIN with a USING clause that names all
171
- # columns that exist in both tables. The constraint
172
- # expression may be nil, so join expression can accept two
173
- # arguments.
174
- #
175
- # === Note
176
- # Full outer joins (:full_outer) are not implemented in
177
- # MySQL (as of v6.0), nor is there currently a work around
178
- # implementation in Sequel. Straight joins with 'ON
179
- # <condition>' are not yet implemented.
180
- #
181
- # === Example
182
- # @ds = MYSQL_DB[:nodes]
183
- # @ds.join_table(:natural_left_outer, :nodes)
184
- # # join SQL is 'NATURAL LEFT OUTER JOIN nodes'
184
+ # Transforms an CROSS JOIN to an INNER JOIN if the expr is not nil.
185
+ # Raises an error on use of :full_outer type, since MySQL doesn't support it.
185
186
  def join_table(type, table, expr=nil, table_alias=nil)
186
187
  type = :inner if (type == :cross) && !expr.nil?
187
- raise(Sequel::Error::InvalidJoinType, "MySQL doesn't support FULL OUTER JOIN") if type == :full_outer
188
+ raise(Sequel::Error, "MySQL doesn't support FULL OUTER JOIN") if type == :full_outer
188
189
  super(type, table, expr, table_alias)
189
190
  end
190
191
 
192
+ # Transforms :natural_inner to NATURAL LEFT JOIN and straight to
193
+ # STRAIGHT_JOIN.
191
194
  def join_type_sql(join_type)
192
195
  case join_type
193
196
  when :straight then 'STRAIGHT_JOIN'
@@ -195,7 +198,8 @@ module Sequel
195
198
  else super
196
199
  end
197
200
  end
198
-
201
+
202
+ # Override the default boolean values.
199
203
  def literal(v)
200
204
  case v
201
205
  when true
@@ -207,16 +211,20 @@ module Sequel
207
211
  end
208
212
  end
209
213
 
214
+ # MySQL specific syntax for inserting multiple values at once.
210
215
  def multi_insert_sql(columns, values)
211
216
  columns = column_list(columns)
212
217
  values = values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)
213
218
  ["INSERT INTO #{source_list(@opts[:from])} (#{columns}) VALUES #{values}"]
214
219
  end
215
220
 
221
+ # MySQL uses the nonstandard ` (backtick) for quoting identifiers.
216
222
  def quoted_identifier(c)
217
223
  "`#{c}`"
218
224
  end
219
225
 
226
+ # MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
227
+ # insert if it doesn't).
220
228
  def replace_sql(*values)
221
229
  from = source_list(@opts[:from])
222
230
  if values.empty?
@@ -1,7 +1,10 @@
1
1
  module Sequel
2
2
  module Postgres
3
+ # Array of exceptions that need to be converted. JDBC
4
+ # uses NativeExceptions, the native adapter uses PGError.
3
5
  CONVERTED_EXCEPTIONS = []
4
-
6
+
7
+ # Methods shared by adapter/connection instances.
5
8
  module AdapterMethods
6
9
  SELECT_CURRVAL = "SELECT currval('%s')".freeze
7
10
  SELECT_PK = <<-end_sql
@@ -45,8 +48,11 @@ module Sequel
45
48
  AND dep.refobjid = '%s'::regclass
46
49
  end_sql
47
50
 
51
+ # Depth of the current transaction on this connection, used
52
+ # to implement multi-level transactions with savepoints.
48
53
  attr_accessor :transaction_depth
49
54
 
55
+ # Get the last inserted value for the given table.
50
56
  def last_insert_id(table)
51
57
  @table_sequences ||= {}
52
58
  if !@table_sequences.include?(table)
@@ -63,6 +69,7 @@ module Sequel
63
69
  end
64
70
  end
65
71
 
72
+ # Get the primary key and sequence for the given table.
66
73
  def pkey_and_sequence(table)
67
74
  execute(SELECT_PK_AND_SERIAL_SEQUENCE % table) do |r|
68
75
  vals = result_set_values(r, 2, 2)
@@ -74,14 +81,17 @@ module Sequel
74
81
  end
75
82
  end
76
83
 
84
+ # Get the primary key for the given table.
77
85
  def primary_key(table)
78
86
  execute(SELECT_PK % table) do |r|
79
87
  result_set_values(r, 0)
80
88
  end
81
89
  end
82
90
  end
83
-
91
+
92
+ # Methods shared by Database instances that connect to PostgreSQL.
84
93
  module DatabaseMethods
94
+ PREPARED_ARG_PLACEHOLDER = '$'.lit.freeze
85
95
  RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session/.freeze
86
96
  RELATION_QUERY = {:from => [:pg_class], :select => [:relname]}.freeze
87
97
  RELATION_FILTER = "(relkind = 'r') AND (relname !~ '^pg|sql')".freeze
@@ -93,23 +103,12 @@ module Sequel
93
103
  SQL_RELEASE_SAVEPOINT = 'RELEASE SAVEPOINT autopoint_%d'.freeze
94
104
  SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
95
105
 
106
+ # Always CASCADE the table drop
96
107
  def drop_table_sql(name)
97
108
  "DROP TABLE #{name} CASCADE"
98
109
  end
99
110
 
100
- def execute_insert(sql, table, values)
101
- begin
102
- log_info(sql)
103
- @pool.hold do |conn|
104
- conn.execute(sql)
105
- insert_result(conn, table, values)
106
- end
107
- rescue => e
108
- log_info(e.message)
109
- raise convert_pgerror(e)
110
- end
111
- end
112
-
111
+ # PostgreSQL specific index SQL.
113
112
  def index_definition_sql(table_name, index)
114
113
  index_name = index[:name] || default_index_name(table_name, index[:columns])
115
114
  expr = literal(Array(index[:columns]))
@@ -129,6 +128,11 @@ module Sequel
129
128
  "CREATE #{unique}INDEX #{index_name} ON #{table_name} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
130
129
  end
131
130
 
131
+ # The result of the insert for the given table and values. Uses
132
+ # last insert id the primary key for the table if it exists,
133
+ # otherwise determines the primary key for the table and uses the
134
+ # value of the hash key. If values is an array, assume the first
135
+ # value is the primary key value and return that.
132
136
  def insert_result(conn, table, values)
133
137
  begin
134
138
  result = conn.last_insert_id(table)
@@ -147,24 +151,31 @@ module Sequel
147
151
  end
148
152
  end
149
153
 
154
+ # Dataset containing all current database locks
150
155
  def locks
151
- dataset.from("pg_class, pg_locks").
152
- select("pg_class.relname, pg_locks.*").
153
- filter("pg_class.relfilenode=pg_locks.relation")
156
+ dataset.from(:pg_class, :pg_locks).
157
+ select(:pg_class__relname, :pg_locks.*).
158
+ filter(:pg_class__relfilenode=>:pg_locks__relation)
154
159
  end
155
-
160
+
161
+ # Returns primary key for the given table. This information is
162
+ # cached, and if the primary key for a table is changed, the
163
+ # @primary_keys instance variable should be reset manually.
156
164
  def primary_key_for_table(conn, table)
157
165
  @primary_keys ||= {}
158
166
  @primary_keys[table] ||= conn.primary_key(table)
159
167
  end
160
168
 
169
+ # PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
170
+ # managing incrementing primary keys.
161
171
  def serial_primary_key_options
162
172
  {:primary_key => true, :type => :serial}
163
173
  end
164
174
 
165
- def server_version
175
+ # The version of the PostgreSQL server, used for determining capability.
176
+ def server_version(server=nil)
166
177
  return @server_version if @server_version
167
- @server_version = pool.hold do |conn|
178
+ @server_version = synchronize(server) do |conn|
168
179
  (conn.server_version rescue nil) if conn.respond_to?(:server_version)
169
180
  end
170
181
  unless @server_version
@@ -174,12 +185,14 @@ module Sequel
174
185
  @server_version
175
186
  end
176
187
 
188
+ # Array of symbols specifying table names in the current database.
177
189
  def tables
178
- dataset(RELATION_QUERY).filter(RELATION_FILTER).map {|r| r[:relname].to_sym}
190
+ dataset(RELATION_QUERY).filter(RELATION_FILTER).map{|r| r[:relname].to_sym}
179
191
  end
180
192
 
181
- def transaction
182
- @pool.hold do |conn|
193
+ # PostgreSQL supports multi-level transactions using save points.
194
+ def transaction(server=nil)
195
+ synchronize(server) do |conn|
183
196
  conn.transaction_depth = 0 if conn.transaction_depth.nil?
184
197
  if conn.transaction_depth > 0
185
198
  log_info(SQL_SAVEPOINT % conn.transaction_depth)
@@ -222,10 +235,20 @@ module Sequel
222
235
 
223
236
  private
224
237
 
238
+ # Convert the exception to a Sequel::Error if it is in CONVERTED_EXCEPTIONS.
225
239
  def convert_pgerror(e)
226
240
  e.is_one_of?(*CONVERTED_EXCEPTIONS) ? Error.new(e.message) : e
227
241
  end
228
-
242
+
243
+ # Use a dollar sign instead of question mark for the argument
244
+ # placeholder.
245
+ def prepared_arg_placeholder
246
+ PREPARED_ARG_PLACEHOLDER
247
+ end
248
+
249
+ # When the :schema option is used, use the the given schema.
250
+ # When the :schema option is nil, return results for all schemas.
251
+ # If the :schema option is not used, use the public schema.
229
252
  def schema_ds_filter(table_name, opts)
230
253
  filt = super
231
254
  # Restrict it to the given or public schema, unless specifically requesting :schema = nil
@@ -233,7 +256,8 @@ module Sequel
233
256
  filt
234
257
  end
235
258
  end
236
-
259
+
260
+ # Instance methods for datasets that connect to a PostgreSQL database.
237
261
  module DatasetMethods
238
262
  ACCESS_SHARE = 'ACCESS SHARE'.freeze
239
263
  ACCESS_EXCLUSIVE = 'ACCESS EXCLUSIVE'.freeze
@@ -253,7 +277,8 @@ module Sequel
253
277
  SHARE = 'SHARE'.freeze
254
278
  SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
255
279
  SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
256
-
280
+
281
+ # Return the results of an ANALYZE query as a string
257
282
  def analyze(opts = nil)
258
283
  analysis = []
259
284
  fetch_rows(EXPLAIN_ANALYZE + select_sql(opts)) do |r|
@@ -262,6 +287,7 @@ module Sequel
262
287
  analysis.join("\r\n")
263
288
  end
264
289
 
290
+ # Return the results of an EXPLAIN query as a string
265
291
  def explain(opts = nil)
266
292
  analysis = []
267
293
  fetch_rows(EXPLAIN + select_sql(opts)) do |r|
@@ -270,14 +296,18 @@ module Sequel
270
296
  analysis.join("\r\n")
271
297
  end
272
298
 
299
+ # Return a cloned dataset with a :share lock type.
273
300
  def for_share
274
301
  clone(:lock => :share)
275
302
  end
276
-
303
+
304
+ # Return a cloned dataset with a :update lock type.
277
305
  def for_update
278
306
  clone(:lock => :update)
279
307
  end
280
-
308
+
309
+ # PostgreSQL specific full text search syntax, using tsearch2 (included
310
+ # in 8.3 by default, and available for earlier versions as an add-on).
281
311
  def full_text_search(cols, terms, opts = {})
282
312
  lang = opts[:language] ? "#{literal(opts[:language])}, " : ""
283
313
  cols = cols.is_a?(Array) ? cols.map {|c| literal(c)}.join(" || ") : literal(cols)
@@ -285,11 +315,14 @@ module Sequel
285
315
  filter("to_tsvector(#{lang}#{cols}) @@ to_tsquery(#{lang}#{terms})")
286
316
  end
287
317
 
318
+ # Insert given values into the database.
288
319
  def insert(*values)
289
- @db.execute_insert(insert_sql(*values), source_list(@opts[:from]),
290
- values.size == 1 ? values.first : values)
320
+ execute_insert(insert_sql(*values), :table=>source_list(@opts[:from]),
321
+ :values=>values.size == 1 ? values.first : values)
291
322
  end
292
-
323
+
324
+ # Handle microseconds for Time and DateTime values, as well as PostgreSQL
325
+ # specific boolean values and string escaping.
293
326
  def literal(v)
294
327
  case v
295
328
  when LiteralString
@@ -310,18 +343,19 @@ module Sequel
310
343
  end
311
344
 
312
345
  # Locks the table with the specified mode.
313
- def lock(mode, &block)
346
+ def lock(mode, server=nil)
314
347
  sql = LOCK % [source_list(@opts[:from]), mode]
315
- @db.synchronize do
316
- if block # perform locking inside a transaction and yield to block
317
- @db.transaction {@db.execute(sql); yield}
348
+ @db.synchronize(server) do
349
+ if block_given? # perform locking inside a transaction and yield to block
350
+ @db.transaction(server){@db.execute(sql, :server=>server); yield}
318
351
  else
319
- @db.execute(sql) # lock without a transaction
352
+ @db.execute(sql, :server=>server) # lock without a transaction
320
353
  self
321
354
  end
322
355
  end
323
356
  end
324
357
 
358
+ # For PostgreSQL version > 8.2, allow inserting multiple rows at once.
325
359
  def multi_insert_sql(columns, values)
326
360
  return super if @db.server_version < 80200
327
361
 
@@ -331,10 +365,13 @@ module Sequel
331
365
  ["INSERT INTO #{source_list(@opts[:from])} (#{columns}) VALUES #{values}"]
332
366
  end
333
367
 
368
+ # PostgreSQL assumes unquoted identifiers are lower case by default,
369
+ # so do not upcase the identifier when quoting it.
334
370
  def quoted_identifier(c)
335
371
  "\"#{c}\""
336
372
  end
337
-
373
+
374
+ # Support lock mode, allowing FOR SHARE and FOR UPDATE queries.
338
375
  def select_sql(opts = nil)
339
376
  row_lock_mode = opts ? opts[:lock] : @opts[:lock]
340
377
  sql = super
@@ -346,6 +383,13 @@ module Sequel
346
383
  end
347
384
  sql
348
385
  end
386
+
387
+ private
388
+
389
+ # Call execute_insert on the database object with the given values.
390
+ def execute_insert(sql, opts={})
391
+ @db.execute_insert(sql, {:server=>@opts[:server] || :default}.merge(opts))
392
+ end
349
393
  end
350
394
  end
351
395
  end