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
@@ -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
|