]> source.dussan.org Git - sonarqube.git/blob
2cbb54576a37c2bd84346d50c6bdf884fc2682ea
[sonarqube.git] /
1 module ::JdbcSpec
2   # Don't need to load native postgres adapter
3   $LOADED_FEATURES << "active_record/connection_adapters/postgresql_adapter.rb"
4
5   module ActiveRecordExtensions
6     def postgresql_connection(config)
7       config[:host] ||= "localhost"
8       config[:port] ||= 5432
9       config[:url] ||= "jdbc:postgresql://#{config[:host]}:#{config[:port]}/#{config[:database]}"
10       config[:url] << config[:pg_params] if config[:pg_params]
11       config[:driver] ||= "org.postgresql.Driver"
12       jdbc_connection(config)
13     end
14   end
15
16   module PostgreSQL
17     def self.column_selector
18       [/postgre/i, lambda {|cfg,col| col.extend(::JdbcSpec::PostgreSQL::Column)}]
19     end
20
21     def self.adapter_selector
22       [/postgre/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::PostgreSQL)}]
23     end
24
25     module Column
26       def type_cast(value)
27         case type
28         when :boolean then cast_to_boolean(value)
29         else super
30         end
31       end
32
33       def simplified_type(field_type)
34         return :integer if field_type =~ /^serial/i
35         return :string if field_type =~ /\[\]$/i || field_type =~ /^interval/i
36         return :string if field_type =~ /^(?:point|lseg|box|"?path"?|polygon|circle)/i
37         return :datetime if field_type =~ /^timestamp/i
38         return :float if field_type =~ /^real|^money/i
39         return :binary if field_type =~ /^bytea/i
40         return :boolean if field_type =~ /^bool/i
41         super
42       end
43
44       def cast_to_boolean(value)
45         if value == true || value == false
46           value
47         else
48           %w(true t 1).include?(value.to_s.downcase)
49         end
50       end
51
52       def cast_to_date_or_time(value)
53         return value if value.is_a? Date
54         return nil if value.blank?
55         guess_date_or_time((value.is_a? Time) ? value : cast_to_time(value))
56       end
57
58       def cast_to_time(value)
59         return value if value.is_a? Time
60         time_array = ParseDate.parsedate value
61         time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
62         Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
63       end
64
65       def guess_date_or_time(value)
66         (value.hour == 0 and value.min == 0 and value.sec == 0) ?
67         Date.new(value.year, value.month, value.day) : value
68       end
69
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 default_sequence_name(table_name, pk = nil)
99       default_pk, default_seq = pk_and_sequence_for(table_name)
100       default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
101     end
102
103     # Resets sequence to the max value of the table's pk if present.
104     def reset_pk_sequence!(table, pk = nil, sequence = nil)
105       unless pk and sequence
106         default_pk, default_sequence = pk_and_sequence_for(table)
107         pk ||= default_pk
108         sequence ||= default_sequence
109       end
110       if pk
111         if sequence
112           select_value <<-end_sql, 'Reset sequence'
113             SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
114           end_sql
115         else
116           @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
117         end
118       end
119     end
120
121     # Find a table's primary key and sequence.
122     def pk_and_sequence_for(table)
123       # First try looking for a sequence with a dependency on the
124       # given table's primary key.
125         result = select(<<-end_sql, 'PK and serial sequence')[0]
126           SELECT attr.attname AS nm, name.nspname AS nsp, seq.relname AS rel
127           FROM pg_class      seq,
128                pg_attribute  attr,
129                pg_depend     dep,
130                pg_namespace  name,
131                pg_constraint cons
132           WHERE seq.oid           = dep.objid
133             AND seq.relnamespace  = name.oid
134             AND seq.relkind       = 'S'
135             AND attr.attrelid     = dep.refobjid
136             AND attr.attnum       = dep.refobjsubid
137             AND attr.attrelid     = cons.conrelid
138             AND attr.attnum       = cons.conkey[1]
139             AND cons.contype      = 'p'
140             AND dep.refobjid      = '#{table}'::regclass
141         end_sql
142
143         if result.nil? or result.empty?
144           # If that fails, try parsing the primary key's default value.
145           # Support the 7.x and 8.0 nextval('foo'::text) as well as
146           # the 8.1+ nextval('foo'::regclass).
147           # TODO: assumes sequence is in same schema as table.
148           result = select(<<-end_sql, 'PK and custom sequence')[0]
149             SELECT attr.attname AS nm, name.nspname AS nsp, split_part(def.adsrc, '\\\'', 2) AS rel
150             FROM pg_class       t
151             JOIN pg_namespace   name ON (t.relnamespace = name.oid)
152             JOIN pg_attribute   attr ON (t.oid = attrelid)
153             JOIN pg_attrdef     def  ON (adrelid = attrelid AND adnum = attnum)
154             JOIN pg_constraint  cons ON (conrelid = adrelid AND adnum = conkey[1])
155             WHERE t.oid = '#{table}'::regclass
156               AND cons.contype = 'p'
157               AND def.adsrc ~* 'nextval'
158           end_sql
159         end
160         # check for existence of . in sequence name as in public.foo_sequence.  if it does not exist, join the current namespace
161         result['rel']['.'] ? [result['nm'], result['rel']] : [result['nm'], "#{result['nsp']}.#{result['rel']}"]
162       rescue
163         nil
164       end
165
166     def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
167       execute(sql, name)
168       table = sql.split(" ", 4)[2]
169       id_value || pk && last_insert_id(table, sequence_name || default_sequence_name(table, pk))
170     end
171
172     def columns(table_name, name=nil)
173       schema_name = "public"
174       if table_name =~ /\./
175         parts = table_name.split(/\./)
176         table_name = parts.pop
177         schema_name = parts.join(".")
178       end
179       @connection.columns_internal(table_name, name, schema_name)
180     end
181
182     # From postgresql_adapter.rb
183     def indexes(table_name, name = nil)
184       result = select_rows(<<-SQL, name)
185         SELECT i.relname, d.indisunique, a.attname
186           FROM pg_class t, pg_class i, pg_index d, pg_attribute a
187          WHERE i.relkind = 'i'
188            AND d.indexrelid = i.oid
189            AND d.indisprimary = 'f'
190            AND t.oid = d.indrelid
191            AND t.relname = '#{table_name}'
192            AND a.attrelid = t.oid
193            AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
194               OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
195               OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
196               OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
197               OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
198         ORDER BY i.relname
199       SQL
200
201       current_index = nil
202       indexes = []
203
204       result.each do |row|
205         if current_index != row[0]
206           indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, row[0], row[1] == "t", [])
207           current_index = row[0]
208         end
209
210         indexes.last.columns << row[2]
211       end
212
213       indexes
214     end
215
216     def last_insert_id(table, sequence_name)
217       Integer(select_value("SELECT currval('#{sequence_name}')"))
218     end
219
220     def recreate_database(name)
221       drop_database(name)
222       create_database(name)
223     end
224
225     def create_database(name, options = {})
226       execute "CREATE DATABASE \"#{name}\" ENCODING='#{options[:encoding] || 'utf8'}'"
227     end
228
229     def drop_database(name)
230       execute "DROP DATABASE \"#{name}\""
231     end
232
233     def structure_dump
234       database = @config[:database]
235       if database.nil?
236         if @config[:url] =~ /\/([^\/]*)$/
237           database = $1
238         else
239           raise "Could not figure out what database this url is for #{@config["url"]}"
240         end
241       end
242
243       ENV['PGHOST']     = @config[:host] if @config[:host]
244       ENV['PGPORT']     = @config[:port].to_s if @config[:port]
245       ENV['PGPASSWORD'] = @config[:password].to_s if @config[:password]
246       search_path = @config[:schema_search_path]
247       search_path = "--schema=#{search_path}" if search_path
248
249       @connection.connection.close
250       begin
251         file = "db/#{RAILS_ENV}_structure.sql"
252         `pg_dump -i -U "#{@config[:username]}" -s -x -O -f #{file} #{search_path} #{database}`
253         raise "Error dumping database" if $?.exitstatus == 1
254
255         # need to patch away any references to SQL_ASCII as it breaks the JDBC driver
256         lines = File.readlines(file)
257         File.open(file, "w") do |io|
258           lines.each do |line|
259             line.gsub!(/SQL_ASCII/, 'UNICODE')
260             io.write(line)
261           end
262         end
263       ensure
264         reconnect!
265       end
266     end
267
268     def _execute(sql, name = nil)
269         case sql.strip
270         when /\A\(?\s*(select|show)/i:
271           @connection.execute_query(sql)
272         else
273           @connection.execute_update(sql)
274         end
275     end
276
277     # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
278     #
279     # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
280     # requires that the ORDER BY include the distinct column.
281     #
282     #   distinct("posts.id", "posts.created_at desc")
283     def distinct(columns, order_by)
284       return "DISTINCT #{columns}" if order_by.blank?
285
286       # construct a clean list of column names from the ORDER BY clause, removing
287       # any asc/desc modifiers
288       order_columns = order_by.split(',').collect { |s| s.split.first }
289       order_columns.delete_if(&:blank?)
290       order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
291
292       # return a DISTINCT ON() clause that's distinct on the columns we want but includes
293       # all the required columns for the ORDER BY to work properly
294       sql = "DISTINCT ON (#{columns}) #{columns}, "
295       sql << order_columns * ', '
296     end
297
298     # ORDER BY clause for the passed order option.
299     #
300     # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
301     # by wrapping the sql as a sub-select and ordering in that query.
302     def add_order_by_for_association_limiting!(sql, options)
303       return sql if options[:order].blank?
304
305       order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
306       order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
307       order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
308
309       sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
310     end
311
312     def quote(value, column = nil)
313       return value.quoted_id if value.respond_to?(:quoted_id)
314
315       if value.kind_of?(String) && column && column.type == :binary
316         "'#{escape_bytea(value)}'"
317       elsif column && column.type == :primary_key
318         return value.to_s
319       else
320         super
321       end
322     end
323
324     def escape_bytea(s)
325       if s
326         result = ''
327         s.each_byte { |c| result << sprintf('\\\\%03o', c) }
328         result
329       end
330     end
331
332     def quote_column_name(name)
333       %("#{name}")
334     end
335
336     def quoted_date(value)
337       value.strftime("%Y-%m-%d %H:%M:%S")
338     end
339
340     def disable_referential_integrity(&block) #:nodoc:
341       execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
342       yield
343     ensure
344       execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
345     end
346
347     def rename_table(name, new_name)
348       execute "ALTER TABLE #{name} RENAME TO #{new_name}"
349     end
350
351     def add_column(table_name, column_name, type, options = {})
352       execute("ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}")
353       change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
354       if options[:null] == false
355         execute("UPDATE #{table_name} SET #{column_name} = '#{options[:default]}'") if options[:default]
356         execute("ALTER TABLE #{table_name} ALTER #{column_name} SET NOT NULL")
357       end
358     end
359
360     def change_column(table_name, column_name, type, options = {}) #:nodoc:
361       begin
362         execute "ALTER TABLE #{table_name} ALTER  #{column_name} TYPE #{type_to_sql(type, options[:limit])}"
363       rescue ActiveRecord::StatementInvalid
364         # This is PG7, so we use a more arcane way of doing it.
365         begin_db_transaction
366         add_column(table_name, "#{column_name}_ar_tmp", type, options)
367         execute "UPDATE #{table_name} SET #{column_name}_ar_tmp = CAST(#{column_name} AS #{type_to_sql(type, options[:limit])})"
368         remove_column(table_name, column_name)
369         rename_column(table_name, "#{column_name}_ar_tmp", column_name)
370         commit_db_transaction
371       end
372       change_column_default(table_name, column_name, options[:default]) unless options[:default].nil?
373     end
374
375     def change_column_default(table_name, column_name, default) #:nodoc:
376       execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT '#{default}'"
377     end
378
379     def rename_column(table_name, column_name, new_column_name) #:nodoc:
380       execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
381     end
382
383     def remove_index(table_name, options) #:nodoc:
384       execute "DROP INDEX #{index_name(table_name, options)}"
385     end
386
387     def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
388       return super unless type.to_s == 'integer'
389
390       if limit.nil? || limit == 4
391         'integer'
392       elsif limit < 4
393         'smallint'
394       else
395         'bigint'
396       end
397     end
398
399     def tables
400       @connection.tables(database_name, nil, nil, ["TABLE"])
401     end
402   end
403 end