sequel 2.9.0 → 2.10.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 (78) hide show
  1. data/CHANGELOG +56 -0
  2. data/{README → README.rdoc} +85 -57
  3. data/Rakefile +10 -5
  4. data/bin/sequel +7 -16
  5. data/doc/advanced_associations.rdoc +5 -17
  6. data/doc/cheat_sheet.rdoc +18 -20
  7. data/doc/dataset_filtering.rdoc +8 -32
  8. data/doc/schema.rdoc +20 -0
  9. data/lib/sequel_core.rb +35 -1
  10. data/lib/sequel_core/adapters/ado.rb +1 -1
  11. data/lib/sequel_core/adapters/db2.rb +2 -2
  12. data/lib/sequel_core/adapters/dbi.rb +2 -11
  13. data/lib/sequel_core/adapters/do.rb +205 -0
  14. data/lib/sequel_core/adapters/do/mysql.rb +38 -0
  15. data/lib/sequel_core/adapters/do/postgres.rb +92 -0
  16. data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
  17. data/lib/sequel_core/adapters/firebird.rb +298 -0
  18. data/lib/sequel_core/adapters/informix.rb +10 -1
  19. data/lib/sequel_core/adapters/jdbc.rb +78 -19
  20. data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
  21. data/lib/sequel_core/adapters/jdbc/mysql.rb +10 -0
  22. data/lib/sequel_core/adapters/jdbc/postgresql.rb +7 -3
  23. data/lib/sequel_core/adapters/mysql.rb +53 -77
  24. data/lib/sequel_core/adapters/odbc.rb +1 -1
  25. data/lib/sequel_core/adapters/openbase.rb +1 -1
  26. data/lib/sequel_core/adapters/oracle.rb +2 -2
  27. data/lib/sequel_core/adapters/postgres.rb +16 -14
  28. data/lib/sequel_core/adapters/shared/mysql.rb +44 -9
  29. data/lib/sequel_core/adapters/shared/oracle.rb +4 -5
  30. data/lib/sequel_core/adapters/shared/postgres.rb +86 -82
  31. data/lib/sequel_core/adapters/shared/sqlite.rb +39 -24
  32. data/lib/sequel_core/adapters/sqlite.rb +11 -1
  33. data/lib/sequel_core/connection_pool.rb +10 -2
  34. data/lib/sequel_core/core_sql.rb +13 -3
  35. data/lib/sequel_core/database.rb +131 -30
  36. data/lib/sequel_core/database/schema.rb +5 -5
  37. data/lib/sequel_core/dataset.rb +31 -6
  38. data/lib/sequel_core/dataset/convenience.rb +11 -11
  39. data/lib/sequel_core/dataset/query.rb +2 -2
  40. data/lib/sequel_core/dataset/sql.rb +6 -6
  41. data/lib/sequel_core/exceptions.rb +4 -0
  42. data/lib/sequel_core/migration.rb +4 -4
  43. data/lib/sequel_core/schema/generator.rb +19 -3
  44. data/lib/sequel_core/schema/sql.rb +24 -20
  45. data/lib/sequel_core/sql.rb +13 -16
  46. data/lib/sequel_core/version.rb +11 -0
  47. data/lib/sequel_model.rb +2 -0
  48. data/lib/sequel_model/base.rb +2 -2
  49. data/lib/sequel_model/hooks.rb +46 -7
  50. data/lib/sequel_model/record.rb +11 -9
  51. data/lib/sequel_model/schema.rb +1 -1
  52. data/lib/sequel_model/validations.rb +72 -61
  53. data/spec/adapters/firebird_spec.rb +371 -0
  54. data/spec/adapters/mysql_spec.rb +118 -62
  55. data/spec/adapters/oracle_spec.rb +5 -5
  56. data/spec/adapters/postgres_spec.rb +33 -18
  57. data/spec/adapters/sqlite_spec.rb +2 -2
  58. data/spec/integration/dataset_test.rb +3 -3
  59. data/spec/integration/schema_test.rb +55 -5
  60. data/spec/integration/spec_helper.rb +11 -0
  61. data/spec/integration/type_test.rb +59 -16
  62. data/spec/sequel_core/connection_pool_spec.rb +14 -0
  63. data/spec/sequel_core/core_sql_spec.rb +24 -14
  64. data/spec/sequel_core/database_spec.rb +96 -11
  65. data/spec/sequel_core/dataset_spec.rb +97 -37
  66. data/spec/sequel_core/expression_filters_spec.rb +51 -40
  67. data/spec/sequel_core/object_graph_spec.rb +2 -2
  68. data/spec/sequel_core/schema_generator_spec.rb +31 -6
  69. data/spec/sequel_core/schema_spec.rb +25 -9
  70. data/spec/sequel_core/spec_helper.rb +4 -1
  71. data/spec/sequel_core/version_spec.rb +7 -0
  72. data/spec/sequel_model/associations_spec.rb +1 -1
  73. data/spec/sequel_model/hooks_spec.rb +68 -13
  74. data/spec/sequel_model/model_spec.rb +4 -4
  75. data/spec/sequel_model/record_spec.rb +22 -0
  76. data/spec/sequel_model/spec_helper.rb +2 -1
  77. data/spec/sequel_model/validations_spec.rb +107 -7
  78. metadata +15 -5
