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.
Files changed (38) hide show
  1. data/CHANGELOG +34 -0
  2. data/Rakefile +1 -1
  3. data/lib/sequel_core.rb +16 -7
  4. data/lib/sequel_core/adapters/ado.rb +6 -2
  5. data/lib/sequel_core/adapters/db2.rb +1 -1
  6. data/lib/sequel_core/adapters/jdbc.rb +2 -2
  7. data/lib/sequel_core/adapters/jdbc/postgresql.rb +22 -10
  8. data/lib/sequel_core/adapters/mysql.rb +2 -2
  9. data/lib/sequel_core/adapters/odbc.rb +6 -2
  10. data/lib/sequel_core/adapters/postgres.rb +25 -14
  11. data/lib/sequel_core/adapters/shared/mysql.rb +15 -35
  12. data/lib/sequel_core/adapters/shared/postgres.rb +137 -77
  13. data/lib/sequel_core/adapters/sqlite.rb +2 -2
  14. data/lib/sequel_core/core_ext.rb +11 -7
  15. data/lib/sequel_core/database.rb +18 -1
  16. data/lib/sequel_core/dataset.rb +23 -7
  17. data/lib/sequel_core/dataset/convenience.rb +1 -1
  18. data/lib/sequel_core/dataset/sql.rb +46 -31
  19. data/lib/sequel_core/exceptions.rb +4 -0
  20. data/lib/sequel_core/schema/generator.rb +43 -3
  21. data/lib/sequel_core/schema/sql.rb +52 -26
  22. data/lib/sequel_model.rb +2 -5
  23. data/lib/sequel_model/associations.rb +3 -3
  24. data/lib/sequel_model/base.rb +19 -13
  25. data/lib/sequel_model/record.rb +19 -11
  26. data/lib/sequel_model/schema.rb +10 -4
  27. data/lib/sequel_model/validations.rb +20 -7
  28. data/spec/adapters/mysql_spec.rb +1 -1
  29. data/spec/adapters/postgres_spec.rb +64 -9
  30. data/spec/integration/dataset_test.rb +32 -0
  31. data/spec/sequel_core/core_sql_spec.rb +38 -0
  32. data/spec/sequel_core/database_spec.rb +16 -1
  33. data/spec/sequel_core/dataset_spec.rb +66 -1
  34. data/spec/sequel_core/schema_generator_spec.rb +23 -3
  35. data/spec/sequel_core/schema_spec.rb +175 -4
  36. data/spec/sequel_model/record_spec.rb +47 -0
  37. data/spec/sequel_model/validations_spec.rb +70 -0
  38. 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.4.0'
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']
@@ -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 connect(server)
18
- opts = server_opts(server)
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)
@@ -69,7 +69,7 @@ module Sequel
69
69
  when SQL_SUCCESS, SQL_SUCCESS_WITH_INFO
70
70
  nil
71
71
  else
72
- raise Error, msg
72
+ raise DatabaseError, msg
73
73
  end
74
74
  end
75
75
  end
@@ -126,7 +126,7 @@ module Sequel
126
126
  end
127
127
  end
128
128
  rescue NativeException, JavaSQL::SQLException => e
129
- raise Error, e.message
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
- raise Error, e.message
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
- raise Error, e.message
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 result_set_values(r, *vals)
34
- return if r.nil?
35
- r.next
36
- return if r.getRow == 0
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
- raise Error.new(e.message)
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
- raise (Mysql::Error === e ? Error.new(e.message) : e) unless Error::Rollback === e
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 connect(server)
12
- opts = server_opts(server)
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 ? exec(sql, args) : exec(sql)
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 ? exec(sql, args) : exec(sql)
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 result_set_values(r, *vals)
148
- return if r.nil? || (r.ntuples == 0)
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
- raise convert_pgerror(e)
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
- raise convert_pgerror(e)
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
- type = type_literal(op[:type])
21
- type << '(255)' if type == 'varchar'
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 #{table} CHANGE COLUMN #{literal(op[:name])} #{literal(op[:new_name])} #{type}"
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 #{table} CHANGE COLUMN #{literal(op[:name])} #{literal(op[:name])} #{type}"
26
+ "ALTER TABLE #{quoted_table} CHANGE COLUMN #{quoted_name} #{quoted_name} #{type_literal(op)}"
27
27
  when :drop_index
28
- "DROP INDEX #{default_index_name(table, op[:columns])} ON #{table}"
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 column syntax (not sure why).
40
- def column_definition_sql(column)
41
- if column[:type] == :check
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
- unique = "UNIQUE " if index[:unique]
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
- "CREATE FULLTEXT INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
49
+ "FULLTEXT "
70
50
  when :spatial
71
- "CREATE SPATIAL INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
72
- when nil
73
- "CREATE #{unique}INDEX #{index_name} ON #{table_name} #{literal(index[:columns])}"
51
+ "SPATIAL "
74
52
  else
75
- "CREATE #{unique}INDEX #{index_name} ON #{table_name} #{literal(index[:columns])} USING #{index[:type]}"
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
- SELECT_PK = <<-end_sql
11
- SELECT pg_attribute.attname
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
- SELECT_PK_AND_SERIAL_SEQUENCE = <<-end_sql
37
- SELECT attr.attname, name.nspname, seq.relname
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 table.
56
- def last_insert_id(table)
57
- @table_sequences ||= {}
58
- if !@table_sequences.include?(table)
59
- pkey_and_seq = pkey_and_sequence(table)
60
- if pkey_and_seq
61
- @table_sequences[table] = pkey_and_seq[1]
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 pkey_and_sequence(table)
74
- execute(SELECT_PK_AND_SERIAL_SEQUENCE % table) do |r|
75
- vals = result_set_values(r, 2, 2)
76
- return vals if vals
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
- execute(SELECT_PK_AND_CUSTOM_SEQUENCE % table) do |r|
80
- result_set_values(r, 0, 1)
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
- execute(SELECT_PK % table) do |r|
87
- result_set_values(r, 0)
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
- # 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.
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
- raise convert_pgerror(e) unless Error::Rollback === e
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
- raise convert_pgerror(e)
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
- # Convert the exception to a Sequel::Error if it is in CONVERTED_EXCEPTIONS.
239
- def convert_pgerror(e)
240
- e.is_one_of?(*CONVERTED_EXCEPTIONS) ? Error.new(e.message) : e
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
- execute_insert(insert_sql(*values), :table=>source_list(@opts[:from]),
321
- :values=>values.size == 1 ? values.first : values)
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 @db.server_version < 80200
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