sequel 2.9.0 → 2.10.0

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