@@ -51,7 +51,7 @@ module Sequel
51
51
  def fetch_rows(sql)
52
52
  execute(sql) do |result|
53
53
  begin
54
- @columns = result.column_infos.map {|c| c.name.to_sym}
54
+ @columns = result.column_infos.map{|c| output_identifier(c.name)}
55
55
  result.each do |r|
56
56
  row = {}
57
57
  r.each_with_index {|v, i| row[@columns[i]] = v}
@@ -90,10 +90,10 @@ module Sequel
90
90
  def fetch_rows(sql, &block)
91
91
  execute(sql) do |cursor|
92
92
  begin
93
- @columns = cursor.get_col_names.map {|c| c.downcase.to_sym}
93
+ @columns = cursor.get_col_names.map{|c| output_identifier(c)}
94
94
  while r = cursor.fetch
95
95
  row = {}
96
- r.each_with_index {|v, i| row[columns[i]] = v unless columns[i] == :raw_rnum_}
96
+ r.each_with_index {|v, i| row[@columns[i]] = v unless @columns[i] == :raw_rnum_}
97
97
  yield row
98
98
  end
99
99
  ensure
@@ -15,7 +15,7 @@ rescue LoadError => e
15
15
  # If there is no escape_string instead method, but there is an
16
16
  # escape class method, use that instead.
17
17
  def escape_string(str)
18
- self.class.escape(str)
18
+ Sequel::Postgres.force_standard_strings ? str.gsub("'", "''") : self.class.escape(str)
19
19
  end
20
20
  else
21
21
  # Raise an error if no valid string escaping method can be found.
@@ -103,19 +103,12 @@ module Sequel
103
103
  }
104
104
 
105
105
  @use_iso_date_format = true
106
- @client_min_messages = :warning
107
106
 
108
107
  # As an optimization, Sequel sets the date style to ISO, so that PostgreSQL provides
109
108
  # the date in a known format that Sequel can parse faster. This can be turned off
110
109
  # if you require a date style other than ISO.
111
110
  metaattr_accessor :use_iso_date_format
112
111
 
113
- # By default, Sequel sets the minimum level of log messages sent to the client
114
- # to WARNING, where PostgreSQL uses a default of NOTICE. This is to avoid a lot
115
- # of mostly useless messages when running migrations, such as a couple of lines
116
- # for every serial primary key field.
117
- metaattr_accessor :client_min_messages
118
-
119
112
  # PGconn subclass for connection specific methods used with the
120
113
  # pg, postgres, or postgres-pr driver.
121
114
  class Adapter < ::PGconn
@@ -132,11 +125,6 @@ module Sequel
132
125
  @db.log_info(sql)
133
126
  execute(sql)
134
127
  end
135
- if cmm = Postgres.client_min_messages
136
- sql = "SET client_min_messages = '#{cmm.to_s.upcase}'"
137
- @db.log_info(sql)
138
- execute(sql)
139
- end
140
128
  end
141
129
 
142
130
  # Execute the given SQL with this connection. If a block is given,
@@ -324,7 +312,7 @@ module Sequel
324
312
  cols = []
325
313
  execute(sql) do |res|
326
314
  res.nfields.times do |fieldnum|
