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