1 require 'arjdbc/jdbc/missing_functionality_helper'
5 def self.column_selector
6 [/derby/i, lambda {|cfg,col| col.extend(::ArJdbc::Derby::Column)}]
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
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(", ")
26 @already_monkeyd = true
30 def self.extended(*args)
34 def self.included(*args)
39 def simplified_type(field_type)
41 when /smallint/i then :boolean
42 when /real/i then :float
43 when /decimal/i then :decimal
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 =~ /^'(.*)'$/
58 def adapter_name #:nodoc:
63 require 'arel/visitors/derby'
64 {'derby' => ::Arel::Visitors::Derby, 'jdbcderby' => ::Arel::Visitors::Derby}
67 include ArJdbc::MissingFunctionalityHelper
73 # Convert the specified column type to a SQL string.
74 # In Derby, the following cannot specify a limit:
76 # - boolean (smallint)
79 def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
80 return super unless [:integer, :boolean, :timestamp, :date].include? type
82 native = native_database_types[type.to_s.downcase.to_sym]
83 native.is_a?(Hash) ? native[:name] : native
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
96 tp[:big_integer] = {:name => "bigint"}
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)
109 def classes_for_table_name(table)
110 ActiveRecord::Base.send(:subclasses).select {|klass| klass.table_name == table}
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}")
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)
128 def remove_index(table_name, options) #:nodoc:
129 execute "DROP INDEX #{index_name(table_name, options)}"
132 def rename_table(name, new_name)
133 execute "RENAME TABLE #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
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'"
139 return name unless name
143 def strip_quotes(str)
144 return str unless str
145 return str unless /^(["']).*\1$/ =~ str
149 def expand_double_quotes(name)
150 return name unless name && name['"']
154 def auto_increment_stmt(tname, cname)
155 stmt = AUTO_INC_STMT2 % [tname, strip_quotes(cname)]
156 data = execute(stmt).first
158 start = data['autoincrementstart']
161 coldef << " GENERATED " << (data['columndefault'].nil? ? "ALWAYS" : "BY DEFAULT ")
162 coldef << "AS IDENTITY (START WITH "
164 coldef << ", INCREMENT BY "
165 coldef << data['autoincrementinc']
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)
180 def execute(sql, name = nil)
181 if sql =~ /\A\s*(UPDATE|INSERT)/i
182 i = sql =~ /\swhere\s/im
184 sql[i..-1] = sql[i..-1].gsub(/!=\s*NULL/, 'IS NOT NULL').gsub(/=\sNULL/i, 'IS NULL')
187 sql.gsub!(/= NULL/i, 'IS NULL')
192 # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
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.
197 # distinct("posts.id", "posts.created_at desc")
199 # Based on distinct method for PostgreSQL Adapter
200 def distinct(columns, order_by)
201 return "DISTINCT #{columns}" if order_by.blank?
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}" }
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 * ', '}"
215 SIZEABLE = %w(VARCHAR CLOB BLOB)
217 def structure_dump #:nodoc:
219 rs = @connection.connection.meta_data.getTables(nil,nil,nil,["TABLE"].to_java(:string))
221 tname = rs.getString(3)
222 definition << "CREATE TABLE #{tname} (\n"
223 rs2 = @connection.connection.meta_data.getColumns(nil,nil,tname,nil)
226 col_name = add_quotes(rs2.getString(4));
228 d1 = rs2.getString(13)
229 if d1 =~ /^GENERATED_/
230 default = auto_increment_stmt(tname, col_name)
232 default = " DEFAULT #{d1}"
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))) +
241 (SIZEABLE.include?(type) ? "(#{col_size})" : "") +
245 create_col_string = ",\n #{create_col_string}"
247 create_col_string = " #{create_col_string}"
250 definition << create_col_string
254 definition << ");\n\n"
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"
263 # Notes about changing in Derby:
264 # http://db.apache.org/derby/docs/10.2/ref/rrefsqlj81859.html#rrefsqlj81859__rrefsqlj37860)
266 # We support changing columns using the strategy outlined in:
267 # https://issues.apache.org/jira/browse/DERBY-1515
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"
278 execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} NULL"
282 # anything left to do?
283 unless options.empty?
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])}"
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
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)}"
306 def primary_keys(table_name)
307 @connection.primary_keys table_name.to_s.upcase
310 def columns(table_name, name=nil)
311 @connection.columns_internal(table_name.to_s, name, derby_schema)
315 @connection.tables(nil, derby_schema)
318 def recreate_database(db_name)
324 def quote_column_name(name) #:nodoc:
325 %Q{"#{name.to_s.upcase.gsub(/"/, '""')}"}
336 def add_limit_offset!(sql, options) #:nodoc:
338 sql << " OFFSET #{options[:offset]} ROWS"
341 #ROWS/ROW and FIRST/NEXT mean the same
342 sql << " FETCH FIRST #{options[:limit]} ROWS ONLY"
347 # Derby appears to define schemas using the username
349 if @config.has_key?(:schema)
352 (@config[:username] && @config[:username].to_s) || ''