327
- cols << [fieldnum, PG_TYPES[res.ftype(fieldnum)], res.fname(fieldnum).to_sym]
315
+ cols << [fieldnum, PG_TYPES[res.ftype(fieldnum)], output_identifier(res.fname(fieldnum))]
328
316
  end
329
317
  @columns = cols.map{|c| c.at(2)}
330
318
  res.ntuples.times do |recnum|
@@ -337,6 +325,20 @@ module Sequel
337
325
  end
338
326
  end
339
327
  end
328
+
329
+ # Literalize strings and blobs using code from the native adapter.
330
+ def literal(v)
331
+ case v
332
+ when LiteralString
333
+ v
334
+ when SQL::Blob
335
+ db.synchronize{|c| "'#{c.escape_bytea(v)}'"}
336
+ when String
337
+ db.synchronize{|c| "'#{c.escape_string(v)}'"}
338
+ else
339
+ super
340
+ end
341
+ end
340
342
 
341
343
  if SEQUEL_POSTGRES_USES_PG
342
344
 
@@ -6,6 +6,9 @@ module Sequel
6
6
  end
7
7
  end
8
8
  module MySQL
9
+ # Set the default options used for CREATE TABLE
10
+ metaattr_accessor :default_charset, :default_collate, :default_engine
11
+
9
12
  # Methods shared by Database instances that connect to MySQL,
10
13
  # currently supported by the native and JDBC adapters.
11
14
  module DatabaseMethods
@@ -13,7 +16,8 @@ module Sequel
13
16
  NOT_NULL = Sequel::Schema::SQL::NOT_NULL
14
17
  NULL = Sequel::Schema::SQL::NULL
15
18
  PRIMARY_KEY = Sequel::Schema::SQL::PRIMARY_KEY
16
- TYPES = Sequel::Schema::SQL::TYPES
19
+ TYPES = Sequel::Schema::SQL::TYPES.merge(DateTime=>'datetime', \
20
+ TrueClass=>'tinyint', FalseClass=>'tinyint')
17
21
  UNIQUE = Sequel::Schema::SQL::UNIQUE
18
22
  UNSIGNED = Sequel::Schema::SQL::UNSIGNED
19
23
 
