sequel 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -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
|
-
|
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
|
-
#
|
169
|
-
#
|
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
|
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
|
-
|
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(
|
152
|
-
select(
|
153
|
-
filter(
|
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
|
-
|
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 =
|
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
|
190
|
+
dataset(RELATION_QUERY).filter(RELATION_FILTER).map{|r| r[:relname].to_sym}
|
179
191
|
end
|
180
192
|
|
181
|
-
|
182
|
-
|
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
|
-
|
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,
|
346
|
+
def lock(mode, server=nil)
|
314
347
|
sql = LOCK % [source_list(@opts[:from]), mode]
|
315
|
-
@db.synchronize do
|
316
|
-
if
|
317
|
-
@db.transaction
|
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
|