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.
- data/CHANGELOG +16 -0
- data/README +4 -1
- data/Rakefile +17 -19
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/sharding.rdoc +113 -0
- data/lib/sequel_core/adapters/ado.rb +24 -17
- data/lib/sequel_core/adapters/db2.rb +30 -33
- data/lib/sequel_core/adapters/dbi.rb +15 -13
- data/lib/sequel_core/adapters/informix.rb +13 -14
- data/lib/sequel_core/adapters/jdbc.rb +243 -60
- data/lib/sequel_core/adapters/jdbc/mysql.rb +32 -24
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +32 -2
- data/lib/sequel_core/adapters/jdbc/sqlite.rb +16 -20
- data/lib/sequel_core/adapters/mysql.rb +164 -76
- data/lib/sequel_core/adapters/odbc.rb +21 -34
- data/lib/sequel_core/adapters/openbase.rb +10 -7
- data/lib/sequel_core/adapters/oracle.rb +17 -23
- data/lib/sequel_core/adapters/postgres.rb +246 -35
- data/lib/sequel_core/adapters/shared/mssql.rb +106 -0
- data/lib/sequel_core/adapters/shared/mysql.rb +34 -26
- data/lib/sequel_core/adapters/shared/postgres.rb +82 -38
- data/lib/sequel_core/adapters/shared/sqlite.rb +48 -16
- data/lib/sequel_core/adapters/sqlite.rb +141 -44
- data/lib/sequel_core/connection_pool.rb +85 -63
- data/lib/sequel_core/database.rb +46 -17
- data/lib/sequel_core/dataset.rb +21 -40
- data/lib/sequel_core/dataset/convenience.rb +3 -3
- data/lib/sequel_core/dataset/prepared_statements.rb +218 -0
- data/lib/sequel_core/exceptions.rb +0 -12
- data/lib/sequel_model/base.rb +1 -2
- data/lib/sequel_model/plugins.rb +1 -1
- data/spec/adapters/ado_spec.rb +32 -3
- data/spec/adapters/mysql_spec.rb +7 -8
- data/spec/integration/prepared_statement_test.rb +106 -0
- data/spec/sequel_core/connection_pool_spec.rb +105 -3
- data/spec/sequel_core/database_spec.rb +41 -3
- data/spec/sequel_core/dataset_spec.rb +117 -7
- data/spec/sequel_core/spec_helper.rb +2 -2
- data/spec/sequel_model/model_spec.rb +0 -6
- data/spec/sequel_model/spec_helper.rb +1 -1
- metadata +11 -6
- data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -54
- data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
@@ -4,21 +4,21 @@ module Sequel
|
|
4
4
|
module ODBC
|
5
5
|
class Database < Sequel::Database
|
6
6
|
set_adapter_scheme :odbc
|
7
|
-
|
8
|
-
# def connect
|
9
|
-
# conn = ::ODBC::connect(@opts[:database], @opts[:user], @opts[:password])
|
10
|
-
# conn.autocommit = true
|
11
|
-
# conn
|
12
|
-
# end
|
13
7
|
|
14
8
|
GUARDED_DRV_NAME = /^\{.+\}$/.freeze
|
15
9
|
DRV_NAME_GUARDS = '{%s}'.freeze
|
16
10
|
|
17
|
-
def connect
|
18
|
-
|
11
|
+
def connect(server)
|
12
|
+
opts = server_opts(server)
|
13
|
+
case opts[:db_type]
|
14
|
+
when 'mssql'
|
15
|
+
require 'sequel_core/adapters/shared/mssql'
|
16
|
+
extend Sequel::MSSQL::DatabaseMethods
|
17
|
+
end
|
18
|
+
if opts.include? :driver
|
19
19
|
drv = ::ODBC::Driver.new
|
20
20
|
drv.name = 'Sequel ODBC Driver130'
|
21
|
-
|
21
|
+
opts.each do |param, value|
|
22
22
|
if :driver == param and not (value =~ GUARDED_DRV_NAME)
|
23
23
|
value = DRV_NAME_GUARDS % value
|
24
24
|
end
|
@@ -27,7 +27,7 @@ module Sequel
|
|
27
27
|
db = ::ODBC::Database.new
|
28
28
|
conn = db.drvconnect(drv)
|
29
29
|
else
|
30
|
-
conn = ::ODBC::connect(
|
30
|
+
conn = ::ODBC::connect(opts[:database], opts[:user], opts[:password])
|
31
31
|
end
|
32
32
|
conn.autocommit = true
|
33
33
|
conn
|
@@ -45,20 +45,20 @@ module Sequel
|
|
45
45
|
# you call execute manually, or you will get warnings. See the
|
46
46
|
# fetch_rows method source code for an example of how to drop
|
47
47
|
# the statements.
|
48
|
-
def execute(sql)
|
48
|
+
def execute(sql, opts={})
|
49
49
|
log_info(sql)
|
50
|
-
|
51
|
-
conn.run(sql)
|
50
|
+
synchronize(opts[:server]) do |conn|
|
51
|
+
r = conn.run(sql)
|
52
|
+
yield(r) if block_given?
|
53
|
+
r
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
55
|
-
def
|
57
|
+
def execute_dui(sql, opts={})
|
56
58
|
log_info(sql)
|
57
|
-
|
58
|
-
conn.do(sql)
|
59
|
-
end
|
59
|
+
synchronize(opts[:server]){|conn| conn.do(sql)}
|
60
60
|
end
|
61
|
-
alias_method :
|
61
|
+
alias_method :do, :execute_dui
|
62
62
|
end
|
63
63
|
|
64
64
|
class Dataset < Sequel::Dataset
|
@@ -90,8 +90,7 @@ module Sequel
|
|
90
90
|
UNTITLED_COLUMN = 'untitled_%d'.freeze
|
91
91
|
|
92
92
|
def fetch_rows(sql, &block)
|
93
|
-
|
94
|
-
s = @db.execute sql
|
93
|
+
execute(sql) do |s|
|
95
94
|
begin
|
96
95
|
untitled_count = 0
|
97
96
|
@columns = s.columns(true).map do |c|
|
@@ -108,20 +107,8 @@ module Sequel
|
|
108
107
|
end
|
109
108
|
self
|
110
109
|
end
|
111
|
-
|
112
|
-
|
113
|
-
# @db.synchronize do
|
114
|
-
# s = @db.execute sql
|
115
|
-
# begin
|
116
|
-
# @columns = s.columns(true).map {|c| c.name.to_sym}
|
117
|
-
# rows = s.fetch_all
|
118
|
-
# rows.each {|row| yield hash_row(row)}
|
119
|
-
# ensure
|
120
|
-
# s.drop unless s.nil? rescue nil
|
121
|
-
# end
|
122
|
-
# end
|
123
|
-
# self
|
124
|
-
# end
|
110
|
+
|
111
|
+
private
|
125
112
|
|
126
113
|
def hash_row(row)
|
127
114
|
hash = {}
|
@@ -5,7 +5,8 @@ module Sequel
|
|
5
5
|
class Database < Sequel::Database
|
6
6
|
set_adapter_scheme :openbase
|
7
7
|
|
8
|
-
def connect
|
8
|
+
def connect(server)
|
9
|
+
opts = server_opts(server)
|
9
10
|
OpenBase.new(
|
10
11
|
opts[:database],
|
11
12
|
opts[:host] || 'localhost',
|
@@ -23,11 +24,14 @@ module Sequel
|
|
23
24
|
OpenBase::Dataset.new(self, opts)
|
24
25
|
end
|
25
26
|
|
26
|
-
def execute(sql)
|
27
|
+
def execute(sql, opts={})
|
27
28
|
log_info(sql)
|
28
|
-
|
29
|
+
synchronize(opts[:server]) do |conn|
|
30
|
+
r = conn.execute(sql)
|
31
|
+
yield(r) if block_given?
|
32
|
+
r
|
33
|
+
end
|
29
34
|
end
|
30
|
-
|
31
35
|
alias_method :do, :execute
|
32
36
|
end
|
33
37
|
|
@@ -43,9 +47,8 @@ module Sequel
|
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
46
|
-
def fetch_rows(sql
|
47
|
-
|
48
|
-
result = @db.execute sql
|
50
|
+
def fetch_rows(sql)
|
51
|
+
execute(sql) do |result|
|
49
52
|
begin
|
50
53
|
@columns = result.column_infos.map {|c| c.name.to_sym}
|
51
54
|
result.each do |r|
|
@@ -5,20 +5,15 @@ module Sequel
|
|
5
5
|
class Database < Sequel::Database
|
6
6
|
set_adapter_scheme :oracle
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def connect
|
15
|
-
if @opts[:database]
|
16
|
-
dbname = @opts[:host] ? \
|
17
|
-
"//#{@opts[:host]}#{":#{@opts[:port]}" if @opts[:port]}/#{@opts[:database]}" : @opts[:database]
|
8
|
+
def connect(server)
|
9
|
+
opts = server_opts(server)
|
10
|
+
if opts[:database]
|
11
|
+
dbname = opts[:host] ? \
|
12
|
+
"//#{opts[:host]}#{":#{opts[:port]}" if opts[:port]}/#{opts[:database]}" : opts[:database]
|
18
13
|
else
|
19
|
-
dbname =
|
14
|
+
dbname = opts[:host]
|
20
15
|
end
|
21
|
-
conn = OCI8.new(
|
16
|
+
conn = OCI8.new(opts[:user], opts[:password], dbname, opts[:privilege])
|
22
17
|
conn.autocommit = true
|
23
18
|
conn.non_blocking = true
|
24
19
|
conn
|
@@ -32,11 +27,14 @@ module Sequel
|
|
32
27
|
Oracle::Dataset.new(self, opts)
|
33
28
|
end
|
34
29
|
|
35
|
-
def execute(sql)
|
30
|
+
def execute(sql, opts={})
|
36
31
|
log_info(sql)
|
37
|
-
|
32
|
+
synchronize(opts[:server]) do |conn|
|
33
|
+
r = conn.exec(sql)
|
34
|
+
yield(r) if block_given?
|
35
|
+
r
|
36
|
+
end
|
38
37
|
end
|
39
|
-
|
40
38
|
alias_method :do, :execute
|
41
39
|
|
42
40
|
def tables
|
@@ -49,12 +47,9 @@ module Sequel
|
|
49
47
|
from(:tab).filter(:tname => name.to_s.upcase, :tabtype => 'TABLE').count > 0
|
50
48
|
end
|
51
49
|
|
52
|
-
def transaction
|
53
|
-
|
54
|
-
@transactions
|
55
|
-
if @transactions.include? Thread.current
|
56
|
-
return yield(conn)
|
57
|
-
end
|
50
|
+
def transaction(server=nil)
|
51
|
+
synchronize(server) do |conn|
|
52
|
+
return yield(conn) if @transactions.include?(Thread.current)
|
58
53
|
|
59
54
|
conn.autocommit = false
|
60
55
|
begin
|
@@ -83,8 +78,7 @@ module Sequel
|
|
83
78
|
end
|
84
79
|
|
85
80
|
def fetch_rows(sql, &block)
|
86
|
-
|
87
|
-
cursor = @db.execute sql
|
81
|
+
execute(sql) do |cursor|
|
88
82
|
begin
|
89
83
|
@columns = cursor.get_col_names.map {|c| c.downcase.to_sym}
|
90
84
|
while r = cursor.fetch
|
@@ -2,16 +2,23 @@ require 'sequel_core/adapters/shared/postgres'
|
|
2
2
|
|
3
3
|
begin
|
4
4
|
require 'pg'
|
5
|
+
SEQUEL_POSTGRES_USES_PG = true
|
5
6
|
rescue LoadError => e
|
6
|
-
|
7
|
-
|
7
|
+
SEQUEL_POSTGRES_USES_PG = false
|
8
|
+
begin
|
9
|
+
require 'postgres'
|
10
|
+
# Attempt to get uniform behavior for the PGconn object no matter
|
11
|
+
# if pg, postgres, or postgres-pr is used.
|
8
12
|
class PGconn
|
9
13
|
unless method_defined?(:escape_string)
|
10
14
|
if self.respond_to?(:escape)
|
15
|
+
# If there is no escape_string instead method, but there is an
|
16
|
+
# escape class method, use that instead.
|
11
17
|
def escape_string(str)
|
12
18
|
self.class.escape(str)
|
13
19
|
end
|
14
20
|
else
|
21
|
+
# Raise an error if no valid string escaping method can be found.
|
15
22
|
def escape_string(obj)
|
16
23
|
raise Sequel::Error, "string escaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
|
17
24
|
end
|
@@ -19,6 +26,8 @@ rescue LoadError => e
|
|
19
26
|
end
|
20
27
|
unless method_defined?(:escape_bytea)
|
21
28
|
if self.respond_to?(:escape_bytea)
|
29
|
+
# If there is no escape_bytea instance method, but there is an
|
30
|
+
# escape_bytea class method, use that instead.
|
22
31
|
def escape_bytea(obj)
|
23
32
|
self.class.escape_bytea(obj)
|
24
33
|
end
|
@@ -27,14 +36,20 @@ rescue LoadError => e
|
|
27
36
|
require 'postgres-pr/typeconv/conv'
|
28
37
|
require 'postgres-pr/typeconv/bytea'
|
29
38
|
extend Postgres::Conversion
|
39
|
+
# If we are using postgres-pr, use the encode_bytea method from
|
40
|
+
# that.
|
30
41
|
def escape_bytea(obj)
|
31
42
|
self.class.encode_bytea(obj)
|
32
43
|
end
|
33
44
|
metaalias :unescape_bytea, :decode_bytea
|
34
45
|
rescue
|
46
|
+
# If no valid bytea escaping method can be found, create one that
|
47
|
+
# raises an error
|
35
48
|
def escape_bytea(obj)
|
36
49
|
raise Sequel::Error, "bytea escaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
|
37
50
|
end
|
51
|
+
# If no valid bytea unescaping method can be found, create one that
|
52
|
+
# raises an error
|
38
53
|
def self.unescape_bytea(obj)
|
39
54
|
raise Sequel::Error, "bytea unescaping not supported with this postgres driver. Try using ruby-pg, ruby-postgres, or postgres-pr."
|
40
55
|
end
|
@@ -56,8 +71,12 @@ rescue LoadError => e
|
|
56
71
|
end
|
57
72
|
|
58
73
|
module Sequel
|
74
|
+
# Top level module for holding all PostgreSQL-related modules and classes
|
75
|
+
# for Sequel.
|
59
76
|
module Postgres
|
60
77
|
CONVERTED_EXCEPTIONS << PGError
|
78
|
+
|
79
|
+
# Hash with integer keys and proc values for converting PostgreSQL types.
|
61
80
|
PG_TYPES = {
|
62
81
|
16 => lambda{ |s| Postgres.string_to_bool(s) }, # boolean
|
63
82
|
17 => lambda{ |s| Adapter.unescape_bytea(s).to_blob }, # bytea
|
@@ -78,6 +97,7 @@ module Sequel
|
|
78
97
|
1700 => lambda{ |s| s.to_d }, # numeric
|
79
98
|
}
|
80
99
|
|
100
|
+
# Module method for converting a PostgreSQL string to a boolean value.
|
81
101
|
def self.string_to_bool(s)
|
82
102
|
if(s.blank?)
|
83
103
|
nil
|
@@ -87,34 +107,43 @@ module Sequel
|
|
87
107
|
false
|
88
108
|
end
|
89
109
|
end
|
90
|
-
|
110
|
+
|
111
|
+
# PGconn subclass for connection specific methods used with the
|
112
|
+
# pg, postgres, or postgres-pr driver.
|
91
113
|
class Adapter < ::PGconn
|
92
114
|
include Sequel::Postgres::AdapterMethods
|
93
115
|
self.translate_results = false if respond_to?(:translate_results=)
|
94
116
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
def execute(sql, &block)
|
117
|
+
# Execute the given SQL with this connection. If a block is given,
|
118
|
+
# yield the results, otherwise, return the number of changed rows.
|
119
|
+
def execute(sql, args=nil)
|
100
120
|
q = nil
|
101
121
|
begin
|
102
|
-
q = exec(sql)
|
122
|
+
q = args ? exec(sql, args) : exec(sql)
|
103
123
|
rescue PGError => e
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
else
|
108
|
-
raise e
|
109
|
-
end
|
124
|
+
raise if status == Adapter::CONNECTION_OK
|
125
|
+
reset
|
126
|
+
q = args ? exec(sql, args) : exec(sql)
|
110
127
|
end
|
111
128
|
begin
|
112
|
-
|
129
|
+
block_given? ? yield(q) : q.cmd_tuples
|
113
130
|
ensure
|
114
131
|
q.clear
|
115
132
|
end
|
116
133
|
end
|
117
134
|
|
135
|
+
if SEQUEL_POSTGRES_USES_PG
|
136
|
+
# Hash of prepared statements for this connection. Keys are
|
137
|
+
# string names of the server side prepared statement, and values
|
138
|
+
# are SQL strings.
|
139
|
+
def prepared_statements
|
140
|
+
@prepared_statements ||= {}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
# Return the requested values for the given row.
|
118
147
|
def result_set_values(r, *vals)
|
119
148
|
return if r.nil? || (r.ntuples == 0)
|
120
149
|
case vals.length
|
@@ -125,39 +154,65 @@ module Sequel
|
|
125
154
|
end
|
126
155
|
end
|
127
156
|
end
|
128
|
-
|
157
|
+
|
158
|
+
# Database class for PostgreSQL databases used with Sequel and the
|
159
|
+
# pg, postgres, or postgres-pr driver.
|
129
160
|
class Database < Sequel::Database
|
130
161
|
include Sequel::Postgres::DatabaseMethods
|
131
162
|
|
132
163
|
set_adapter_scheme :postgres
|
133
|
-
|
134
|
-
|
164
|
+
|
165
|
+
# Connects to the database. In addition to the standard database
|
166
|
+
# options, using the :encoding or :charset option changes the
|
167
|
+
# client encoding for the connection.
|
168
|
+
def connect(server)
|
169
|
+
opts = server_opts(server)
|
135
170
|
conn = Adapter.connect(
|
136
|
-
|
137
|
-
|
171
|
+
opts[:host] || 'localhost',
|
172
|
+
opts[:port] || 5432,
|
138
173
|
'', '',
|
139
|
-
|
140
|
-
|
141
|
-
|
174
|
+
opts[:database],
|
175
|
+
opts[:user],
|
176
|
+
opts[:password]
|
142
177
|
)
|
143
|
-
if encoding =
|
178
|
+
if encoding = opts[:encoding] || opts[:charset]
|
144
179
|
conn.set_client_encoding(encoding)
|
145
180
|
end
|
146
181
|
conn
|
147
182
|
end
|
148
183
|
|
184
|
+
# Return instance of Sequel::Postgres::Dataset with the given options.
|
149
185
|
def dataset(opts = nil)
|
150
186
|
Postgres::Dataset.new(self, opts)
|
151
187
|
end
|
152
188
|
|
189
|
+
# Disconnect all active connections.
|
153
190
|
def disconnect
|
154
191
|
@pool.disconnect {|c| c.finish}
|
155
192
|
end
|
156
|
-
|
157
|
-
|
193
|
+
|
194
|
+
# Execute the given SQL with the given args on an available connection.
|
195
|
+
def execute(sql, opts={}, &block)
|
196
|
+
return execute_prepared_statement(sql, opts, &block) if Symbol === sql
|
158
197
|
begin
|
159
|
-
log_info(sql)
|
160
|
-
|
198
|
+
log_info(sql, opts[:arguments])
|
199
|
+
synchronize(opts[:server]){|conn| conn.execute(sql, opts[:arguments], &block)}
|
200
|
+
rescue => e
|
201
|
+
log_info(e.message)
|
202
|
+
raise convert_pgerror(e)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Insert the values into the table and return the primary key (if
|
207
|
+
# automatically generated).
|
208
|
+
def execute_insert(sql, opts={})
|
209
|
+
return execute(sql, opts) if Symbol === sql
|
210
|
+
begin
|
211
|
+
log_info(sql, opts[:arguments])
|
212
|
+
synchronize(opts[:server]) do |conn|
|
213
|
+
conn.execute(sql, opts[:arguments])
|
214
|
+
insert_result(conn, opts[:table], opts[:values])
|
215
|
+
end
|
161
216
|
rescue => e
|
162
217
|
log_info(e.message)
|
163
218
|
raise convert_pgerror(e)
|
@@ -166,18 +221,60 @@ module Sequel
|
|
166
221
|
|
167
222
|
private
|
168
223
|
|
169
|
-
# PostgreSQL doesn't need the pool to convert exceptions
|
224
|
+
# PostgreSQL doesn't need the connection pool to convert exceptions.
|
170
225
|
def connection_pool_default_options
|
171
226
|
super.merge(:pool_convert_exceptions=>false)
|
172
227
|
end
|
228
|
+
|
229
|
+
# Execute the prepared statement with the given name on an available
|
230
|
+
# connection, using the given args. If the connection has not prepared
|
231
|
+
# a statement with the given name yet, prepare it. If the connection
|
232
|
+
# has prepared a statement with the same name and different SQL,
|
233
|
+
# deallocate that statement first and then prepare this statement.
|
234
|
+
# If a block is given, yield the result, otherwise, return the number
|
235
|
+
# of rows changed. If the :insert option is passed, return the value
|
236
|
+
# of the primary key for the last inserted row.
|
237
|
+
def execute_prepared_statement(name, opts={})
|
238
|
+
ps = prepared_statements[name]
|
239
|
+
sql = ps.prepared_sql
|
240
|
+
ps_name = name.to_s
|
241
|
+
args = opts[:arguments]
|
242
|
+
synchronize(opts[:server]) do |conn|
|
243
|
+
unless conn.prepared_statements[ps_name] == sql
|
244
|
+
if conn.prepared_statements.include?(ps_name)
|
245
|
+
s = "DEALLOCATE #{ps_name}"
|
246
|
+
log_info(s)
|
247
|
+
conn.execute(s) unless conn.prepared_statements[ps_name] == sql
|
248
|
+
end
|
249
|
+
conn.prepared_statements[ps_name] = sql
|
250
|
+
log_info("PREPARE #{ps_name} AS #{sql}")
|
251
|
+
conn.prepare(ps_name, sql)
|
252
|
+
end
|
253
|
+
log_info("EXECUTE #{ps_name}", args)
|
254
|
+
q = conn.exec_prepared(ps_name, args)
|
255
|
+
if opts[:table] && opts[:values]
|
256
|
+
insert_result(conn, opts[:table], opts[:values])
|
257
|
+
else
|
258
|
+
begin
|
259
|
+
block_given? ? yield(q) : q.cmd_tuples
|
260
|
+
ensure
|
261
|
+
q.clear
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
173
266
|
end
|
174
|
-
|
267
|
+
|
268
|
+
# Dataset class for PostgreSQL datasets that use the pg, postgres, or
|
269
|
+
# postgres-pr driver.
|
175
270
|
class Dataset < Sequel::Dataset
|
176
271
|
include Sequel::Postgres::DatasetMethods
|
177
|
-
|
178
|
-
|
272
|
+
|
273
|
+
# yield all rows returned by executing the given SQL and converting
|
274
|
+
# the types.
|
275
|
+
def fetch_rows(sql)
|
179
276
|
@columns = []
|
180
|
-
|
277
|
+
execute(sql) do |res|
|
181
278
|
(0...res.ntuples).each do |recnum|
|
182
279
|
converted_rec = {}
|
183
280
|
(0...res.nfields).each do |fieldnum|
|
@@ -193,7 +290,121 @@ module Sequel
|
|
193
290
|
end
|
194
291
|
end
|
195
292
|
end
|
293
|
+
|
294
|
+
if SEQUEL_POSTGRES_USES_PG
|
295
|
+
|
296
|
+
PREPARED_ARG_PLACEHOLDER = '$'.lit.freeze
|
297
|
+
|
298
|
+
# PostgreSQL specific argument mapper used for mapping the named
|
299
|
+
# argument hash to a array with numbered arguments. Only used with
|
300
|
+
# the pg driver.
|
301
|
+
module ArgumentMapper
|
302
|
+
include Sequel::Dataset::ArgumentMapper
|
303
|
+
|
304
|
+
protected
|
305
|
+
|
306
|
+
# Return an array of strings for each of the hash values, inserting
|
307
|
+
# them to the correct position in the array.
|
308
|
+
def map_to_prepared_args(hash)
|
309
|
+
array = []
|
310
|
+
@prepared_args.each{|k,v| array[v] = hash[k].to_s}
|
311
|
+
array
|
312
|
+
end
|
313
|
+
|
314
|
+
private
|
315
|
+
|
316
|
+
# PostgreSQL most of the time requires type information for each of
|
317
|
+
# arguments to a prepared statement. Handle this by allowing the
|
318
|
+
# named argument to have a __* suffix, with the * being the type.
|
319
|
+
# In the generated SQL, cast the bound argument to that type to
|
320
|
+
# elminate ambiguity (and PostgreSQL from raising an exception).
|
321
|
+
def prepared_arg(k)
|
322
|
+
y, type = k.to_s.split("__")
|
323
|
+
"#{prepared_arg_placeholder}#{@prepared_args[y.to_sym]}#{"::#{type}" if type}".lit
|
324
|
+
end
|
325
|
+
|
326
|
+
# If the named argument has already been used, return the position in
|
327
|
+
# the output array that it is mapped to. Otherwise, map it to the
|
328
|
+
# next position in the array.
|
329
|
+
def prepared_args_hash
|
330
|
+
max_prepared_arg = 0
|
331
|
+
Hash.new do |h,k|
|
332
|
+
h[k] = max_prepared_arg
|
333
|
+
max_prepared_arg += 1
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
# Allow use of bind arguments for PostgreSQL using the pg driver.
|
339
|
+
module BindArgumentMethods
|
340
|
+
include ArgumentMapper
|
341
|
+
|
342
|
+
private
|
343
|
+
|
344
|
+
# Execute the given SQL with the stored bind arguments.
|
345
|
+
def execute(sql, opts={}, &block)
|
346
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
347
|
+
end
|
348
|
+
|
349
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
350
|
+
def execute_dui(sql, opts={}, &block)
|
351
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
352
|
+
end
|
353
|
+
|
354
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
355
|
+
def execute_insert(sql, opts={}, &block)
|
356
|
+
super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
# Allow use of server side prepared statements for PostgreSQL using the
|
361
|
+
# pg driver.
|
362
|
+
module PreparedStatementMethods
|
363
|
+
include BindArgumentMethods
|
364
|
+
|
365
|
+
private
|
366
|
+
|
367
|
+
# Execute the stored prepared statement name and the stored bind
|
368
|
+
# arguments instead of the SQL given.
|
369
|
+
def execute(sql, opts={}, &block)
|
370
|
+
super(prepared_statement_name, opts, &block)
|
371
|
+
end
|
372
|
+
|
373
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
374
|
+
def execute_dui(sql, opts={}, &block)
|
375
|
+
super(prepared_statement_name, opts, &block)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Same as execute, explicit due to intricacies of alias and super.
|
379
|
+
def execute_insert(sql, opts={}, &block)
|
380
|
+
super(prepared_statement_name, opts, &block)
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# Execute the given type of statement with the hash of values.
|
385
|
+
def call(type, hash, values=nil, &block)
|
386
|
+
ps = to_prepared_statement(type, values)
|
387
|
+
ps.extend(BindArgumentMethods)
|
388
|
+
ps.call(hash, &block)
|
389
|
+
end
|
390
|
+
|
391
|
+
# Prepare the given type of statement with the given name, and store
|
392
|
+
# it in the database to be called later.
|
393
|
+
def prepare(type, name, values=nil)
|
394
|
+
ps = to_prepared_statement(type, values)
|
395
|
+
ps.extend(PreparedStatementMethods)
|
396
|
+
ps.prepared_statement_name = name
|
397
|
+
db.prepared_statements[name] = ps
|
398
|
+
end
|
399
|
+
|
400
|
+
private
|
401
|
+
|
402
|
+
# PostgreSQL uses $N for placeholders instead of ?, so use a $
|
403
|
+
# as the placeholder.
|
404
|
+
def prepared_arg_placeholder
|
405
|
+
PREPARED_ARG_PLACEHOLDER
|
406
|
+
end
|
407
|
+
end
|
196
408
|
end
|
197
409
|
end
|
198
410
|
end
|
199
|
-
|