kb-activerecord-jdbc-adapter 0.9.7.1-java

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 (135) hide show
  1. data/History.txt +296 -0
  2. data/LICENSE.txt +21 -0
  3. data/Manifest.txt +139 -0
  4. data/README.txt +219 -0
  5. data/Rakefile +10 -0
  6. data/lib/active_record/connection_adapters/cachedb_adapter.rb +1 -0
  7. data/lib/active_record/connection_adapters/derby_adapter.rb +13 -0
  8. data/lib/active_record/connection_adapters/h2_adapter.rb +13 -0
  9. data/lib/active_record/connection_adapters/hsqldb_adapter.rb +13 -0
  10. data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
  11. data/lib/active_record/connection_adapters/jdbc_adapter.rb +661 -0
  12. data/lib/active_record/connection_adapters/jdbc_adapter_spec.rb +26 -0
  13. data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
  14. data/lib/active_record/connection_adapters/mssql_adapter.rb +13 -0
  15. data/lib/active_record/connection_adapters/mysql_adapter.rb +13 -0
  16. data/lib/active_record/connection_adapters/oracle_adapter.rb +1 -0
  17. data/lib/active_record/connection_adapters/postgresql_adapter.rb +13 -0
  18. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +13 -0
  19. data/lib/activerecord-jdbc-adapter.rb +6 -0
  20. data/lib/arel/engines/sql/compilers/db2_compiler.rb +9 -0
  21. data/lib/arel/engines/sql/compilers/derby_compiler.rb +6 -0
  22. data/lib/arel/engines/sql/compilers/h2_compiler.rb +6 -0
  23. data/lib/arel/engines/sql/compilers/hsqldb_compiler.rb +6 -0
  24. data/lib/arel/engines/sql/compilers/jdbc_compiler.rb +6 -0
  25. data/lib/generators/jdbc/jdbc_generator.rb +9 -0
  26. data/lib/jdbc_adapter.rb +27 -0
  27. data/lib/jdbc_adapter/jdbc.rake +122 -0
  28. data/lib/jdbc_adapter/jdbc_adapter_internal.jar +0 -0
  29. data/lib/jdbc_adapter/jdbc_cachedb.rb +33 -0
  30. data/lib/jdbc_adapter/jdbc_db2.rb +222 -0
  31. data/lib/jdbc_adapter/jdbc_derby.rb +426 -0
  32. data/lib/jdbc_adapter/jdbc_firebird.rb +109 -0
  33. data/lib/jdbc_adapter/jdbc_hsqldb.rb +221 -0
  34. data/lib/jdbc_adapter/jdbc_informix.rb +147 -0
  35. data/lib/jdbc_adapter/jdbc_mimer.rb +145 -0
  36. data/lib/jdbc_adapter/jdbc_mssql.rb +468 -0
  37. data/lib/jdbc_adapter/jdbc_mysql.rb +260 -0
  38. data/lib/jdbc_adapter/jdbc_oracle.rb +397 -0
  39. data/lib/jdbc_adapter/jdbc_postgre.rb +531 -0
  40. data/lib/jdbc_adapter/jdbc_sqlite3.rb +386 -0
  41. data/lib/jdbc_adapter/jdbc_sybase.rb +50 -0
  42. data/lib/jdbc_adapter/missing_functionality_helper.rb +87 -0
  43. data/lib/jdbc_adapter/railtie.rb +9 -0
  44. data/lib/jdbc_adapter/rake_tasks.rb +10 -0
  45. data/lib/jdbc_adapter/tsql_helper.rb +69 -0
  46. data/lib/jdbc_adapter/version.rb +5 -0
  47. data/lib/pg.rb +4 -0
  48. data/rails_generators/jdbc_generator.rb +15 -0
  49. data/rails_generators/templates/config/initializers/jdbc.rb +7 -0
  50. data/rails_generators/templates/lib/tasks/jdbc.rake +8 -0
  51. data/rakelib/compile.rake +23 -0
  52. data/rakelib/package.rake +91 -0
  53. data/rakelib/rails.rake +41 -0
  54. data/rakelib/test.rake +78 -0
  55. data/src/java/jdbc_adapter/JdbcAdapterInternalService.java +53 -0
  56. data/src/java/jdbc_adapter/JdbcConnectionFactory.java +36 -0
  57. data/src/java/jdbc_adapter/JdbcDerbySpec.java +293 -0
  58. data/src/java/jdbc_adapter/JdbcMySQLSpec.java +134 -0
  59. data/src/java/jdbc_adapter/MssqlRubyJdbcConnection.java +71 -0
  60. data/src/java/jdbc_adapter/PostgresRubyJdbcConnection.java +55 -0
  61. data/src/java/jdbc_adapter/RubyJdbcConnection.java +1176 -0
  62. data/src/java/jdbc_adapter/SQLBlock.java +27 -0
  63. data/src/java/jdbc_adapter/Sqlite3RubyJdbcConnection.java +41 -0
  64. data/test/abstract_db_create.rb +107 -0
  65. data/test/activerecord/connection_adapters/type_conversion_test.rb +31 -0
  66. data/test/activerecord/connections/native_jdbc_mysql/connection.rb +25 -0
  67. data/test/cachedb_simple_test.rb +6 -0
  68. data/test/db/cachedb.rb +9 -0
  69. data/test/db/db2.rb +9 -0
  70. data/test/db/derby.rb +14 -0
  71. data/test/db/h2.rb +11 -0
  72. data/test/db/hsqldb.rb +12 -0
  73. data/test/db/informix.rb +11 -0
  74. data/test/db/jdbc.rb +11 -0
  75. data/test/db/jndi_config.rb +30 -0
  76. data/test/db/logger.rb +3 -0
  77. data/test/db/mssql.rb +9 -0
  78. data/test/db/mysql.rb +10 -0
  79. data/test/db/oracle.rb +34 -0
  80. data/test/db/postgres.rb +9 -0
  81. data/test/db/sqlite3.rb +15 -0
  82. data/test/db2_simple_test.rb +10 -0
  83. data/test/derby_migration_test.rb +21 -0
  84. data/test/derby_multibyte_test.rb +12 -0
  85. data/test/derby_simple_test.rb +21 -0
  86. data/test/generic_jdbc_connection_test.rb +9 -0
  87. data/test/h2_simple_test.rb +6 -0
  88. data/test/has_many_through.rb +79 -0
  89. data/test/helper.rb +5 -0
  90. data/test/hsqldb_simple_test.rb +6 -0
  91. data/test/informix_simple_test.rb +48 -0
  92. data/test/jdbc_adapter/jdbc_db2_test.rb +26 -0
  93. data/test/jdbc_adapter/jdbc_sybase_test.rb +33 -0
  94. data/test/jdbc_common.rb +25 -0
  95. data/test/jndi_callbacks_test.rb +38 -0
  96. data/test/jndi_test.rb +35 -0
  97. data/test/manualTestDatabase.rb +191 -0
  98. data/test/minirunit.rb +109 -0
  99. data/test/minirunit/testConnect.rb +14 -0
  100. data/test/minirunit/testH2.rb +73 -0
  101. data/test/minirunit/testHsqldb.rb +73 -0
  102. data/test/minirunit/testLoadActiveRecord.rb +3 -0
  103. data/test/minirunit/testMysql.rb +83 -0
  104. data/test/minirunit/testRawSelect.rb +24 -0
  105. data/test/models/add_not_null_column_to_table.rb +12 -0
  106. data/test/models/auto_id.rb +18 -0
  107. data/test/models/data_types.rb +28 -0
  108. data/test/models/entry.rb +23 -0
  109. data/test/models/mixed_case.rb +20 -0
  110. data/test/models/reserved_word.rb +18 -0
  111. data/test/models/string_id.rb +18 -0
  112. data/test/models/validates_uniqueness_of_string.rb +19 -0
  113. data/test/mssql_db_create_test.rb +26 -0
  114. data/test/mssql_identity_insert_test.rb +19 -0
  115. data/test/mssql_legacy_types_test.rb +58 -0
  116. data/test/mssql_limit_offset_test.rb +108 -0
  117. data/test/mssql_multibyte_test.rb +18 -0
  118. data/test/mssql_simple_test.rb +49 -0
  119. data/test/mysql_db_create_test.rb +25 -0
  120. data/test/mysql_info_test.rb +62 -0
  121. data/test/mysql_multibyte_test.rb +10 -0
  122. data/test/mysql_nonstandard_primary_key_test.rb +42 -0
  123. data/test/mysql_simple_test.rb +32 -0
  124. data/test/oracle_simple_test.rb +54 -0
  125. data/test/pick_rails_version.rb +3 -0
  126. data/test/postgres_db_create_test.rb +21 -0
  127. data/test/postgres_mixed_case_test.rb +19 -0
  128. data/test/postgres_nonseq_pkey_test.rb +40 -0
  129. data/test/postgres_reserved_test.rb +22 -0
  130. data/test/postgres_schema_search_path_test.rb +46 -0
  131. data/test/postgres_simple_test.rb +13 -0
  132. data/test/simple.rb +494 -0
  133. data/test/sqlite3_simple_test.rb +233 -0
  134. data/test/sybase_jtds_simple_test.rb +6 -0
  135. metadata +230 -0
