sequel 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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