]> source.dussan.org Git - sonarqube.git/blob
56b31eda89605bc17b5d70f0613a5c915e6c1be0
[sonarqube.git] /
1 require 'jdbc_adapter/missing_functionality_helper'
2
3 module ::JdbcSpec
4   module ActiveRecordExtensions
5     def derby_connection(config)
6       config[:url] ||= "jdbc:derby:#{config[:database]};create=true"
7       config[:driver] ||= "org.apache.derby.jdbc.EmbeddedDriver"
8       embedded_driver(config)
9     end
10   end
11
12   module Derby
13     def self.column_selector
14       [/derby/i, lambda {|cfg,col| col.extend(::JdbcSpec::Derby::Column)}]
15     end
16
17     def self.adapter_selector
18       [/derby/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::Derby)}]
19     end
20
21     def self.monkey_rails
22       unless @already_monkeyd
23         # Needed because Rails is broken wrt to quoting of
24         # some values. Most databases are nice about it,
25         # but not Derby. The real issue is that you can't
26         # compare a CHAR value to a NUMBER column.
27         ::ActiveRecord::Associations::ClassMethods.module_eval do
28           private
29
30           def select_limited_ids_list(options, join_dependency)
31             connection.select_all(
32                                   construct_finder_sql_for_association_limiting(options, join_dependency),
33                                   "#{name} Load IDs For Limited Eager Loading"
34                                   ).collect { |row| connection.quote(row[primary_key], columns_hash[primary_key]) }.join(", ")
35           end
36         end
37
38         @already_monkeyd = true
39       end
40     end
41
42     def self.extended(*args)
43       monkey_rails
44     end
45
46     def self.included(*args)
47       monkey_rails
48     end
49
50     module Column
51       def value_to_binary(value)
52         value.scan(/[0-9A-Fa-f]{2}/).collect {|v| v.to_i(16)}.pack("C*")
53       end
54
55       def cast_to_date_or_time(value)
56         return value if value.is_a? Date
57         return nil if value.blank?
58         guess_date_or_time((value.is_a? Time) ? value : cast_to_time(value))
59       end
60
61       def cast_to_time(value)
62         return value if value.is_a? Time
63         time_array = ParseDate.parsedate value
64         time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
65         Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
66       end
67
68       def guess_date_or_time(value)
69         (value.hour == 0 and value.min == 0 and value.sec == 0) ?
70         Date.new(value.year, value.month, value.day) : value
71       end
72
73       def simplified_type(field_type)
74         return :boolean if field_type =~ /smallint/i
75         return :float if field_type =~ /real/i
76         super
77       end
78     end
79
80     include JdbcSpec::MissingFunctionalityHelper
81
82     def modify_types(tp)
83       tp[:primary_key] = "int generated by default as identity NOT NULL PRIMARY KEY"
84       tp[:integer][:limit] = nil
85       tp[:string][:limit] = 256
86       tp[:boolean] = {:name => "smallint"}
87       tp
88     end
89
90     # Override default -- fix case where ActiveRecord passes :default => nil, :null => true
91     def add_column_options!(sql, options)
92       options.delete(:default) if options.has_key?(:default) && options[:default].nil?
93       options.delete(:null) if options.has_key?(:null) && (options[:null].nil? || options[:null] == true)
94       super
95     end
96
97     def classes_for_table_name(table)
98       ActiveRecord::Base.send(:subclasses).select {|klass| klass.table_name == table}
99     end
100
101     # Set the sequence to the max value of the table's column.
102     def reset_sequence!(table, column, sequence = nil)
103       mpk = select_value("SELECT MAX(#{quote_column_name column}) FROM #{table}")
104       execute("ALTER TABLE #{table} ALTER COLUMN #{quote_column_name column} RESTART WITH #{mpk.to_i + 1}")
105     end
106
107     def reset_pk_sequence!(table, pk = nil, sequence = nil)
108       klasses = classes_for_table_name(table)
109       klass   = klasses.nil? ? nil : klasses.first
110       pk      = klass.primary_key unless klass.nil?
111       if pk && klass.columns_hash[pk].type == :integer
112         reset_sequence!(klass.table_name, pk)
113       end
114     end
115
116     def primary_key(table_name) #:nodoc:
117       primary_keys(table_name).first
118     end
119
120     def remove_index(table_name, options) #:nodoc:
121       execute "DROP INDEX #{index_name(table_name, options)}"
122     end
123
124     def rename_table(name, new_name)
125       execute "RENAME TABLE #{name} TO #{new_name}"
126     end
127
128     COLUMN_INFO_STMT = "SELECT C.COLUMNNAME, C.REFERENCEID, C.COLUMNNUMBER FROM SYS.SYSCOLUMNS C, SYS.SYSTABLES T WHERE T.TABLEID = '%s' AND T.TABLEID = C.REFERENCEID ORDER BY C.COLUMNNUMBER"
129
130     COLUMN_TYPE_STMT = "SELECT COLUMNDATATYPE, COLUMNDEFAULT FROM SYS.SYSCOLUMNS WHERE REFERENCEID = '%s' AND COLUMNNAME = '%s'"
131
132     AUTO_INC_STMT = "SELECT AUTOINCREMENTSTART, AUTOINCREMENTINC, COLUMNNAME, REFERENCEID, COLUMNDEFAULT FROM SYS.SYSCOLUMNS WHERE REFERENCEID = '%s' AND COLUMNNAME = '%s'"
133     AUTO_INC_STMT2 = "SELECT AUTOINCREMENTSTART, AUTOINCREMENTINC, COLUMNNAME, REFERENCEID, COLUMNDEFAULT FROM SYS.SYSCOLUMNS WHERE REFERENCEID = (SELECT T.TABLEID FROM SYS.SYSTABLES T WHERE T.TABLENAME = '%s') AND COLUMNNAME = '%s'"
134
135     def add_quotes(name)
136       return name unless name
137       %Q{"#{name}"}
138     end
139
140     def strip_quotes(str)
141       return str unless str
142       return str unless /^(["']).*\1$/ =~ str
143       str[1..-2]
144     end
145
146     def expand_double_quotes(name)
147       return name unless name && name['"']
148       name.gsub(/"/,'""')
149     end
150
151     def reinstate_auto_increment(name, refid, coldef)
152       stmt = AUTO_INC_STMT % [refid, strip_quotes(name)]
153       data = execute(stmt).first
154       if data
155         start = data['autoincrementstart']
156         if start
157           coldef << " GENERATED " << (data['columndefault'].nil? ? "ALWAYS" : "BY DEFAULT ")
158           coldef << "AS IDENTITY (START WITH "
159           coldef << start
160           coldef << ", INCREMENT BY "
161           coldef << data['autoincrementinc']
162           coldef << ")"
163           return true
164         end
165       end
166       false
167     end
168
169     def reinstate_auto_increment(name, refid, coldef)
170       stmt = AUTO_INC_STMT % [refid, strip_quotes(name)]
171       data = execute(stmt).first
172       if data
173         start = data['autoincrementstart']
174         if start
175           coldef << " GENERATED " << (data['columndefault'].nil? ? "ALWAYS" : "BY DEFAULT ")
176           coldef << "AS IDENTITY (START WITH "
177           coldef << start
178           coldef << ", INCREMENT BY "
179           coldef << data['autoincrementinc']
180           coldef << ")"
181           return true
182         end
183       end
184       false
185     end
186
187     def auto_increment_stmt(tname, cname)
188       stmt = AUTO_INC_STMT2 % [tname, strip_quotes(cname)]
189       data = execute(stmt).first
190       if data
191         start = data['autoincrementstart']
192         if start
193           coldef = ""
194           coldef << " GENERATED " << (data['columndefault'].nil? ? "ALWAYS" : "BY DEFAULT ")
195           coldef << "AS IDENTITY (START WITH "
196           coldef << start
197           coldef << ", INCREMENT BY "
198           coldef << data['autoincrementinc']
199           coldef << ")"
200           return coldef
201         end
202       end
203       ""
204     end
205
206
207     def add_column(table_name, column_name, type, options = {})
208       if option_not_null = options[:null] == false
209         option_not_null = options.delete(:null)
210       end
211       add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
212       add_column_options!(add_column_sql, options)
213       execute(add_column_sql)
214       if option_not_null
215         alter_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} NOT NULL"
216       end
217     end
218
219     # I don't think this method is ever called ??? (stepheneb)
220     def create_column(name, refid, colno)
221       stmt = COLUMN_TYPE_STMT % [refid, strip_quotes(name)]
222       coldef = ""
223       data = execute(stmt).first
224       if data
225         coldef << add_quotes(expand_double_quotes(strip_quotes(name)))
226         coldef << " "
227         coldef << data['columndatatype']
228         if !reinstate_auto_increment(name, refid, coldef) && data['columndefault']
229           coldef << " DEFAULT " << data['columndefault']
230         end
231       end
232       coldef
233     end
234
235     SIZEABLE = %w(VARCHAR CLOB BLOB)
236
237     def structure_dump #:nodoc:
238       definition=""
239       rs = @connection.connection.meta_data.getTables(nil,nil,nil,["TABLE"].to_java(:string))
240       while rs.next
241         tname = rs.getString(3)
242         definition << "CREATE TABLE #{tname} (\n"
243         rs2 = @connection.connection.meta_data.getColumns(nil,nil,tname,nil)
244         first_col = true
245         while rs2.next
246           col_name = add_quotes(rs2.getString(4));
247           default = ""
248           d1 = rs2.getString(13)
249           if d1 =~ /^GENERATED_/
250             default = auto_increment_stmt(tname, col_name)
251           elsif d1
252             default = " DEFAULT #{d1}"
253           end
254
255           type = rs2.getString(6)
256           col_size = rs2.getString(7)
257           nulling = (rs2.getString(18) == 'NO' ? " NOT NULL" : "")
258           create_col_string = add_quotes(expand_double_quotes(strip_quotes(col_name))) +
259             " " +
260             type +
261             (SIZEABLE.include?(type) ? "(#{col_size})" : "") +
262             nulling +
263             default
264           if !first_col
265             create_col_string = ",\n #{create_col_string}"
266           else
267             create_col_string = " #{create_col_string}"
268           end
269
270           definition << create_col_string
271
272           first_col = false
273         end
274         definition << ");\n\n"
275       end
276       definition
277     end
278
279     # Support for removing columns added via derby bug issue:
280     # https://issues.apache.org/jira/browse/DERBY-1489
281     #
282     # This feature has not made it into a formal release and is not in Java 6.
283     # If the normal strategy fails we fall back on a strategy by creating a new
284     # table without the new column and there after moving the data to the new
285     #
286     def remove_column(table_name, column_name)
287       begin
288         execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name} RESTRICT"
289       rescue
290         alter_table(table_name) do |definition|
291           definition.columns.delete(definition[column_name])
292         end
293       end
294     end
295
296     # Notes about changing in Derby:
297     #    http://db.apache.org/derby/docs/10.2/ref/rrefsqlj81859.html#rrefsqlj81859__rrefsqlj37860)
298     #
299     # We support changing columns using the strategy outlined in:
300     #    https://issues.apache.org/jira/browse/DERBY-1515
301     #
302     # This feature has not made it into a formal release and is not in Java 6.  We will
303     # need to conditionally support this somehow (supposed to arrive for 10.3.0.0)
304     def change_column(table_name, column_name, type, options = {})
305       # null/not nulling is easy, handle that separately
306       if options.include?(:null)
307         # This seems to only work with 10.2 of Derby
308         if options.delete(:null) == false
309           execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} NOT NULL"
310         else
311           execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} NULL"
312         end
313       end
314
315       # anything left to do?
316       unless options.empty?
317         begin
318           execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DATA TYPE #{type_to_sql(type, options[:limit])}"
319         rescue
320           transaction do
321             temp_new_column_name = "#{column_name}_newtype"
322             # 1) ALTER TABLE t ADD COLUMN c1_newtype NEWTYPE;
323             add_column table_name, temp_new_column_name, type, options
324             # 2) UPDATE t SET c1_newtype = c1;
325             execute "UPDATE #{table_name} SET #{temp_new_column_name} = CAST(#{column_name} AS #{type_to_sql(type, options[:limit])})"
326             # 3) ALTER TABLE t DROP COLUMN c1;
327             remove_column table_name, column_name
328             # 4) ALTER TABLE t RENAME COLUMN c1_newtype to c1;
329             rename_column table_name, temp_new_column_name, column_name
330           end
331         end
332       end
333     end
334
335     # Support for renaming columns:
336     # https://issues.apache.org/jira/browse/DERBY-1490
337     #
338     # This feature is expect to arrive in version 10.3.0.0:
339     # http://wiki.apache.org/db-derby/DerbyTenThreeRelease)
340     #
341     def rename_column(table_name, column_name, new_column_name) #:nodoc:
342       begin
343         execute "ALTER TABLE #{table_name} ALTER RENAME COLUMN #{column_name} TO #{new_column_name}"
344       rescue
345         alter_table(table_name, :rename => {column_name => new_column_name})
346       end
347     end
348
349     def primary_keys(table_name)
350       @connection.primary_keys table_name.to_s.upcase
351     end
352
353     def recreate_database(db_name)
354       tables.each do |t|
355         drop_table t
356       end
357     end
358
359     # For DDL it appears you can quote "" column names, but in queries (like insert it errors out?)
360     def quote_column_name(name) #:nodoc:
361       name = name.to_s
362       if /^references$/i =~ name
363         %Q{"#{name.upcase}"}
364       elsif /[A-Z]/ =~ name && /[a-z]/ =~ name
365         %Q{"#{name}"}
366       elsif name =~ /\s/
367         %Q{"#{name.upcase}"}
368       elsif name =~ /^[_\d]/
369         %Q{"#{name.upcase}"}
370       else
371         name
372       end
373     end
374
375     def quoted_true
376       '1'
377     end
378
379     def quoted_false
380       '0'
381     end
382   end
383 end
384