sequel 2.4.0 → 2.5.0

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