1 require 'jdbc_adapter/missing_functionality_helper'
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)
13 def self.column_selector
14 [/derby/i, lambda {|cfg,col| col.extend(::JdbcSpec::Derby::Column)}]
17 def self.adapter_selector
18 [/derby/i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::Derby)}]
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
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(", ")
38 @already_monkeyd = true
42 def self.extended(*args)
46 def self.included(*args)
51 def value_to_binary(value)
52 value.scan(/[0-9A-Fa-f]{2}/).collect {|v| v.to_i(16)}.pack("C*")
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))
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
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
73 def simplified_type(field_type)
74 return :boolean if field_type =~ /smallint/i
75 return :float if field_type =~ /real/i
80 include JdbcSpec::MissingFunctionalityHelper
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"}
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)
97 def classes_for_table_name(table)
98 ActiveRecord::Base.send(:subclasses).select {|klass| klass.table_name == table}
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}")
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)
116 def primary_key(table_name) #:nodoc:
117 primary_keys(table_name).first
120 def remove_index(table_name, options) #:nodoc:
121 execute "DROP INDEX #{index_name(table_name, options)}"
124 def rename_table(name, new_name)
125 execute "RENAME TABLE #{name} TO #{new_name}"
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"
130 COLUMN_TYPE_STMT = "SELECT COLUMNDATATYPE, COLUMNDEFAULT FROM SYS.SYSCOLUMNS WHERE REFERENCEID = '%s' AND COLUMNNAME = '%s'"
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'"
136 return name unless name
140 def strip_quotes(str)
141 return str unless str
142 return str unless /^(["']).*\1$/ =~ str
146 def expand_double_quotes(name)
147 return name unless name && name['"']
151 def reinstate_auto_increment(name, refid, coldef)
152 stmt = AUTO_INC_STMT % [refid, strip_quotes(name)]
153 data = execute(stmt).first
155 start = data['autoincrementstart']
157 coldef << " GENERATED " << (data['columndefault'].nil? ? "ALWAYS" : "BY DEFAULT ")
158 coldef << "AS IDENTITY (START WITH "
160 coldef << ", INCREMENT BY "
161 coldef << data['autoincrementinc']
169 def reinstate_auto_increment(name, refid, coldef)
170 stmt = AUTO_INC_STMT % [refid, strip_quotes(name)]
171 data = execute(stmt).first
173 start = data['autoincrementstart']
175 coldef << " GENERATED " << (data['columndefault'].nil? ? "ALWAYS" : "BY DEFAULT ")
176 coldef << "AS IDENTITY (START WITH "
178 coldef << ", INCREMENT BY "
179 coldef << data['autoincrementinc']
187 def auto_increment_stmt(tname, cname)
188 stmt = AUTO_INC_STMT2 % [tname, strip_quotes(cname)]
189 data = execute(stmt).first
191 start = data['autoincrementstart']
194 coldef << " GENERATED " << (data['columndefault'].nil? ? "ALWAYS" : "BY DEFAULT ")
195 coldef << "AS IDENTITY (START WITH "
197 coldef << ", INCREMENT BY "
198 coldef << data['autoincrementinc']
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)
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)
215 alter_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} NOT NULL"
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)]
223 data = execute(stmt).first
225 coldef << add_quotes(expand_double_quotes(strip_quotes(name)))
227 coldef << data['columndatatype']
228 if !reinstate_auto_increment(name, refid, coldef) && data['columndefault']
229 coldef << " DEFAULT " << data['columndefault']
235 SIZEABLE = %w(VARCHAR CLOB BLOB)
237 def structure_dump #:nodoc:
239 rs = @connection.connection.meta_data.getTables(nil,nil,nil,["TABLE"].to_java(:string))
241 tname = rs.getString(3)
242 definition << "CREATE TABLE #{tname} (\n"
243 rs2 = @connection.connection.meta_data.getColumns(nil,nil,tname,nil)
246 col_name = add_quotes(rs2.getString(4));
248 d1 = rs2.getString(13)
249 if d1 =~ /^GENERATED_/
250 default = auto_increment_stmt(tname, col_name)
252 default = " DEFAULT #{d1}"
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))) +
261 (SIZEABLE.include?(type) ? "(#{col_size})" : "") +
265 create_col_string = ",\n #{create_col_string}"
267 create_col_string = " #{create_col_string}"
270 definition << create_col_string
274 definition << ");\n\n"
279 # Support for removing columns added via derby bug issue:
280 # https://issues.apache.org/jira/browse/DERBY-1489
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
286 def remove_column(table_name, column_name)
288 execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name} RESTRICT"
290 alter_table(table_name) do |definition|
291 definition.columns.delete(definition[column_name])
296 # Notes about changing in Derby:
297 # http://db.apache.org/derby/docs/10.2/ref/rrefsqlj81859.html#rrefsqlj81859__rrefsqlj37860)
299 # We support changing columns using the strategy outlined in:
300 # https://issues.apache.org/jira/browse/DERBY-1515
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"
311 execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} NULL"
315 # anything left to do?
316 unless options.empty?
318 execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DATA TYPE #{type_to_sql(type, options[:limit])}"
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
335 # Support for renaming columns:
336 # https://issues.apache.org/jira/browse/DERBY-1490
338 # This feature is expect to arrive in version 10.3.0.0:
339 # http://wiki.apache.org/db-derby/DerbyTenThreeRelease)
341 def rename_column(table_name, column_name, new_column_name) #:nodoc:
343 execute "ALTER TABLE #{table_name} ALTER RENAME COLUMN #{column_name} TO #{new_column_name}"
345 alter_table(table_name, :rename => {column_name => new_column_name})
349 def primary_keys(table_name)
350 @connection.primary_keys table_name.to_s.upcase
353 def recreate_database(db_name)
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:
362 if /^references$/i =~ name
364 elsif /[A-Z]/ =~ name && /[a-z]/ =~ name
368 elsif name =~ /^[_\d]/