@@ -50,6 +54,16 @@ module Sequel
50
54
  "#{", FOREIGN KEY (#{quote_identifier(column[:name])})" unless column[:type] == :check}#{super(column)}"
51
55
  end
52
56
 
57
+ # Use MySQL specific syntax for engine type and character encoding
58
+ def create_table_sql_list(name, columns, indexes = nil, options = {})
59
+ options[:engine] = Sequel::MySQL.default_engine unless options.include?(:engine)
60
+ options[:charset] = Sequel::MySQL.default_charset unless options.include?(:charset)
61
+ options[:collate] = Sequel::MySQL.default_collate unless options.include?(:collate)
62
+ sql = ["CREATE TABLE #{quote_schema_table(name)} (#{column_list_sql(columns)})#{" ENGINE=#{options[:engine]}" if options[:engine]}#{" DEFAULT CHARSET=#{options[:charset]}" if options[:charset]}#{" DEFAULT COLLATE=#{options[:collate]}" if options[:collate]}"]
63
+ sql.concat(index_list_sql_list(name, indexes)) if indexes && !indexes.empty?
64
+ sql
65
+ end
66
+
53
67
  # Handle MySQL specific index SQL syntax
54
68
  def index_definition_sql(table_name, index)
55
69
  index_name = quote_identifier(index[:name] || default_index_name(table_name, index[:columns]))
@@ -67,13 +81,19 @@ module Sequel
67
81
 
68
82
  # Get version of MySQL server, used for determined capabilities.
69
83
  def server_version
70
- m = /(\d+)\.(\d+)\.(\d+)/.match(get(:version[]))
84
+ m = /(\d+)\.(\d+)\.(\d+)/.match(get(SQL::Function.new(:version)))
71
85
  @server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
72
86
  end
73
87
 
74
88
  # Return an array of symbols specifying table names in the current database.
75
- def tables(server=nil)
76
- self['SHOW TABLES'].server(server).map{|r| r.values.first.to_sym}
89
+ #
90
+ # Options:
91
+ # * :server - Set the server to use
92
+ def tables(opts={})
93
+ ds = self['SHOW TABLES'].server(opts[:server])
94
+ ds.identifier_output_method = nil
95
+ ds2 = dataset
96
+ ds.map{|r| ds2.send(:output_identifier, r.values.first)}
77
97
  end
78
98
 
79
99
  # Changes the database in use by issuing a USE statement. I would be
@@ -87,14 +107,22 @@ module Sequel
87
107
 
88
108
  private
89
109
 
90
- # MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers by default.
91
- def upcase_identifiers_default
92
- false
110
+ # MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
111
+ def identifier_input_method_default
112
+ nil
113
+ end
114
+
115
+ # MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on output.
116
+ def identifier_output_method_default
117
+ nil
93
118
  end
94
119
 
95
120
  # Use the MySQL specific DESCRIBE syntax to get a table description.
96
121
  def schema_parse_table(table_name, opts)
97
- self["DESCRIBE ?", SQL::Identifier.new(table_name)].map do |row|
122
+ ds = self["DESCRIBE ?", SQL::Identifier.new(table_name)]
123
+ ds.identifier_output_method = nil
124
+ ds2 = dataset
125
+ ds.map do |row|
98
126
  row.delete(:Extra)
99
127
  row[:allow_null] = row.delete(:Null) == 'YES'
100
128
  row[:default] = row.delete(:Default)
@@ -102,9 +130,14 @@ module Sequel
102
130
  row[:default] = nil if row[:default].blank?
103
131
  row[:db_type] = row.delete(:Type)
104
132
  row[:type] = schema_column_type(row[:db_type])
105
- [row.delete(:Field).to_sym, row]
133
+ [ds2.send(:output_identifier, row.delete(:Field)), row]
106
134
  end
107
135
  end
136
+
137
+ # Override the standard type conversions with MySQL specific ones
138
+ def type_literal_base(column)
139
+ TYPES[column[:type]]
140
+ end
108
141
  end
109
142
 
110
143
  # Dataset methods shared by datasets that use MySQL databases.
@@ -198,6 +231,8 @@ module Sequel
198
231
  BOOL_TRUE
199
232
  when false
200
233
  BOOL_FALSE
234
+ when DateTime, Time
235
+ v.strftime("'%Y-%m-%d %H:%M:%S'")
201
236
  else
202
237
  super
203
238
  end
@@ -1,14 +1,13 @@
1
1
  module Sequel
2
2
  module Oracle
3
3
  module DatabaseMethods
4
- def tables
5
- from(:tab).select(:tname).filter(:tabtype => 'TABLE').map do |r|
6
- r[:tname].downcase.to_sym
7
- end
4
+ def tables(opts={})
5
+ ds = from(:tab).server(opts[:server]).select(:tname).filter(:tabtype => 'TABLE')
6
+ ds.map{|r| ds.send(:output_identifier, r[:tname])}
8
7
  end
9
8
 
10
9
  def table_exists?(name)
11
- from(:tab).filter(:tname => name.to_s.upcase, :tabtype => 'TABLE').count > 0
10
+ from(:tab).filter(:tname =>dataset.send(:input_identifier, name), :tabtype => 'TABLE').count > 0
12
11
  end
13
12
  end
14
13
 
@@ -32,7 +32,14 @@ module Sequel
32
32
  # uses NativeExceptions, the native adapter uses PGError.
33
33
  CONVERTED_EXCEPTIONS = []
34
34
 
35
+ @client_min_messages = :warning
35
36
  @force_standard_strings = true
37
+
38
+ # By default, Sequel sets the minimum level of log messages sent to the client
39
+ # to WARNING, where PostgreSQL uses a default of NOTICE. This is to avoid a lot
40
+ # of mostly useless messages when running migrations, such as a couple of lines
41
+ # for every serial primary key field.
42
+ metaattr_accessor :client_min_messages
36
43
 
37
44
  # By default, Sequel forces the use of standard strings, so that
38
45
  # '\\' is interpreted as \\ and not \. While PostgreSQL defaults
@@ -46,7 +53,7 @@ module Sequel
46
53
  attr_writer :db
47
54
 
48
55
  SELECT_CURRVAL = "SELECT currval('%s')".freeze
49
- SELECT_CUSTOM_SEQUENCE = <<-end_sql
56
+ SELECT_CUSTOM_SEQUENCE = proc do |schema, table| <<-end_sql
50
57
  SELECT '"' || name.nspname || '"."' || CASE
51
58
  WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
52
59
  substr(split_part(def.adsrc, '''', 2),
@@ -60,10 +67,11 @@ module Sequel
60
67
  JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
61
68
  WHERE cons.contype = 'p'
62
69
  AND def.adsrc ~* 'nextval'
63
- AND name.nspname = '%s'
64
- AND t.relname = '%s'
70
+ #{"AND name.nspname = '#{schema}'" if schema}
71
+ AND t.relname = '#{table}'
65
72
  end_sql
66
- SELECT_PK = <<-end_sql
73
+ end
74
+ SELECT_PK = proc do |schema, table| <<-end_sql
67
75
  SELECT pg_attribute.attname
68
76
  FROM pg_class, pg_attribute, pg_index, pg_namespace
69
77
  WHERE pg_class.oid = pg_attribute.attrelid
@@ -71,10 +79,11 @@ module Sequel
71
79
  AND pg_class.oid = pg_index.indrelid
72
80
  AND pg_index.indkey[0] = pg_attribute.attnum
73
81
  AND pg_index.indisprimary = 't'
74
- AND pg_namespace.nspname = '%s'
75
- AND pg_class.relname = '%s'
82
+ #{"AND pg_namespace.nspname = '#{schema}'" if schema}
83
+ AND pg_class.relname = '#{table}'
76
84
  end_sql
77
- SELECT_SERIAL_SEQUENCE = <<-end_sql
85
+ end
86
+ SELECT_SERIAL_SEQUENCE = proc do |schema, table| <<-end_sql
78
87
  SELECT '"' || name.nspname || '"."' || seq.relname || '"'
79
88
  FROM pg_class seq, pg_attribute attr, pg_depend dep,
80
89
  pg_namespace name, pg_constraint cons
@@ -86,9 +95,10 @@ module Sequel
86
95
  AND attr.attrelid = cons.conrelid
87
96
  AND attr.attnum = cons.conkey[1]
88
97
  AND cons.contype = 'p'
89
- AND name.nspname = '%s'
90
- AND seq.relname = '%s'
98
+ #{"AND name.nspname = '#{schema}'" if schema}
99
+ AND seq.relname = '#{table}'
91
100
  end_sql
101
+ end
92
102
 
93
103
  # Depth of the current transaction on this connection, used
94
104
  # to implement multi-level transactions with savepoints.
@@ -106,6 +116,11 @@ module Sequel
106
116
  # try it unconditionally and rescue any errors.
107
117
  execute(sql) rescue nil
108
118
  end
119
+ if cmm = Postgres.client_min_messages
120
+ sql = "SET client_min_messages = '#{cmm.to_s.upcase}'"
121
+ @db.log_info(sql)
122
+ execute(sql)
123
+ end
109
124
  end
110
125
 
111
126
  # Get the last inserted value for the given sequence.
@@ -120,7 +135,7 @@ module Sequel
120
135
 
121
136
  # Get the primary key for the given table.
122
137
  def primary_key(schema, table)
123
- sql = SELECT_PK % [schema, table]
138
+ sql = SELECT_PK[schema, table]
124
139
  @db.log_info(sql)
125
140
  execute(sql) do |r|
126
141
  return single_value(r)
@@ -129,14 +144,14 @@ module Sequel
129
144
 
130
145
  # Get the primary key and sequence for the given table.
131
146
  def sequence(schema, table)
132
- sql = SELECT_SERIAL_SEQUENCE % [schema, table]
147
+ sql = SELECT_SERIAL_SEQUENCE[schema, table]
133
148
  @db.log_info(sql)
134
149
  execute(sql) do |r|
135
150
  seq = single_value(r)
136
151
  return seq if seq
137
152
  end
138
153
 
139
- sql = SELECT_CUSTOM_SEQUENCE % [schema, table]
154
+ sql = SELECT_CUSTOM_SEQUENCE[schema, table]
140
155
  @db.log_info(sql)
141
156
  execute(sql) do |r|
142
157
  return single_value(r)
@@ -155,6 +170,7 @@ module Sequel
155
170
  SQL_ROLLBACK = 'ROLLBACK'.freeze
156
171
  SQL_RELEASE_SAVEPOINT = 'RELEASE SAVEPOINT autopoint_%d'.freeze
157
172
  SYSTEM_TABLE_REGEXP = /^pg|sql/.freeze
173
+ TYPES = Sequel::Schema::SQL::TYPES.merge(File=>'bytea', String=>'text')
158
174
 
159
175
  # Creates the function in the database. See create_function_sql for arguments.
160
176
  def create_function(*args)
@@ -237,11 +253,6 @@ module Sequel
237
253
  "CREATE TRIGGER #{name} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} ON #{quote_schema_table(table)}#{' FOR EACH ROW' if opts[:each_row]} EXECUTE PROCEDURE #{function}(#{Array(opts[:args]).map{|a| literal(a)}.join(', ')})"
238
254
  end
239
255
 
240
- # The default schema to use if none is specified (default: public)
241
- def default_schema
242
- @default_schema ||= :public
243
- end
244
-
245
256
  # Drops the function from the database. See drop_function_sql for arguments.
246
257
  def drop_function(*args)
247
258
  self << drop_function_sql(*args)
@@ -311,13 +322,13 @@ module Sequel
311
322
  filter = " WHERE #{filter_expr(filter)}" if filter
312
323
  case index_type
313
324
  when :full_text
314
- cols = Array(index[:columns]).map{|x| :COALESCE[x, '']}.sql_string_join(' ')
325
+ cols = Array(index[:columns]).map{|x| SQL::Function.new(:COALESCE, x, '')}.sql_string_join(' ')
315
326
  expr = "(to_tsvector(#{literal(index[:language] || 'simple')}, #{literal(cols)}))"
316
327
  index_type = :gin
317
328
  when :spatial
318
329
  index_type = :gist
319
330
  end
320
- "CREATE #{unique}INDEX #{index_name} ON #{table_name} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
331
+ "CREATE #{unique}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{"USING #{index_type} " if index_type}#{expr}#{filter}"
321
332
  end
322
333
 
323
334
  # Dataset containing all current database locks
@@ -328,8 +339,25 @@ module Sequel
328
339
  end
329
340
 
330
341
  # Return primary key for the given table.
331
- def primary_key(table, server=nil)
332
- synchronize(server){|conn| primary_key_for_table(conn, table)}
342
+ def primary_key(table, opts={})
343
+ quoted_table = quote_schema_table(table)
344
+ return @primary_keys[quoted_table] if @primary_keys.include?(quoted_table)
345
+ @primary_keys[quoted_table] = if conn = opts[:conn]
346
+ conn.primary_key(*schema_and_table(table))
347
+ else
348
+ synchronize(opts[:server]){|con| con.primary_key(*schema_and_table(table))}
349
+ end
350
+ end
351
+
352
+ # Return the sequence providing the default for the primary key for the given table.
353
+ def primary_key_sequence(table, opts={})
354
+ quoted_table = quote_schema_table(table)
355
+ return @primary_key_sequences[quoted_table] if @primary_key_sequences.include?(quoted_table)
356
+ @primary_key_sequences[quoted_table] = if conn = opts[:conn]
357
+ conn.sequence(*schema_and_table(table))
358
+ else
359
+ synchronize(opts[:server]){|con| con.sequence(*schema_and_table(table))}
360
+ end
333
361
  end
334
362
 
335
363
  # SQL DDL statement for renaming a table. PostgreSQL doesn't allow you to change a table's schema in
@@ -351,7 +379,7 @@ module Sequel
351
379
  (conn.server_version rescue nil) if conn.respond_to?(:server_version)
352
380
  end
353
381
  unless @server_version
354
- m = /PostgreSQL (\d+)\.(\d+)\.(\d+)/.match(get(:version[]))
382
+ m = /PostgreSQL (\d+)\.(\d+)\.(\d+)/.match(get(SQL::Function.new(:version)))
355
383
  @server_version = (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
356
384
  end
357
385
  @server_version
@@ -365,7 +393,7 @@ module Sequel
365
393
  def table_exists?(table, opts={})
366
394
  schema, table = schema_and_table(table)
367
395
  opts[:schema] ||= schema
368
- tables(opts){|ds| !ds.first(:relname=>table.to_s).nil?}
396
+ tables(opts){|ds| !ds.first(:relname=>ds.send(:input_identifier, table)).nil?}
369
397
  end
370
398
 
371
399
  # Array of symbols specifying table names in the current database.
@@ -376,8 +404,12 @@ module Sequel
376
404
  # * :schema - The schema to search (default_schema by default)
377
405
  # * :server - The server to use
378
406
  def tables(opts={})
379
- ds = self[:pg_class].join(:pg_namespace, :oid=>:relnamespace, 'r'=>:relkind, :nspname=>(opts[:schema]||default_schema).to_s).select(:relname).exclude(:relname.like(SYSTEM_TABLE_REGEXP)).server(opts[:server])
380
- block_given? ? yield(ds) : ds.map{|r| r[:relname].to_sym}
407
+ ds = self[:pg_class].filter(:relkind=>'r').select(:relname).exclude(:relname.like(SYSTEM_TABLE_REGEXP)).server(opts[:server])
408
+ ds.join!(:pg_namespace, :oid=>:relnamespace, :nspname=>(opts[:schema]||default_schema).to_s) if opts[:schema] || default_schema
409
+ ds.identifier_input_method = nil
410
+ ds.identifier_output_method = nil
411
+ ds2 = dataset
412
+ block_given? ? yield(ds) : ds.map{|r| ds2.send(:output_identifier, r[:relname])}
381
413
  end
382
414
 
383
415
  # PostgreSQL supports multi-level transactions using save points.
@@ -424,10 +456,15 @@ module Sequel
424
456
  end
425
457
 
426
458
  private
459
+
460
+ # PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
461
+ def identifier_input_method_default
462
+ nil
463
+ end
427
464
 
428
- # PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers by default.
429
- def upcase_identifiers_default
430
- false
465
+ # PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on output.
466
+ def identifier_output_method_default
467
+ nil
431
468
  end
432
469
 
433
470
  # The result of the insert for the given table and values. If values
@@ -439,12 +476,12 @@ module Sequel
439
476
  def insert_result(conn, table, values)
440
477
  case values
441
478
  when Hash
442
- return nil unless pk = primary_key_for_table(conn, table)
479
+ return nil unless pk = primary_key(table, :conn=>conn)
443
480
  if pk and pkv = values[pk.to_sym]
444
481
  pkv
445
482
  else
446
483
  begin
447
- if seq = primary_key_sequence_for_table(conn, table)
484
+ if seq = primary_key_sequence(table, :conn=>conn)
448
485
  conn.last_insert_id(seq)
449
486
  end
450
487
  rescue Exception => e
@@ -464,49 +501,9 @@ module Sequel
464
501
  PREPARED_ARG_PLACEHOLDER
465
502
  end
466
503
 
467
- # Returns primary key for the given table. This information is
468
- # cached, and if the primary key for a table is changed, the
469
- # @primary_keys instance variable should be reset manually.
470
- def primary_key_for_table(conn, table)
471
- @primary_keys[quote_schema_table(table)] ||= conn.primary_key(*schema_and_table(table))
472
- end
473
-
474
- # Returns primary key for the given table. This information is
475
- # cached, and if the primary key for a table is changed, the
476
- # @primary_keys instance variable should be reset manually.
477
- def primary_key_sequence_for_table(conn, table)
478
- @primary_key_sequences[quote_schema_table(table)] ||= conn.sequence(*schema_and_table(table))
479
- end
480
-
481
- # Set the default of the row to NULL if it is blank, and set
482
- # the ruby type for the column based on the database type.
483
- def schema_parse_rows(rows)
484
- rows.map do |row|
485
- row[:default] = nil if row[:default].blank?
486
- row[:type] = schema_column_type(row[:db_type])
487
- [row.delete(:name).to_sym, row]
488
- end
489
- end
490
-
491
- # Parse the schema for a single table.
492
- def schema_parse_table(table_name, opts)
493
- schema_parse_rows(schema_parser_dataset(table_name, opts))
494
- end
495
-
496
- # Parse the schema for multiple tables.
497
- def schema_parse_tables(opts)
498
- schemas = {}
499
- schema_parser_dataset(nil, opts).each do |row|
500
- (schemas[quote_schema_table(SQL::QualifiedIdentifier.new(row.delete(:schema), row.delete(:table)))] ||= []) << row
501
- end
502
- schemas.each do |table, rows|
503
- schemas[table] = schema_parse_rows(rows)
504
- end
505
- schemas
506
- end
507
-
508
504
  # The dataset used for parsing table schemas, using the pg_* system catalogs.
509
- def schema_parser_dataset(table_name, opts)
505
+ def schema_parse_table(table_name, opts)
506
+ ds2 = dataset
510
507
  ds = dataset.select(:pg_attribute__attname___name,
511
508
  SQL::Function.new(:format_type, :pg_type__oid, :pg_attribute__atttypmod).as(:db_type),
512
509
  SQL::Function.new(:pg_get_expr, :pg_attrdef__adbin, :pg_class__oid).as(:default),
@@ -518,21 +515,28 @@ module Sequel
518
515
  left_outer_join(:pg_attrdef, :adrelid=>:pg_class__oid, :adnum=>:pg_attribute__attnum).
519
516
  left_outer_join(:pg_index, :indrelid=>:pg_class__oid, :indisprimary=>true).
520
517
  filter(:pg_attribute__attisdropped=>false).
521
- filter(:pg_attribute__attnum > 0).
518
+ filter{|o| o.pg_attribute__attnum > 0}.
519
+ filter(:pg_class__relname=>ds2.send(:input_identifier, table_name)).
522
520
  order(:pg_attribute__attnum)
523
- if table_name
524
- ds.filter!(:pg_class__relname=>table_name.to_s)
525
- else
526
- ds.select_more!(:pg_class__relname___table, :pg_namespace__nspname___schema)
527
- ds.join!(:pg_namespace, :oid=>:pg_class__relnamespace, :nspname=>(opts[:schema] || default_schema).to_s)
521
+ ds.join!(:pg_namespace, :oid=>:pg_class__relnamespace, :nspname=>(opts[:schema] || default_schema).to_s) if opts[:schema] || default_schema
522
+ ds.identifier_input_method = nil
523
+ ds.identifier_output_method = nil
524
+ ds.map do |row|
525
+ row[:default] = nil if row[:default].blank?
526
+ row[:type] = schema_column_type(row[:db_type])
527
+ [ds2.send(:output_identifier, row.delete(:name)), row]
528
528
  end
529
- ds
530
529
  end
531
530
 
532
531
  # Turns an array of argument specifiers into an SQL fragment used for function arguments. See create_function_sql.
533
532
  def sql_function_args(args)
534
533
  "(#{Array(args).map{|a| Array(a).reverse.join(' ')}.join(', ')})"
535
534
  end
535
+
536
+ # Override the standard type conversions with PostgreSQL specific ones
537
+ def type_literal_base(column)
538
+ TYPES[column[:type]]
539
+ end
536
540
  end
537
541
 
538
542
  # Instance methods for datasets that connect to a PostgreSQL database.
@@ -603,7 +607,7 @@ module Sequel
603
607
  # in 8.3 by default, and available for earlier versions as an add-on).
604
608
  def full_text_search(cols, terms, opts = {})
605
609
  lang = opts[:language] || 'simple'
606
- cols = Array(cols).map{|x| :COALESCE[x, '']}.sql_string_join(' ')
610
+ cols = Array(cols).map{|x| SQL::Function.new(:COALESCE, x, '')}.sql_string_join(' ')
607
611
  filter("to_tsvector(#{literal(lang)}, #{literal(cols)}) @@ to_tsquery(#{literal(lang)}, #{literal(Array(terms).join(' | '))})")
608
612
  end
609
613
 
@@ -634,9 +638,9 @@ module Sequel
634
638
  when LiteralString
635
639
  v
636
640
  when SQL::Blob
637
- db.synchronize{|c| "'#{c.escape_bytea(v)}'"}
641
+ "'#{v.gsub(/[\000-\037\047\134\177-\377]/){|b| "\\#{("%o" % b[0..1].unpack("C")[0]).rjust(3, '0')}"}}'"
638
642
  when String
639
- db.synchronize{|c| "'#{c.escape_string(v)}'"}
643
+ "'#{v.gsub("'", "''")}'"
640
644
  when Time
641
645
  "#{v.strftime(PG_TIMESTAMP_FORMAT)}.#{sprintf("%06d",v.usec)}'"
642
646
  when DateTime