@@ -0,0 +1,531 @@
1
+
2
+ module ::JdbcSpec
3
+ # Don't need to load native postgres adapter
4
+ $LOADED_FEATURES << "active_record/connection_adapters/postgresql_adapter.rb"
5
+
6
+ module ActiveRecordExtensions
7
+ add_method_to_remove_from_ar_base(:postgresql_connection)
8
+
9
+ def postgresql_connection(config)
10
+ require File.dirname(__FILE__) + "/../active_record/connection_adapters/postgresql_adapter"
11
+ config[:host] ||= "localhost"
12
+ config[:port] ||= 5432
13
+ config[:url] ||= "jdbc:postgresql://#{config[:host]}:#{config[:port]}/#{config[:database]}"
14
+ config[:url] << config[:pg_params] if config[:pg_params]
15
+ config[:driver] ||= "org.postgresql.Driver"
16
+ conn = jdbc_connection(config)
17
+ conn.execute("SET SEARCH_PATH TO #{config[:schema_search_path]}") if config[:schema_search_path]
18
+ conn
19
+ end
20
+ end
21
+
22
+ module PostgreSQL
23
+ def self.extended(mod)
24
+ mod.class.class_eval do
25
+ alias_chained_method :insert, :query_dirty, :insert
26
+ end
27
+ end
28
+
29
+ def self.adapter_matcher(name, *)
30
+ name =~ /postgre/i ? self : false
31
+ end
32
+
33
+ def self.column_selector
34
+ [/postgre/i, lambda {|cfg,col| col.extend(::JdbcSpec::PostgreSQL::Column)}]
35
+ end
36
+
37
+ def self.jdbc_connection_class
38
+ ::ActiveRecord::ConnectionAdapters::PostgresJdbcConnection
39
+ end
40
+
41
+ module Column
42
+ def type_cast(value)
43
+ case type
44
+ when :boolean then cast_to_boolean(value)
45
+ else super
46
+ end
47
+ end
48
+
49
+ def simplified_type(field_type)
50
+ return :integer if field_type =~ /^serial/i
51
+ return :string if field_type =~ /\[\]$/i || field_type =~ /^interval/i
52
+ return :string if field_type =~ /^(?:point|lseg|box|"?path"?|polygon|circle)/i
53
+ return :datetime if field_type =~ /^timestamp/i
54
+ return :float if field_type =~ /^real|^money/i
55
+ return :binary if field_type =~ /^bytea/i
56
+ return :boolean if field_type =~ /^bool/i
57
+ super
58
+ end
59
+
60
+ def cast_to_boolean(value)
61
+ return nil if value.nil?
62
+ if value == true || value == false
63
+ value
64
+ else
65
+ %w(true t 1).include?(value.to_s.downcase)
66
+ end
67
+ end
68
+
69
+ # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
70
+ def default_value(value)
71
+ # Boolean types
72
+ return "t" if value =~ /true/i
73
+ return "f" if value =~ /false/i
74
+
75
+ # Char/String/Bytea type values
76
+ return $1 if value =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/
77
+
78
+ # Numeric values
79
+ return value if value =~ /^-?[0-9]+(\.[0-9]*)?/
80
+
81
+ # Fixed dates / timestamp
82
+ return $1 if value =~ /^'(.+)'::(date|timestamp)/
83
+
84
+ # Anything else is blank, some user type, or some function
85
+ # and we can't know the value of that, so return nil.
86
+ return nil
87
+ end
88
+ end
89
+
90
+ def modify_types(tp)
91
+ tp[:primary_key] = "serial primary key"
92
+ tp[:string][:limit] = 255
93
+ tp[:integer][:limit] = nil
94
+ tp[:boolean][:limit] = nil
95
+ tp
96
+ end
97
+
98
+ def adapter_name #:nodoc:
99
+ 'PostgreSQL'
100
+ end
101
+
102
+ def postgresql_version
103
+ @postgresql_version ||=
104
+ begin
105
+ value = select_value('SELECT version()')
106
+ if value =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
107
+ ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
108
+ else
109
+ 0
110
+ end
111
+ end
112
+ end
113
+
114
+ # Does PostgreSQL support migrations?
115
+ def supports_migrations?
116
+ true
117
+ end
118
+
119
+ # Does PostgreSQL support standard conforming strings?
120
+ def supports_standard_conforming_strings?
121
+ # Temporarily set the client message level above error to prevent unintentional
122
+ # error messages in the logs when working on a PostgreSQL database server that
123
+ # does not support standard conforming strings.
124
+ client_min_messages_old = client_min_messages
125
+ self.client_min_messages = 'panic'
126
+
127
+ # postgres-pr does not raise an exception when client_min_messages is set higher
128
+ # than error and "SHOW standard_conforming_strings" fails, but returns an empty
129
+ # PGresult instead.
130
+ has_support = select('SHOW standard_conforming_strings').to_a[0][0] rescue false
131
+ self.client_min_messages = client_min_messages_old
132
+ has_support
133
+ end
134
+
135
+ def supports_insert_with_returning?
136
+ postgresql_version >= 80200
137
+ end
138
+
139
+ def supports_ddl_transactions?
140
+ true
141
+ end
142
+
143
+ def supports_savepoints?
144
+ true
145
+ end
146
+
147
+ def create_savepoint
148
+ execute("SAVEPOINT #{current_savepoint_name}")
149
+ end
150
+
151
+ def rollback_to_savepoint
152
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
153
+ end
154
+
155
+ def release_savepoint
156
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
157
+ end
158
+
159
+ # Returns the configured supported identifier length supported by PostgreSQL,
160
+ # or report the default of 63 on PostgreSQL 7.x.
161
+ def table_alias_length
162
+ @table_alias_length ||= (postgresql_version >= 80000 ? select('SHOW max_identifier_length').to_a[0][0].to_i : 63)
163
+ end
164
+
165
+ def default_sequence_name(table_name, pk = nil)
166
+ default_pk, default_seq = pk_and_sequence_for(table_name)
167
+ default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
168
+ end
169
+
170
+ # Resets sequence to the max value of the table's pk if present.
171
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
172
+ unless pk and sequence
173
+ default_pk, default_sequence = pk_and_sequence_for(table)
174
+ pk ||= default_pk
175
+ sequence ||= default_sequence
176
+ end
177
+ if pk
178
+ if sequence
179
+ quoted_sequence = quote_column_name(sequence)
180
+
181
+ select_value <<-end_sql, 'Reset sequence'
182
+ SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
183
+ end_sql
184
+ else
185
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
186
+ end
187
+ end
188
+ end
189
+
190
+ def quote_regclass(table_name)
191
+ table_name.to_s.split('.').map do |part|
192
+ part =~ /".*"/i ? part : quote_table_name(part)
193
+ end.join('.')
194
+ end
195
+
196
+ # Find a table's primary key and sequence.
197
+ def pk_and_sequence_for(table) #:nodoc:
198
+ # First try looking for a sequence with a dependency on the
199
+ # given table's primary key.
200
+ result = select(<<-end_sql, 'PK and serial sequence')[0]
201
+ SELECT attr.attname, seq.relname
202
+ FROM pg_class seq,
203
+ pg_attribute attr,
204
+ pg_depend dep,
205
+ pg_namespace name,
206
+ pg_constraint cons
207
+ WHERE seq.oid = dep.objid
208
+ AND seq.relkind = 'S'
209
+ AND attr.attrelid = dep.refobjid
210
+ AND attr.attnum = dep.refobjsubid
211
+ AND attr.attrelid = cons.conrelid
212
+ AND attr.attnum = cons.conkey[1]
213
+ AND cons.contype = 'p'
214
+ AND dep.refobjid = '#{table}'::regclass
215
+ end_sql
216
+
217
+ if result.nil? or result.empty?
218
+ # If that fails, try parsing the primary key's default value.
219
+ # Support the 7.x and 8.0 nextval('foo'::text) as well as
220
+ # the 8.1+ nextval('foo'::regclass).
221
+ result = select(<<-end_sql, 'PK and custom sequence')[0]
222
+ SELECT attr.attname,
223
+ CASE
224
+ WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
225
+ substr(split_part(def.adsrc, '''', 2),
226
+ strpos(split_part(def.adsrc, '''', 2), '.')+1)
227
+ ELSE split_part(def.adsrc, '''', 2)
228
+ END as relname
229
+ FROM pg_class t
230
+ JOIN pg_attribute attr ON (t.oid = attrelid)
231
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
232
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
233
+ WHERE t.oid = '#{table}'::regclass
234
+ AND cons.contype = 'p'
235
+ AND def.adsrc ~* 'nextval'
236
+ end_sql
237
+ end
238
+
239
+ [result["attname"], result["relname"]]
240
+ rescue
241
+ nil
242
+ end
243
+
244
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
245
+ # Extract the table from the insert sql. Yuck.
246
+ table = sql.split(" ", 4)[2].gsub('"', '')
247
+
248
+ # Try an insert with 'returning id' if available (PG >= 8.2)
249
+ if supports_insert_with_returning? && id_value.nil?
250
+ pk, sequence_name = *pk_and_sequence_for(table) unless pk
251
+ if pk
252
+ id_value = select_value("#{sql} RETURNING #{quote_column_name(pk)}")
253
+ clear_query_cache #FIXME: Why now?
254
+ return id_value
255
+ end
256
+ end
257
+
258
+ # Otherwise, plain insert
259
+ execute(sql, name)
260
+
261
+ # Don't need to look up id_value if we already have it.
262
+ # (and can't in case of non-sequence PK)
263
+ unless id_value
264
+ # If neither pk nor sequence name is given, look them up.
265
+ unless pk || sequence_name
266
+ pk, sequence_name = *pk_and_sequence_for(table)
267
+ end
268
+
269
+ # If a pk is given, fallback to default sequence name.
270
+ # Don't fetch last insert id for a table without a pk.
271
+ if pk && sequence_name ||= default_sequence_name(table, pk)
272
+ id_value = last_insert_id(table, sequence_name)
273
+ end
274
+ end
275
+ id_value
276
+ end
277
+
278
+ def columns(table_name, name=nil)
279
+ schema_name = @config[:schema_search_path]
280
+ if table_name =~ /\./
281
+ parts = table_name.split(/\./)
282
+ table_name = parts.pop
283
+ schema_name = parts.join(".")
284
+ end
285
+ @connection.columns_internal(table_name, name, schema_name)
286
+ end
287
+
288
+ # From postgresql_adapter.rb
289
+ def indexes(table_name, name = nil)
290
+ result = select_rows(<<-SQL, name)
291
+ SELECT i.relname, d.indisunique, a.attname
292
+ FROM pg_class t, pg_class i, pg_index d, pg_attribute a
293
+ WHERE i.relkind = 'i'
294
+ AND d.indexrelid = i.oid
295
+ AND d.indisprimary = 'f'
296
+ AND t.oid = d.indrelid
297
+ AND t.relname = '#{table_name}'
298
+ AND a.attrelid = t.oid
299
+ AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
300
+ OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
301
+ OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
302
+ OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
303
+ OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
304
+ ORDER BY i.relname
305
+ SQL
306
+
307
+ current_index = nil
308
+ indexes = []
309
+
310
+ result.each do |row|
311
+ if current_index != row[0]
312
+ indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", [])
313
+ current_index = row[0]
314
+ end
315
+
316
+ indexes.last.columns << row[2]
317
+ end
318
+
319
+ indexes
320
+ end
321
+
322
+ def last_insert_id(table, sequence_name)
323
+ Integer(select_value("SELECT currval('#{sequence_name}')"))
324
+ end
325
+
326
+ def recreate_database(name)
327
+ drop_database(name)
328
+ create_database(name)
329
+ end
330
+
331
+ def create_database(name, options = {})
332
+ execute "CREATE DATABASE \"#{name}\" ENCODING='#{options[:encoding] || 'utf8'}'"
333
+ end
334
+
335
+ def drop_database(name)
336
+ execute "DROP DATABASE \"#{name}\""
337
+ end
338
+
339
+ def create_schema(schema_name, pg_username)
340
+ execute("CREATE SCHEMA \"#{schema_name}\" AUTHORIZATION \"#{pg_username}\"")
341
+ end
342
+
343
+ def drop_schema(schema_name)
344
+ execute("DROP SCHEMA \"#{schema_name}\"")
345
+ end
346
+
347
+ def all_schemas
348
+ select('select nspname from pg_namespace').map {|r| r["nspname"] }
349
+ end
350
+
351
+ def primary_key(table)
352
+ pk_and_sequence = pk_and_sequence_for(table)
353
+ pk_and_sequence && pk_and_sequence.first
354
+ end
355
+
356
+ def structure_dump
357
+ database = @config[:database]
358
+ if database.nil?
359
+ if @config[:url] =~ /\/([^\/]*)$/
360
+ database = $1
361
+ else
362
+ raise "Could not figure out what database this url is for #{@config["url"]}"
363
+ end
364
+ end
365
+
366
+ ENV['PGHOST'] = @config[:host] if @config[:host]
367
+ ENV['PGPORT'] = @config[:port].to_s if @config[:port]
368
+ ENV['PGPASSWORD'] = @config[:password].to_s if @config[:password]
369
+ search_path = @config[:schema_search_path]
370
+ search_path = "--schema=#{search_path}" if search_path
371
+
372
+ @connection.connection.close
373
+ begin
374
+ definition = `pg_dump -i -U "#{@config[:username]}" -s -x -O #{search_path} #{database}`
375
+ raise "Error dumping database" if $?.exitstatus == 1
376
+
377
+ # need to patch away any references to SQL_ASCII as it breaks the JDBC driver
378
+ definition.gsub(/SQL_ASCII/, 'UNICODE')
379
+ ensure
380
+ reconnect!
381
+ end
382
+ end
383
+
384
+ def _execute(sql, name = nil)
385
+ case sql.strip
386
+ when /\A\(?\s*(select|show)/i then
387
+ @connection.execute_query(sql)
388
+ else
389
+ @connection.execute_update(sql)
390
+ end
391
+ end
392
+
393
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
394
+ #
395
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
396
+ # requires that the ORDER BY include the distinct column.
397
+ #
398
+ # distinct("posts.id", "posts.created_at desc")
399
+ def distinct(columns, order_by)
400
+ return "DISTINCT #{columns}" if order_by.blank?
401
+
402
+ # construct a clean list of column names from the ORDER BY clause, removing
403
+ # any asc/desc modifiers
404
+ order_columns = order_by.split(',').collect { |s| s.split.first }
405
+ order_columns.delete_if(&:blank?)
406
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
407
+
408
+ # return a DISTINCT ON() clause that's distinct on the columns we want but includes
409
+ # all the required columns for the ORDER BY to work properly
410
+ sql = "DISTINCT ON (#{columns}) #{columns}, "
411
+ sql << order_columns * ', '
412
+ end
413
+
414
+ # ORDER BY clause for the passed order option.
415
+ #
416
+ # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
417
+ # by wrapping the sql as a sub-select and ordering in that query.
418
+ def add_order_by_for_association_limiting!(sql, options)
419
+ return sql if options[:order].blank?
420
+
421
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
422
+ order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
423
+ order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
424
+
425
+ sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
426
+ end
427
+
428
+ def quote(value, column = nil)
429
+ return value.quoted_id if value.respond_to?(:quoted_id)
430
+
431
+ if value.kind_of?(String) && column && column.type == :binary
432
+ "'#{escape_bytea(value)}'"
433
+ elsif column && column.type == :primary_key
434
+ return value.to_s
435
+ else
436
+ super
437
+ end
438
+ end
439
+
440
+ def escape_bytea(s)
441
+ if s
442
+ result = ''
443
+ s.each_byte { |c| result << sprintf('\\\\%03o', c) }
444
+ result
445
+ end
446
+ end
447
+
448
+ def quote_column_name(name)
449
+ %("#{name}")
450
+ end
451
+
452
+ def quoted_date(value) #:nodoc:
453
+ if value.acts_like?(:time) && value.respond_to?(:usec)
454
+ "#{super}.#{sprintf("%06d", value.usec)}"
455
+ else
456
+ super
457
+ end
458
+ end
459
+
460
+ def disable_referential_integrity(&block) #:nodoc:
461
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
462
+ yield
463
+ ensure
464
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
465
+ end
466
+
467
+ def rename_table(name, new_name)
468
+ execute "ALTER TABLE #{name} RENAME TO #{new_name}"
469
+ end
470
+
471
+ def add_column(table_name, column_name, type, options = {})
472
+ execute("ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit])}")
473
+ change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
474
+ if options[:null] == false
475
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)} = '#{options[:default]}'") if options[:default]
476
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} SET NOT NULL")
477
+ end
478
+ end
479
+
480
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
481
+ begin
482
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit])}"
483
+ rescue ActiveRecord::StatementInvalid
484
+ # This is PG7, so we use a more arcane way of doing it.
485
+ begin_db_transaction
486
+ add_column(table_name, "#{column_name}_ar_tmp", type, options)
487
+ execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit])})"
488
+ remove_column(table_name, column_name)
489
+ rename_column(table_name, "#{column_name}_ar_tmp", column_name)
490
+ commit_db_transaction
491
+ end
492
+ change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
493
+ end
494
+
495
+ def change_column_default(table_name, column_name, default) #:nodoc:
496
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT '#{default}'"
497
+ end
498
+
499
+ def change_column_null(table_name, column_name, null, default = nil)
500
+ unless null || default.nil?
501
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
502
+ end
503
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
504
+ end
505
+
506
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
507
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
508
+ end
509
+
510
+ def remove_index(table_name, options) #:nodoc:
511
+ execute "DROP INDEX #{index_name(table_name, options)}"
512
+ end
513
+
514
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
515
+ return super unless type.to_s == 'integer'
516
+
517
+ if limit.nil? || limit == 4
518
+ 'integer'
519
+ elsif limit < 4
520
+ 'smallint'
521
+ else
522
+ 'bigint'
523
+ end
524
+ end
525
+
526
+ def tables
527
+ @connection.tables(database_name, nil, nil, ["TABLE"])
528
+ end
529
+ end
530
+ end
531
+