]> source.dussan.org Git - sonarqube.git/blob
0f2a25c9f10584648a379270cea63c4110461f67
[sonarqube.git] /
1 require 'arjdbc/jdbc/missing_functionality_helper'
2
3 module ::ArJdbc
4   module Derby
5     def self.column_selector
6       [/derby/i, lambda {|cfg,col| col.extend(::ArJdbc::Derby::Column)}]
7     end
8
9     def self.monkey_rails
10       unless @already_monkeyd
11         # Needed because Rails is broken wrt to quoting of
12         # some values. Most databases are nice about it,
13         # but not Derby. The real issue is that you can't
14         # compare a CHAR value to a NUMBER column.
15         ::ActiveRecord::Associations::ClassMethods.module_eval do
16           private
17
18           def select_limited_ids_list(options, join_dependency)
19             connection.select_all(
20                                   construct_finder_sql_for_association_limiting(options, join_dependency),
21                                   "#{name} Load IDs For Limited Eager Loading"
22                                   ).collect { |row| connection.quote(row[primary_key], columns_hash[primary_key]) }.join(", ")
23           end
24         end
25
26         @already_monkeyd = true
27       end
28     end
29
30     def self.extended(*args)
31       monkey_rails
32     end
33
34     def self.included(*args)
35       monkey_rails
36     end
37
38     module Column
39       def simplified_type(field_type)
40         case field_type
41         when /smallint/i  then :boolean
42         when /real/i      then :float
43         when /decimal/i   then :decimal
44         else
45           super
46         end
47       end
48
49       # Post process default value from JDBC into a Rails-friendly format (columns{-internal})
50       def default_value(value)
51         # jdbc returns column default strings with actual single quotes around the value.
52         return $1 if value =~ /^'(.*)'$/
53
54         value
55       end
56     end
57
58     def adapter_name #:nodoc:
59       'Derby'
60     end
61
62     def arel2_visitors
63       require 'arel/visitors/derby'
64       {'derby' => ::Arel::Visitors::Derby, 'jdbcderby' => ::Arel::Visitors::Derby}
65     end
66
67     include ArJdbc::MissingFunctionalityHelper
68
69     def index_name_length
70       128
71     end
72
73     # Convert the specified column type to a SQL string.
74     # In Derby, the following cannot specify a limit:
75     # - integer
76     # - boolean (smallint)
77     # - timestamp
78     # - date
79     def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
80       return super unless [:integer, :boolean, :timestamp, :date].include? type
81
82       native = native_database_types[type.to_s.downcase.to_sym]
83       native.is_a?(Hash) ? native[:name] : native
84     end
85
86     def modify_types(tp)
87       tp[:primary_key] = "int generated by default as identity NOT NULL PRIMARY KEY"
88       tp[:string][:limit] = 256
89       tp[:integer][:limit] = nil
90       tp[:boolean] = {:name => "smallint"}
91       tp[:timestamp][:limit] = nil
92       tp[:date][:limit] = nil
93
94       # sonar
95       # New type
96       tp[:big_integer] = {:name => "bigint"}
97       # /sonar
98
99       tp
100     end
101
102     # Override default -- fix case where ActiveRecord passes :default => nil, :null => true
103     def add_column_options!(sql, options)
104       options.delete(:default) if options.has_key?(:default) && options[:default].nil?
105       sql << " DEFAULT #{quote(options.delete(:default))}" if options.has_key?(:default)
106       super
107     end
108
109     def classes_for_table_name(table)
110       ActiveRecord::Base.send(:subclasses).select {|klass| klass.table_name == table}
111     end
112
113     # Set the sequence to the max value of the table's column.
114     def reset_sequence!(table, column, sequence = nil)
115       mpk = select_value("SELECT MAX(#{quote_column_name(column)}) FROM #{quote_table_name(table)}")
116       execute("ALTER TABLE #{quote_table_name(table)} ALTER COLUMN #{quote_column_name(column)} RESTART WITH #{mpk.to_i + 1}")
117     end
118
119     def reset_pk_sequence!(table, pk = nil, sequence = nil)
120       klasses = classes_for_table_name(table)
121       klass   = klasses.nil? ? nil : klasses.first
122       pk      = klass.primary_key unless klass.nil?
123       if pk && klass.columns_hash[pk].type == :integer
124         reset_sequence!(klass.table_name, pk)
125       end
126     end
127
128     def remove_index(table_name, options) #:nodoc:
129       execute "DROP INDEX #{index_name(table_name, options)}"
130     end
131
132     def rename_table(name, new_name)
133       execute "RENAME TABLE #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
134     end
135
136     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'"
137
138     def add_quotes(name)
139       return name unless name
140       %Q{"#{name}"}
141     end
142
143     def strip_quotes(str)
144       return str unless str
145       return str unless /^(["']).*\1$/ =~ str
146       str[1..-2]
147     end
148
149     def expand_double_quotes(name)
150       return name unless name && name['"']
151       name.gsub(/"/,'""')
152     end
153
154     def auto_increment_stmt(tname, cname)
155       stmt = AUTO_INC_STMT2 % [tname, strip_quotes(cname)]
156       data = execute(stmt).first
157       if data
158         start = data['autoincrementstart']
159         if start
160           coldef = ""
161           coldef << " GENERATED " << (data['columndefault'].nil? ? "ALWAYS" : "BY DEFAULT ")
162           coldef << "AS IDENTITY (START WITH "
163           coldef << start
164           coldef << ", INCREMENT BY "
165           coldef << data['autoincrementinc']
166           coldef << ")"
167           return coldef
168         end
169       end
170       ""
171     end
172
173
174     def add_column(table_name, column_name, type, options = {})
175       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])}"
176       add_column_options!(add_column_sql, options)
177       execute(add_column_sql)
178     end
179
180     def execute(sql, name = nil)
181       if sql =~ /\A\s*(UPDATE|INSERT)/i
182         i = sql =~ /\swhere\s/im
183         if i
184           sql[i..-1] = sql[i..-1].gsub(/!=\s*NULL/, 'IS NOT NULL').gsub(/=\sNULL/i, 'IS NULL')
185         end
186       else
187         sql.gsub!(/= NULL/i, 'IS NULL')
188       end
189       super
190     end
191
192     # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
193     #
194     # Derby requires the ORDER BY columns in the select list for distinct queries, and
195     # requires that the ORDER BY include the distinct column.
196     #
197     #   distinct("posts.id", "posts.created_at desc")
198     #
199     # Based on distinct method for PostgreSQL Adapter
200     def distinct(columns, order_by)
201       return "DISTINCT #{columns}" if order_by.blank?
202
203       # construct a clean list of column names from the ORDER BY clause, removing
204       # any asc/desc modifiers
205       order_columns = order_by.split(',').collect { |s| s.split.first }
206       order_columns.delete_if(&:blank?)
207       order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
208
209       # return a DISTINCT clause that's distinct on the columns we want but includes
210       # all the required columns for the ORDER BY to work properly
211       sql = "DISTINCT #{columns}, #{order_columns * ', '}"
212       sql
213     end
214
215     SIZEABLE = %w(VARCHAR CLOB BLOB)
216
217     def structure_dump #:nodoc:
218       definition=""
219       rs = @connection.connection.meta_data.getTables(nil,nil,nil,["TABLE"].to_java(:string))
220       while rs.next
221         tname = rs.getString(3)
222         definition << "CREATE TABLE #{tname} (\n"
223         rs2 = @connection.connection.meta_data.getColumns(nil,nil,tname,nil)
224         first_col = true
225         while rs2.next
226           col_name = add_quotes(rs2.getString(4));
227           default = ""
228           d1 = rs2.getString(13)
229           if d1 =~ /^GENERATED_/
230             default = auto_increment_stmt(tname, col_name)
231           elsif d1
232             default = " DEFAULT #{d1}"
233           end
234
235           type = rs2.getString(6)
236           col_size = rs2.getString(7)
237           nulling = (rs2.getString(18) == 'NO' ? " NOT NULL" : "")
238           create_col_string = add_quotes(expand_double_quotes(strip_quotes(col_name))) +
239             " " +
240             type +
241             (SIZEABLE.include?(type) ? "(#{col_size})" : "") +
242             nulling +
243             default
244           if !first_col
245             create_col_string = ",\n #{create_col_string}"
246           else
247             create_col_string = " #{create_col_string}"
248           end
249
250           definition << create_col_string
251
252           first_col = false
253         end
254         definition << ");\n\n"
255       end
256       definition
257     end
258
259     def remove_column(table_name, column_name)
260       execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)} RESTRICT"
261     end
262
263     # Notes about changing in Derby:
264     #    http://db.apache.org/derby/docs/10.2/ref/rrefsqlj81859.html#rrefsqlj81859__rrefsqlj37860)
265     #
266     # We support changing columns using the strategy outlined in:
267     #    https://issues.apache.org/jira/browse/DERBY-1515
268     #
269     # This feature has not made it into a formal release and is not in Java 6.  We will
270     # need to conditionally support this somehow (supposed to arrive for 10.3.0.0)
271     def change_column(table_name, column_name, type, options = {})
272       # null/not nulling is easy, handle that separately
273       if options.include?(:null)
274         # This seems to only work with 10.2 of Derby
275         if options.delete(:null) == false
276           execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} NOT NULL"
277         else
278           execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} NULL"
279         end
280       end
281
282       # anything left to do?
283       unless options.empty?
284         begin
285           execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DATA TYPE #{type_to_sql(type, options[:limit])}"
286         rescue
287           transaction do
288             temp_new_column_name = "#{column_name}_newtype"
289             # 1) ALTER TABLE t ADD COLUMN c1_newtype NEWTYPE;
290             add_column table_name, temp_new_column_name, type, options
291             # 2) UPDATE t SET c1_newtype = c1;
292             execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(temp_new_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit])})"
293             # 3) ALTER TABLE t DROP COLUMN c1;
294             remove_column table_name, column_name
295             # 4) ALTER TABLE t RENAME COLUMN c1_newtype to c1;
296             rename_column table_name, temp_new_column_name, column_name
297           end
298         end
299       end
300     end
301
302     def rename_column(table_name, column_name, new_column_name) #:nodoc:
303       execute "RENAME COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
304     end
305
306     def primary_keys(table_name)
307       @connection.primary_keys table_name.to_s.upcase
308     end
309
310     def columns(table_name, name=nil)
311       @connection.columns_internal(table_name.to_s, name, derby_schema)
312     end
313
314     def tables
315       @connection.tables(nil, derby_schema)
316     end
317
318     def recreate_database(db_name)
319       tables.each do |t|
320         drop_table t
321       end
322     end
323
324     def quote_column_name(name) #:nodoc:
325       %Q{"#{name.to_s.upcase.gsub(/"/, '""')}"}
326     end
327
328     def quoted_true
329       '1'
330     end
331
332     def quoted_false
333       '0'
334     end
335
336     def add_limit_offset!(sql, options) #:nodoc:
337       if options[:offset]
338         sql << " OFFSET #{options[:offset]} ROWS"
339       end
340       if options[:limit]
341         #ROWS/ROW and FIRST/NEXT mean the same
342         sql << " FETCH FIRST #{options[:limit]} ROWS ONLY"
343       end
344     end
345
346     private
347     # Derby appears to define schemas using the username
348     def derby_schema
349       if @config.has_key?(:schema)
350         config[:schema]
351       else
352         (@config[:username] && @config[:username].to_s) || ''
353       end
354     end
355   end
356 end
357
358