sequel 2.4.0 → 2.5.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 +34 -0
- data/Rakefile +1 -1
- data/lib/sequel_core.rb +16 -7
- data/lib/sequel_core/adapters/ado.rb +6 -2
- data/lib/sequel_core/adapters/db2.rb +1 -1
- data/lib/sequel_core/adapters/jdbc.rb +2 -2
- data/lib/sequel_core/adapters/jdbc/postgresql.rb +22 -10
- data/lib/sequel_core/adapters/mysql.rb +2 -2
- data/lib/sequel_core/adapters/odbc.rb +6 -2
- data/lib/sequel_core/adapters/postgres.rb +25 -14
- data/lib/sequel_core/adapters/shared/mysql.rb +15 -35
- data/lib/sequel_core/adapters/shared/postgres.rb +137 -77
- data/lib/sequel_core/adapters/sqlite.rb +2 -2
- data/lib/sequel_core/core_ext.rb +11 -7
- data/lib/sequel_core/database.rb +18 -1
- data/lib/sequel_core/dataset.rb +23 -7
- data/lib/sequel_core/dataset/convenience.rb +1 -1
- data/lib/sequel_core/dataset/sql.rb +46 -31
- data/lib/sequel_core/exceptions.rb +4 -0
- data/lib/sequel_core/schema/generator.rb +43 -3
- data/lib/sequel_core/schema/sql.rb +52 -26
- data/lib/sequel_model.rb +2 -5
- data/lib/sequel_model/associations.rb +3 -3
- data/lib/sequel_model/base.rb +19 -13
- data/lib/sequel_model/record.rb +19 -11
- data/lib/sequel_model/schema.rb +10 -4
- data/lib/sequel_model/validations.rb +20 -7
- data/spec/adapters/mysql_spec.rb +1 -1
- data/spec/adapters/postgres_spec.rb +64 -9
- data/spec/integration/dataset_test.rb +32 -0
- data/spec/sequel_core/core_sql_spec.rb +38 -0
- data/spec/sequel_core/database_spec.rb +16 -1
- data/spec/sequel_core/dataset_spec.rb +66 -1
- data/spec/sequel_core/schema_generator_spec.rb +23 -3
- data/spec/sequel_core/schema_spec.rb +175 -4
- data/spec/sequel_model/record_spec.rb +47 -0
- data/spec/sequel_model/validations_spec.rb +70 -0
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,37 @@
|
|
1
|
+
=== 2.5.0 (2008-09-03)
|
2
|
+
|
3
|
+
* Add Dataset #set_defaults and #set_overrides, used for scoping the values used in insert/update statements (jeremyevans)
|
4
|
+
|
5
|
+
* Allow Models to use the RETURNING clause when inserting records on PostgreSQL (jeremyevans)
|
6
|
+
|
7
|
+
* Raise Sequel::DatabaseError instead of generic Sequel::Error for database errors, don't swallow tracebacks (jeremyevans)
|
8
|
+
|
9
|
+
* Use INSERT ... RETURNING ... with PostgreSQL 8.2 and higher (jeremyevans)
|
10
|
+
|
11
|
+
* Make insert_sql, delete_sql, and update_sql respect the :sql option (jeremyevans)
|
12
|
+
|
13
|
+
* Default to converting 2 digit years, use Sequel.convert_two_digit_years = false to get back the old behavior (jeremyevans)
|
14
|
+
|
15
|
+
* Make the PostgreSQL adapter with the pg driver use async_exec, so it doesn't block the entire interpreter (jeremyevans)
|
16
|
+
|
17
|
+
* Make the schema generators support composite primary and foreign keys and unique constraints (jarredholman)
|
18
|
+
|
19
|
+
* Work with the 2008.08.17 version of the pg gem (erikh)
|
20
|
+
|
21
|
+
* Disallow abuse of SQL function syntax for types (use :type=>:varchar, :size=>255 instead of :type=>:varchar[255]) (jeremyevans)
|
22
|
+
|
23
|
+
* Quote index names when creating or dropping indexes (jeremyevans, SanityInAnarchy)
|
24
|
+
|
25
|
+
* Don't have column accessor methods override plugin instance methods (jeremyevans)
|
26
|
+
|
27
|
+
* Allow validation of multiple attributes at once, with built in support for uniqueness checking of multiple columns (jeremyevans)
|
28
|
+
|
29
|
+
* In PostgreSQL adapter, fix inserting a row with a primary key value inside a transaction (jeremyevans)
|
30
|
+
|
31
|
+
* Allow before_save and before_update to affect the columns saved by save_changes (jeremyevans)
|
32
|
+
|
33
|
+
* Make Dataset#single_value work when graphing, which fixes count and paginate on graphed datasets (jeremyevans)
|
34
|
+
|
1
35
|
=== 2.4.0 (2008-08-06)
|
2
36
|
|
3
37
|
* Handle Java::JavaSql::Date type in the JDBC adapter (jeremyevans)
|
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ require "spec/rake/spectask"
|
|
8
8
|
include FileUtils
|
9
9
|
|
10
10
|
NAME = 'sequel'
|
11
|
-
VERS = '2.
|
11
|
+
VERS = '2.5.0'
|
12
12
|
CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage", "www/public/*.html"]
|
13
13
|
RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
|
14
14
|
'Sequel: The Database Toolkit for Ruby', '--main', 'README']
|
data/lib/sequel_core.rb
CHANGED
@@ -22,21 +22,30 @@ end
|
|
22
22
|
#
|
23
23
|
# Sequel.sqlite('blog.db'){|db| puts db.users.count}
|
24
24
|
#
|
25
|
-
# Sequel can use either Time or DateTime for times returned from the
|
26
|
-
# database. It defaults to Time. To change it to DateTime, use:
|
27
|
-
#
|
28
|
-
# Sequel.datetime_class = DateTime
|
29
|
-
#
|
30
25
|
# Sequel converts the column type tinyint to a boolean by default,
|
31
26
|
# you can override the conversion to use tinyint as an integer:
|
32
27
|
#
|
33
28
|
# Sequel.convert_tinyint_to_bool = false
|
29
|
+
#
|
30
|
+
# Sequel converts two digit years in Dates and DateTimes by default,
|
31
|
+
# so 01/02/03 is interpreted at January 2nd, 2003, and 12/13/99 is interpreted
|
32
|
+
# as December 13, 1999.. You can override this # to treat those dates as
|
33
|
+
# January 2nd, 0003 and December 13, 0099, respectively, by setting:
|
34
|
+
#
|
35
|
+
# Sequel.convert_two_digit_years = false
|
36
|
+
#
|
37
|
+
# Sequel can use either Time or DateTime for times returned from the
|
38
|
+
# database. It defaults to Time. To change it to DateTime, use:
|
39
|
+
#
|
40
|
+
# Sequel.datetime_class = DateTime
|
34
41
|
module Sequel
|
35
|
-
@datetime_class = Time
|
36
42
|
@convert_tinyint_to_bool = true
|
43
|
+
@convert_two_digit_years = true
|
44
|
+
@datetime_class = Time
|
37
45
|
|
38
|
-
metaattr_accessor :datetime_class
|
39
46
|
metaattr_accessor :convert_tinyint_to_bool
|
47
|
+
metaattr_accessor :convert_two_digit_years
|
48
|
+
metaattr_accessor :datetime_class
|
40
49
|
|
41
50
|
# Creates a new database object based on the supplied connection string
|
42
51
|
# and optional arguments. The specified scheme determines the database
|
@@ -14,14 +14,18 @@ module Sequel
|
|
14
14
|
class Database < Sequel::Database
|
15
15
|
set_adapter_scheme :ado
|
16
16
|
|
17
|
-
def
|
18
|
-
opts
|
17
|
+
def initialize(opts)
|
18
|
+
super(opts)
|
19
19
|
opts[:driver] ||= 'SQL Server'
|
20
20
|
case opts[:driver]
|
21
21
|
when 'SQL Server'
|
22
22
|
require 'sequel_core/adapters/shared/mssql'
|
23
23
|
extend Sequel::MSSQL::DatabaseMethods
|
24
24
|
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def connect(server)
|
28
|
+
opts = server_opts(server)
|
25
29
|
s = "driver=#{opts[:driver]};server=#{opts[:host]};database=#{opts[:database]}#{";uid=#{opts[:user]};pwd=#{opts[:password]}" if opts[:user]}"
|
26
30
|
handle = WIN32OLE.new('ADODB.Connection')
|
27
31
|
handle.Open(s)
|
@@ -126,7 +126,7 @@ module Sequel
|
|
126
126
|
end
|
127
127
|
end
|
128
128
|
rescue NativeException, JavaSQL::SQLException => e
|
129
|
-
|
129
|
+
raise_error(e)
|
130
130
|
ensure
|
131
131
|
stmt.close
|
132
132
|
end
|
@@ -232,7 +232,7 @@ module Sequel
|
|
232
232
|
end
|
233
233
|
end
|
234
234
|
rescue NativeException, JavaSQL::SQLException => e
|
235
|
-
|
235
|
+
raise_error(e)
|
236
236
|
ensure
|
237
237
|
cps.close unless name
|
238
238
|
end
|
@@ -21,7 +21,7 @@ module Sequel
|
|
21
21
|
rows = stmt.send(method, sql)
|
22
22
|
yield(rows) if block_given?
|
23
23
|
rescue NativeException => e
|
24
|
-
|
24
|
+
raise_error(e)
|
25
25
|
ensure
|
26
26
|
stmt.close
|
27
27
|
end
|
@@ -30,15 +30,10 @@ module Sequel
|
|
30
30
|
private
|
31
31
|
|
32
32
|
# JDBC specific method of getting specific values from a result set.
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
case vals.length
|
38
|
-
when 1
|
39
|
-
r.getString(vals.first+1)
|
40
|
-
else
|
41
|
-
vals.collect{|col| r.getString(col+1)}
|
33
|
+
def single_value(r)
|
34
|
+
unless r.nil?
|
35
|
+
r.next
|
36
|
+
r.getString(1) unless r.getRow == 0
|
42
37
|
end
|
43
38
|
end
|
44
39
|
end
|
@@ -48,6 +43,15 @@ module Sequel
|
|
48
43
|
module DatabaseMethods
|
49
44
|
include Sequel::Postgres::DatabaseMethods
|
50
45
|
|
46
|
+
# Add the primary_keys and primary_key_sequences instance variables,
|
47
|
+
# so we can get the correct return values for inserted rows.
|
48
|
+
def self.extended(db)
|
49
|
+
db.instance_eval do
|
50
|
+
@primary_keys = {}
|
51
|
+
@primary_key_sequences = {}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
51
55
|
# Return instance of Sequel::JDBC::Postgres::Dataset with the given opts.
|
52
56
|
def dataset(opts=nil)
|
53
57
|
Sequel::JDBC::Postgres::Dataset.new(self, opts)
|
@@ -65,6 +69,7 @@ module Sequel
|
|
65
69
|
def setup_connection(conn)
|
66
70
|
conn = super(conn)
|
67
71
|
conn.extend(Sequel::JDBC::Postgres::AdapterMethods)
|
72
|
+
conn.db = self
|
68
73
|
conn
|
69
74
|
end
|
70
75
|
|
@@ -77,6 +82,13 @@ module Sequel
|
|
77
82
|
# Dataset subclass used for datasets that connect to PostgreSQL via JDBC.
|
78
83
|
class Dataset < JDBC::Dataset
|
79
84
|
include Sequel::Postgres::DatasetMethods
|
85
|
+
|
86
|
+
# Add the shared PostgreSQL prepared statement methods
|
87
|
+
def prepare(*args)
|
88
|
+
ps = super
|
89
|
+
ps.extend(::Sequel::Postgres::DatasetMethods::PreparedStatementMethods)
|
90
|
+
ps
|
91
|
+
end
|
80
92
|
|
81
93
|
# Convert Java::JavaSql::Timestamps correctly, and handle SQL::Blobs
|
82
94
|
# correctly.
|
@@ -137,7 +137,7 @@ module Sequel
|
|
137
137
|
begin
|
138
138
|
synchronize(opts[:server]){|conn| _execute(conn, sql, opts, &block)}
|
139
139
|
rescue Mysql::Error => e
|
140
|
-
|
140
|
+
raise_error(e)
|
141
141
|
end
|
142
142
|
end
|
143
143
|
|
@@ -163,7 +163,7 @@ module Sequel
|
|
163
163
|
rescue ::Exception => e
|
164
164
|
log_info(SQL_ROLLBACK)
|
165
165
|
conn.query(SQL_ROLLBACK)
|
166
|
-
|
166
|
+
transaction_error(e, Mysql::Error)
|
167
167
|
ensure
|
168
168
|
unless e
|
169
169
|
log_info(SQL_COMMIT)
|
@@ -8,13 +8,17 @@ module Sequel
|
|
8
8
|
GUARDED_DRV_NAME = /^\{.+\}$/.freeze
|
9
9
|
DRV_NAME_GUARDS = '{%s}'.freeze
|
10
10
|
|
11
|
-
def
|
12
|
-
opts
|
11
|
+
def initialize(opts)
|
12
|
+
super(opts)
|
13
13
|
case opts[:db_type]
|
14
14
|
when 'mssql'
|
15
15
|
require 'sequel_core/adapters/shared/mssql'
|
16
16
|
extend Sequel::MSSQL::DatabaseMethods
|
17
17
|
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def connect(server)
|
21
|
+
opts = server_opts(server)
|
18
22
|
if opts.include? :driver
|
19
23
|
drv = ::ODBC::Driver.new
|
20
24
|
drv.name = 'Sequel ODBC Driver130'
|
@@ -56,7 +56,12 @@ rescue LoadError => e
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
59
|
-
alias_method :finish, :close unless method_defined?(:finish)
|
59
|
+
alias_method :finish, :close unless method_defined?(:finish)
|
60
|
+
alias_method :async_exec, :exec unless method_defined?(:async_exec)
|
61
|
+
unless method_defined?(:block)
|
62
|
+
def block(timeout=nil)
|
63
|
+
end
|
64
|
+
end
|
60
65
|
end
|
61
66
|
class PGresult
|
62
67
|
alias_method :nfields, :num_fields unless method_defined?(:nfields)
|
@@ -119,11 +124,13 @@ module Sequel
|
|
119
124
|
def execute(sql, args=nil)
|
120
125
|
q = nil
|
121
126
|
begin
|
122
|
-
q = args ?
|
127
|
+
q = args ? async_exec(sql, args) : async_exec(sql)
|
123
128
|
rescue PGError => e
|
124
129
|
raise if status == Adapter::CONNECTION_OK
|
125
130
|
reset
|
126
|
-
q = args ?
|
131
|
+
q = args ? async_exec(sql, args) : async_exec(sql)
|
132
|
+
ensure
|
133
|
+
block
|
127
134
|
end
|
128
135
|
begin
|
129
136
|
block_given? ? yield(q) : q.cmd_tuples
|
@@ -144,14 +151,8 @@ module Sequel
|
|
144
151
|
private
|
145
152
|
|
146
153
|
# Return the requested values for the given row.
|
147
|
-
def
|
148
|
-
|
149
|
-
case vals.length
|
150
|
-
when 1
|
151
|
-
r.getvalue(0, vals.first)
|
152
|
-
else
|
153
|
-
vals.collect{|col| r.getvalue(0, col)}
|
154
|
-
end
|
154
|
+
def single_value(r)
|
155
|
+
r.getvalue(0, 0) unless r.nil? || (r.ntuples == 0)
|
155
156
|
end
|
156
157
|
end
|
157
158
|
|
@@ -162,6 +163,14 @@ module Sequel
|
|
162
163
|
|
163
164
|
set_adapter_scheme :postgres
|
164
165
|
|
166
|
+
# Add the primary_keys and primary_key_sequences instance variables,
|
167
|
+
# so we can get the correct return values for inserted rows.
|
168
|
+
def initialize(*args)
|
169
|
+
super
|
170
|
+
@primary_keys = {}
|
171
|
+
@primary_key_sequences = {}
|
172
|
+
end
|
173
|
+
|
165
174
|
# Connects to the database. In addition to the standard database
|
166
175
|
# options, using the :encoding or :charset option changes the
|
167
176
|
# client encoding for the connection.
|
@@ -170,7 +179,7 @@ module Sequel
|
|
170
179
|
conn = Adapter.connect(
|
171
180
|
opts[:host] || 'localhost',
|
172
181
|
opts[:port] || 5432,
|
173
|
-
|
182
|
+
nil, '',
|
174
183
|
opts[:database],
|
175
184
|
opts[:user],
|
176
185
|
opts[:password]
|
@@ -178,6 +187,7 @@ module Sequel
|
|
178
187
|
if encoding = opts[:encoding] || opts[:charset]
|
179
188
|
conn.set_client_encoding(encoding)
|
180
189
|
end
|
190
|
+
conn.db = self
|
181
191
|
conn
|
182
192
|
end
|
183
193
|
|
@@ -199,7 +209,7 @@ module Sequel
|
|
199
209
|
synchronize(opts[:server]){|conn| conn.execute(sql, opts[:arguments], &block)}
|
200
210
|
rescue => e
|
201
211
|
log_info(e.message)
|
202
|
-
|
212
|
+
raise_error(e, :classes=>CONVERTED_EXCEPTIONS)
|
203
213
|
end
|
204
214
|
end
|
205
215
|
|
@@ -215,7 +225,7 @@ module Sequel
|
|
215
225
|
end
|
216
226
|
rescue => e
|
217
227
|
log_info(e.message)
|
218
|
-
|
228
|
+
raise_error(e, :classes=>CONVERTED_EXCEPTIONS)
|
219
229
|
end
|
220
230
|
end
|
221
231
|
|
@@ -361,6 +371,7 @@ module Sequel
|
|
361
371
|
# pg driver.
|
362
372
|
module PreparedStatementMethods
|
363
373
|
include BindArgumentMethods
|
374
|
+
include ::Sequel::Postgres::DatasetMethods::PreparedStatementMethods
|
364
375
|
|
365
376
|
private
|
366
377
|
|
@@ -17,15 +17,15 @@ module Sequel
|
|
17
17
|
# Use MySQL specific syntax for rename column, set column type, and
|
18
18
|
# drop index cases.
|
19
19
|
def alter_table_sql(table, op)
|
20
|
-
|
21
|
-
|
20
|
+
quoted_table = quote_identifier(table)
|
21
|
+
quoted_name = quote_identifier(op[:name]) if op[:name]
|
22
22
|
case op[:op]
|
23
23
|
when :rename_column
|
24
|
-
"ALTER TABLE #{
|
24
|
+
"ALTER TABLE #{quoted_table} CHANGE COLUMN #{quoted_name} #{quote_identifier(op[:new_name])} #{type_literal(op)}"
|
25
25
|
when :set_column_type
|
26
|
-
"ALTER TABLE #{
|
26
|
+
"ALTER TABLE #{quoted_table} CHANGE COLUMN #{quoted_name} #{quoted_name} #{type_literal(op)}"
|
27
27
|
when :drop_index
|
28
|
-
"
|
28
|
+
"#{drop_index_sql(table, op)} ON #{quoted_table}"
|
29
29
|
else
|
30
30
|
super(table, op)
|
31
31
|
end
|
@@ -36,44 +36,24 @@ module Sequel
|
|
36
36
|
AUTO_INCREMENT
|
37
37
|
end
|
38
38
|
|
39
|
-
# Handle MySQL specific
|
40
|
-
def
|
41
|
-
|
42
|
-
return constraint_definition_sql(column)
|
43
|
-
end
|
44
|
-
sql = "#{literal(column[:name].to_sym)} #{TYPES[column[:type]]}"
|
45
|
-
column[:size] ||= 255 if column[:type] == :varchar
|
46
|
-
elements = column[:size] || column[:elements]
|
47
|
-
sql << literal(Array(elements)) if elements
|
48
|
-
sql << UNSIGNED if column[:unsigned]
|
49
|
-
sql << UNIQUE if column[:unique]
|
50
|
-
sql << NOT_NULL if column[:null] == false
|
51
|
-
sql << NULL if column[:null] == true
|
52
|
-
sql << " DEFAULT #{literal(column[:default])}" if column.include?(:default)
|
53
|
-
sql << PRIMARY_KEY if column[:primary_key]
|
54
|
-
sql << " #{auto_increment_sql}" if column[:auto_increment]
|
55
|
-
if column[:table]
|
56
|
-
sql << ", FOREIGN KEY (#{literal(column[:name].to_sym)}) REFERENCES #{column[:table]}"
|
57
|
-
sql << literal(Array(column[:key])) if column[:key]
|
58
|
-
sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
|
59
|
-
end
|
60
|
-
sql
|
39
|
+
# Handle MySQL specific syntax for column references
|
40
|
+
def column_references_sql(column)
|
41
|
+
", FOREIGN KEY (#{quote_identifier(column[:name])})#{super(column)}"
|
61
42
|
end
|
62
43
|
|
63
44
|
# Handle MySQL specific index SQL syntax
|
64
45
|
def index_definition_sql(table_name, index)
|
65
|
-
index_name = index[:name] || default_index_name(table_name, index[:columns])
|
66
|
-
|
67
|
-
case index[:type]
|
46
|
+
index_name = quote_identifier(index[:name] || default_index_name(table_name, index[:columns]))
|
47
|
+
index_type = case index[:type]
|
68
48
|
when :full_text
|
69
|
-
"
|
49
|
+
"FULLTEXT "
|
70
50
|
when :spatial
|
71
|
-
"
|
72
|
-
when nil
|
73
|
-
"CREATE #{unique}INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
|
51
|
+
"SPATIAL "
|
74
52
|
else
|
75
|
-
|
53
|
+
using = " USING #{index[:type]}" unless index[:type] == nil
|
54
|
+
"UNIQUE " if index[:unique]
|
76
55
|
end
|
56
|
+
"CREATE #{index_type}INDEX #{index_name} ON #{quote_identifier(table_name)} #{literal(index[:columns])}#{using}"
|
77
57
|
end
|
78
58
|
|
79
59
|
# Get version of MySQL server, used for determined capabilities.
|
@@ -6,19 +6,11 @@ module Sequel
|
|
6
6
|
|
7
7
|
# Methods shared by adapter/connection instances.
|
8
8
|
module AdapterMethods
|
9
|
+
attr_writer :db
|
10
|
+
|
9
11
|
SELECT_CURRVAL = "SELECT currval('%s')".freeze
|
10
|
-
|
11
|
-
SELECT
|
12
|
-
FROM pg_class, pg_attribute, pg_index
|
13
|
-
WHERE pg_class.oid = pg_attribute.attrelid AND
|
14
|
-
pg_class.oid = pg_index.indrelid AND
|
15
|
-
pg_index.indkey[0] = pg_attribute.attnum AND
|
16
|
-
pg_index.indisprimary = 't' AND
|
17
|
-
pg_class.relname = '%s'
|
18
|
-
end_sql
|
19
|
-
SELECT_PK_AND_CUSTOM_SEQUENCE = <<-end_sql
|
20
|
-
SELECT attr.attname,
|
21
|
-
CASE
|
12
|
+
SELECT_CUSTOM_SEQUENCE = <<-end_sql
|
13
|
+
SELECT CASE
|
22
14
|
WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
|
23
15
|
substr(split_part(def.adsrc, '''', 2),
|
24
16
|
strpos(split_part(def.adsrc, '''', 2), '.')+1)
|
@@ -33,8 +25,17 @@ module Sequel
|
|
33
25
|
AND cons.contype = 'p'
|
34
26
|
AND def.adsrc ~* 'nextval'
|
35
27
|
end_sql
|
36
|
-
|
37
|
-
SELECT
|
28
|
+
SELECT_PK = <<-end_sql
|
29
|
+
SELECT pg_attribute.attname
|
30
|
+
FROM pg_class, pg_attribute, pg_index
|
31
|
+
WHERE pg_class.oid = pg_attribute.attrelid AND
|
32
|
+
pg_class.oid = pg_index.indrelid AND
|
33
|
+
pg_index.indkey[0] = pg_attribute.attnum AND
|
34
|
+
pg_index.indisprimary = 't' AND
|
35
|
+
pg_class.relname = '%s'
|
36
|
+
end_sql
|
37
|
+
SELECT_SERIAL_SEQUENCE = <<-end_sql
|
38
|
+
SELECT seq.relname
|
38
39
|
FROM pg_class seq, pg_attribute attr, pg_depend dep,
|
39
40
|
pg_namespace name, pg_constraint cons
|
40
41
|
WHERE seq.oid = dep.objid
|
@@ -52,39 +53,38 @@ module Sequel
|
|
52
53
|
# to implement multi-level transactions with savepoints.
|
53
54
|
attr_accessor :transaction_depth
|
54
55
|
|
55
|
-
# Get the last inserted value for the given
|
56
|
-
def last_insert_id(
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
end
|
64
|
-
if seq = @table_sequences[table]
|
65
|
-
execute(SELECT_CURRVAL % seq) do |r|
|
66
|
-
val = result_set_values(r, 0)
|
67
|
-
val.to_i if val
|
68
|
-
end
|
56
|
+
# Get the last inserted value for the given sequence.
|
57
|
+
def last_insert_id(sequence)
|
58
|
+
sql = SELECT_CURRVAL % sequence
|
59
|
+
@db.log_info(sql)
|
60
|
+
execute(sql) do |r|
|
61
|
+
val = single_value(r)
|
62
|
+
return val.to_i if val
|
69
63
|
end
|
70
64
|
end
|
71
65
|
|
72
66
|
# Get the primary key and sequence for the given table.
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
67
|
+
def sequence(table)
|
68
|
+
sql = SELECT_SERIAL_SEQUENCE % table
|
69
|
+
@db.log_info(sql)
|
70
|
+
execute(sql) do |r|
|
71
|
+
seq = single_value(r)
|
72
|
+
return seq if seq
|
77
73
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
74
|
+
|
75
|
+
sql = SELECT_CUSTOM_SEQUENCE % table
|
76
|
+
@db.log_info(sql)
|
77
|
+
execute(sql) do |r|
|
78
|
+
return single_value(r)
|
81
79
|
end
|
82
80
|
end
|
83
81
|
|
84
82
|
# Get the primary key for the given table.
|
85
83
|
def primary_key(table)
|
86
|
-
|
87
|
-
|
84
|
+
sql = SELECT_PK % table
|
85
|
+
@db.log_info(sql)
|
86
|
+
execute(sql) do |r|
|
87
|
+
return single_value(r)
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
@@ -92,7 +92,7 @@ module Sequel
|
|
92
92
|
# Methods shared by Database instances that connect to PostgreSQL.
|
93
93
|
module DatabaseMethods
|
94
94
|
PREPARED_ARG_PLACEHOLDER = '$'.lit.freeze
|
95
|
-
RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session/.freeze
|
95
|
+
RE_CURRVAL_ERROR = /currval of sequence "(.*)" is not yet defined in this session|relation "(.*)" does not exist/.freeze
|
96
96
|
RELATION_QUERY = {:from => [:pg_class], :select => [:relname]}.freeze
|
97
97
|
RELATION_FILTER = "(relkind = 'r') AND (relname !~ '^pg|sql')".freeze
|
98
98
|
SQL_BEGIN = 'BEGIN'.freeze
|
@@ -103,6 +103,16 @@ module Sequel
|
|
103
103
|
SQL_RELEASE_SAVEPOINT = 'RELEASE SAVEPOINT autopoint_%d'.freeze
|
104
104
|
SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
|
105
105
|
|
106
|
+
# Remove the cached entries for primary keys and sequences when dropping a table.
|
107
|
+
def drop_table(*names)
|
108
|
+
names.each do |name|
|
109
|
+
s = name.to_sym
|
110
|
+
@primary_keys.delete(s)
|
111
|
+
@primary_key_sequences.delete(s)
|
112
|
+
end
|
113
|
+
super
|
114
|
+
end
|
115
|
+
|
106
116
|
# Always CASCADE the table drop
|
107
117
|
def drop_table_sql(name)
|
108
118
|
"DROP TABLE #{name} CASCADE"
|
@@ -128,29 +138,6 @@ module Sequel
|
|
128
138
|
"CREATE #{unique}INDEX #{index_name} ON #{table_name} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
|
129
139
|
end
|
130
140
|
|
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.
|
136
|
-
def insert_result(conn, table, values)
|
137
|
-
begin
|
138
|
-
result = conn.last_insert_id(table)
|
139
|
-
return result if result
|
140
|
-
rescue Exception => e
|
141
|
-
convert_pgerror(e) unless RE_CURRVAL_ERROR.match(e.message)
|
142
|
-
end
|
143
|
-
|
144
|
-
case values
|
145
|
-
when Hash
|
146
|
-
values[primary_key_for_table(conn, table)]
|
147
|
-
when Array
|
148
|
-
values.first
|
149
|
-
else
|
150
|
-
nil
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
141
|
# Dataset containing all current database locks
|
155
142
|
def locks
|
156
143
|
dataset.from(:pg_class, :pg_locks).
|
@@ -158,14 +145,11 @@ module Sequel
|
|
158
145
|
filter(:pg_class__relfilenode=>:pg_locks__relation)
|
159
146
|
end
|
160
147
|
|
161
|
-
#
|
162
|
-
|
163
|
-
|
164
|
-
def primary_key_for_table(conn, table)
|
165
|
-
@primary_keys ||= {}
|
166
|
-
@primary_keys[table] ||= conn.primary_key(table)
|
148
|
+
# Return primary key for the given table.
|
149
|
+
def primary_key(table, server=nil)
|
150
|
+
synchronize(server){|conn| primary_key_for_table(conn, table)}
|
167
151
|
end
|
168
|
-
|
152
|
+
|
169
153
|
# PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
|
170
154
|
# managing incrementing primary keys.
|
171
155
|
def serial_primary_key_options
|
@@ -212,7 +196,7 @@ module Sequel
|
|
212
196
|
log_info(SQL_ROLLBACK)
|
213
197
|
conn.execute(SQL_ROLLBACK) rescue nil
|
214
198
|
end
|
215
|
-
|
199
|
+
transaction_error(e, *CONVERTED_EXCEPTIONS)
|
216
200
|
ensure
|
217
201
|
unless e
|
218
202
|
begin
|
@@ -225,7 +209,7 @@ module Sequel
|
|
225
209
|
end
|
226
210
|
rescue => e
|
227
211
|
log_info(e.message)
|
228
|
-
|
212
|
+
raise_error(e, :classes=>CONVERTED_EXCEPTIONS)
|
229
213
|
end
|
230
214
|
end
|
231
215
|
conn.transaction_depth -= 1
|
@@ -235,9 +219,32 @@ module Sequel
|
|
235
219
|
|
236
220
|
private
|
237
221
|
|
238
|
-
#
|
239
|
-
|
240
|
-
|
222
|
+
# The result of the insert for the given table and values. If values
|
223
|
+
# is an array, assume the first column is the primary key and return
|
224
|
+
# that. If values is a hash, lookup the primary key for the table. If
|
225
|
+
# the primary key is present in the hash, return its value. Otherwise,
|
226
|
+
# look up the sequence for the table's primary key. If one exists,
|
227
|
+
# return the last value the of the sequence for the connection.
|
228
|
+
def insert_result(conn, table, values)
|
229
|
+
case values
|
230
|
+
when Hash
|
231
|
+
return nil unless pk = primary_key_for_table(conn, table)
|
232
|
+
if pk and pkv = values[pk.to_sym]
|
233
|
+
pkv
|
234
|
+
else
|
235
|
+
begin
|
236
|
+
if seq = primary_key_sequence_for_table(conn, table)
|
237
|
+
conn.last_insert_id(seq)
|
238
|
+
end
|
239
|
+
rescue Exception => e
|
240
|
+
raise_error(e, :classes=>CONVERTED_EXCEPTIONS) unless RE_CURRVAL_ERROR.match(e.message)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
when Array
|
244
|
+
values.first
|
245
|
+
else
|
246
|
+
nil
|
247
|
+
end
|
241
248
|
end
|
242
249
|
|
243
250
|
# Use a dollar sign instead of question mark for the argument
|
@@ -246,6 +253,20 @@ module Sequel
|
|
246
253
|
PREPARED_ARG_PLACEHOLDER
|
247
254
|
end
|
248
255
|
|
256
|
+
# Returns primary key for the given table. This information is
|
257
|
+
# cached, and if the primary key for a table is changed, the
|
258
|
+
# @primary_keys instance variable should be reset manually.
|
259
|
+
def primary_key_for_table(conn, table)
|
260
|
+
@primary_keys.include?(table) ? @primary_keys[table] : (@primary_keys[table] = conn.primary_key(table))
|
261
|
+
end
|
262
|
+
|
263
|
+
# Returns primary key for the given table. This information is
|
264
|
+
# cached, and if the primary key for a table is changed, the
|
265
|
+
# @primary_keys instance variable should be reset manually.
|
266
|
+
def primary_key_sequence_for_table(conn, table)
|
267
|
+
@primary_key_sequences.include?(table) ? @primary_key_sequences[table] : (@primary_key_sequences[table] = conn.sequence(table))
|
268
|
+
end
|
269
|
+
|
249
270
|
# When the :schema option is used, use the the given schema.
|
250
271
|
# When the :schema option is nil, return results for all schemas.
|
251
272
|
# If the :schema option is not used, use the public schema.
|
@@ -278,6 +299,20 @@ module Sequel
|
|
278
299
|
SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
|
279
300
|
SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
|
280
301
|
|
302
|
+
# Shared methods for prepared statements when used with PostgreSQL databases.
|
303
|
+
module PreparedStatementMethods
|
304
|
+
# Override insert action to use RETURNING if the server supports it.
|
305
|
+
def prepared_sql
|
306
|
+
return @prepared_sql if @prepared_sql
|
307
|
+
super
|
308
|
+
if @prepared_type == :insert and server_version >= 80200
|
309
|
+
@prepared_sql = insert_returning_pk_sql(@prepared_modify_values)
|
310
|
+
meta_def(:insert_returning_pk_sql){|*args| prepared_sql}
|
311
|
+
end
|
312
|
+
@prepared_sql
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
281
316
|
# Return the results of an ANALYZE query as a string
|
282
317
|
def analyze(opts = nil)
|
283
318
|
analysis = []
|
@@ -317,10 +352,24 @@ module Sequel
|
|
317
352
|
|
318
353
|
# Insert given values into the database.
|
319
354
|
def insert(*values)
|
320
|
-
|
321
|
-
:
|
355
|
+
if !@opts[:sql] and server_version >= 80200
|
356
|
+
single_value(:sql=>insert_returning_pk_sql(*values))
|
357
|
+
else
|
358
|
+
execute_insert(insert_sql(*values), :table=>opts[:from].first,
|
359
|
+
:values=>values.size == 1 ? values.first : values)
|
360
|
+
end
|
322
361
|
end
|
323
|
-
|
362
|
+
|
363
|
+
# Use the RETURNING clause to return the columns listed in returning.
|
364
|
+
def insert_returning_sql(returning, *values)
|
365
|
+
"#{insert_sql(*values)} RETURNING #{column_list(Array(returning))}"
|
366
|
+
end
|
367
|
+
|
368
|
+
# Insert a record returning the record inserted
|
369
|
+
def insert_select(*values)
|
370
|
+
single_record(:naked=>true, :sql=>insert_returning_sql(nil, *values)) if server_version >= 80200
|
371
|
+
end
|
372
|
+
|
324
373
|
# Handle microseconds for Time and DateTime values, as well as PostgreSQL
|
325
374
|
# specific boolean values and string escaping.
|
326
375
|
def literal(v)
|
@@ -357,7 +406,7 @@ module Sequel
|
|
357
406
|
|
358
407
|
# For PostgreSQL version > 8.2, allow inserting multiple rows at once.
|
359
408
|
def multi_insert_sql(columns, values)
|
360
|
-
return super if
|
409
|
+
return super if server_version < 80200
|
361
410
|
|
362
411
|
# postgresql 8.2 introduces support for multi-row insert
|
363
412
|
columns = column_list(columns)
|
@@ -390,6 +439,17 @@ module Sequel
|
|
390
439
|
def execute_insert(sql, opts={})
|
391
440
|
@db.execute_insert(sql, {:server=>@opts[:server] || :default}.merge(opts))
|
392
441
|
end
|
442
|
+
|
443
|
+
# Use the RETURNING clause to return the primary key of the inserted record, if it exists
|
444
|
+
def insert_returning_pk_sql(*values)
|
445
|
+
pk = db.primary_key(opts[:from].first)
|
446
|
+
insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : 'NULL'.lit, *values)
|
447
|
+
end
|
448
|
+
|
449
|
+
# The version of the database server
|
450
|
+
def server_version
|
451
|
+
db.server_version(@opts[:server])
|
452
|
+
end
|
393
453
|
end
|
394
454
|
end
|
395
455
|
end
|