aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcel Molina <marcel@vernix.org>2006-04-27 22:39:45 +0000
committerMarcel Molina <marcel@vernix.org>2006-04-27 22:39:45 +0000
commitb2c0ddf033fbf143539ad8a7630747a0e866e4fc (patch)
tree2aae48e00aa7d4951e711ebb667d8f37e0c8a6d0
parentdd5397a57c39da78f22e0fec517ff6e5ede7628a (diff)
downloadrails-b2c0ddf033fbf143539ad8a7630747a0e866e4fc.tar.gz
rails-b2c0ddf033fbf143539ad8a7630747a0e866e4fc.tar.bz2
rails-b2c0ddf033fbf143539ad8a7630747a0e866e4fc.zip
Add support for FrontBase (http://www.frontbase.com/) with a new adapter thanks to the hard work of one Mike Laster. Closes #4093. [mlaster@metavillage.com]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4291 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
-rw-r--r--activerecord/CHANGELOG2
-rwxr-xr-xactiverecord/Rakefile41
-rwxr-xr-xactiverecord/lib/active_record.rb2
-rwxr-xr-xactiverecord/lib/active_record/base.rb8
-rw-r--r--activerecord/lib/active_record/calculations.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb837
-rwxr-xr-xactiverecord/lib/active_record/fixtures.rb8
-rw-r--r--activerecord/test/adapter_test.rb16
-rwxr-xr-xactiverecord/test/base_test.rb12
-rw-r--r--activerecord/test/connections/native_frontbase/connection.rb26
-rwxr-xr-xactiverecord/test/deprecated_finder_test.rb1
-rw-r--r--activerecord/test/finder_test.rb6
-rw-r--r--activerecord/test/fixtures/db_definitions/frontbase.drop.sql30
-rw-r--r--activerecord/test/fixtures/db_definitions/frontbase.sql251
-rw-r--r--activerecord/test/fixtures/db_definitions/frontbase2.drop.sql1
-rw-r--r--activerecord/test/fixtures/db_definitions/frontbase2.sql4
-rw-r--r--activerecord/test/migration_test.rb36
-rw-r--r--activerecord/test/pk_test.rb1
-rw-r--r--activerecord/test/reflection_test.rb6
-rw-r--r--activerecord/test/threaded_connections_test.rb34
-rw-r--r--activerecord/test/transactions_test.rb1
21 files changed, 1282 insertions, 55 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index d8b0e2a6ff..1aac586b04 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Add support for FrontBase (http://www.frontbase.com/) with a new adapter thanks to the hard work of one Mike Laster. Closes #4093. [mlaster@metavillage.com]
+
* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. [Francois Beausoleil <francois.beausoleil@gmail.com>]
* Fix syntax error in documentation. Closes #4679. [mislav@nippur.irb.hr]
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 129cdd8a7f..623dd5aaa5 100755
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -27,7 +27,7 @@ task :default => [ :test_mysql, :test_sqlite, :test_postgresql ]
# Run the unit tests
-for adapter in %w( mysql postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase )
+for adapter in %w( mysql postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase frontbase )
Rake::TestTask.new("test_#{adapter}") { |t|
t.libs << "test" << "test/connections/native_#{adapter}"
t.pattern = "test/*_test{,_#{adapter}}.rb"
@@ -71,6 +71,45 @@ end
desc 'Rebuild the PostgreSQL test databases'
task :rebuild_postgresql_databases => [:drop_postgresql_databases, :build_postgresql_databases]
+desc 'Build the FrontBase test databases'
+task :build_frontbase_databases => :rebuild_frontbase_databases
+
+desc 'Rebuild the FrontBase test databases'
+task :rebuild_frontbase_databases do
+ build_frontbase_database = Proc.new do |db_name, sql_definition_file|
+ %(
+ STOP DATABASE #{db_name};
+ DELETE DATABASE #{db_name};
+ CREATE DATABASE #{db_name};
+
+ CONNECT TO #{db_name} AS SESSION_NAME USER _SYSTEM;
+ SET COMMIT FALSE;
+
+ CREATE USER RAILS;
+ CREATE SCHEMA RAILS AUTHORIZATION RAILS;
+ COMMIT;
+
+ SET SESSION AUTHORIZATION RAILS;
+ SCRIPT '#{sql_definition_file}';
+
+ COMMIT;
+
+ DISCONNECT ALL;
+ )
+ end
+ create_activerecord_unittest = build_frontbase_database['activerecord_unittest', File.join(SCHEMA_PATH, 'frontbase.sql')]
+ create_activerecord_unittest2 = build_frontbase_database['activerecord_unittest2', File.join(SCHEMA_PATH, 'frontbase2.sql')]
+ execute_frontbase_sql = Proc.new do |sql|
+ system(<<-SHELL)
+ /Library/FrontBase/bin/sql92 <<-SQL
+ #{sql}
+ SQL
+ SHELL
+ end
+ execute_frontbase_sql[create_activerecord_unittest]
+ execute_frontbase_sql[create_activerecord_unittest2]
+end
+
# Generate the RDoc documentation
Rake::RDocTask.new { |rdoc|
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 293c86f13b..d4b08da1f0 100755
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -68,7 +68,7 @@ ActiveRecord::Base.class_eval do
end
unless defined?(RAILS_CONNECTION_ADAPTERS)
- RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase )
+ RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase frontbase )
end
RAILS_CONNECTION_ADAPTERS.each do |adapter|
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c39689c01d..e7fa8434e6 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -755,8 +755,8 @@ module ActiveRecord #:nodoc:
superclass == Base || !columns_hash.include?(inheritance_column)
end
- def quote(object) #:nodoc:
- connection.quote(object)
+ def quote(value, column = nil) #:nodoc:
+ connection.quote(value,column)
end
# Used to sanitize objects before they're used in an SELECT SQL-statement. Delegates to <tt>connection.quote</tt>.
@@ -947,7 +947,7 @@ module ActiveRecord #:nodoc:
def find_one(id, options)
conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
- options.update :conditions => "#{table_name}.#{primary_key} = #{sanitize(id)}#{conditions}"
+ options.update :conditions => "#{table_name}.#{primary_key} = #{quote(id,columns_hash[primary_key])}#{conditions}"
if result = find_initial(options)
result
@@ -958,7 +958,7 @@ module ActiveRecord #:nodoc:
def find_some(ids, options)
conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
- ids_list = ids.map { |id| sanitize(id) }.join(',')
+ ids_list = ids.map { |id| quote(id,columns_hash[primary_key]) }.join(',')
options.update :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}"
result = find_every(options)
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 83bf279785..7a25bd4aa1 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -173,8 +173,18 @@ module ActiveRecord
add_joins!(sql, options, scope)
add_conditions!(sql, options[:conditions], scope)
add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
- sql << " GROUP BY #{options[:group_field]} " if options[:group]
- sql << " HAVING #{options[:having]} " if options[:group] && options[:having]
+ sql << " GROUP BY #{options[:group_alias]} " if options[:group]
+
+ if options[:group] && options[:having]
+ # FrontBase requires identifiers in the HAVING clause and chokes on function calls
+ if Base.connection.adapter_name == 'FrontBase'
+ options[:having].downcase!
+ options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
+ end
+
+ sql << " HAVING #{options[:having]} "
+ end
+
sql << " ORDER BY #{options[:order]} " if options[:order]
add_limit!(sql, options, scope)
sql << ')' if use_workaround
diff --git a/activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb b/activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb
new file mode 100644
index 0000000000..6fa2ca3ffc
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb
@@ -0,0 +1,837 @@
+# Requires FrontBase Ruby bindings from:
+# svn://rubyforge.org/var/svn/frontbase-rails/trunk/ruby-frontbase
+
+require 'active_record/connection_adapters/abstract_adapter'
+require 'frontbase'
+
+FB_TRACE = false
+
+module ActiveRecord
+
+ class Base
+
+ class << self
+ # FrontBase only supports one unnamed sequence per table
+ def set_sequence_name( value=nil, &block )
+ end
+
+ # Establishes a connection to the database that's used by all Active Record objects.
+ def frontbase_connection(config) # :nodoc:
+ config = config.symbolize_keys
+ database = config[:database]
+ port = config[:port]
+ host = config[:host]
+ username = config[:username]
+ password = config[:password]
+ dbpassword = config[:dbpassword]
+ session_name = config[:session_name]
+
+ dbpassword = '' if dbpassword.nil?
+
+ # Turn off colorization since it makes tail/less output difficult
+ self.colorize_logging = false
+
+
+ connection = FBSQL_Connect.connect(host, port, database, username, password, dbpassword, session_name)
+ ConnectionAdapters::FrontBaseAdapter.new(connection, logger, [host, port, database, username, password, dbpassword, session_name], config)
+ end
+ end
+ end
+
+ module ConnectionAdapters
+
+ # From EOF Documentation....
+ # buffer should have space for EOUniqueBinaryKeyLength (12) bytes.
+ # Assigns a world-wide unique ID made up of:
+ # < Sequence [2], ProcessID [2] , Time [4], IP Addr [4] >
+
+ class TwelveByteKey < String #:nodoc
+ @@mutex = Mutex.new
+ @@sequence_number = rand(65536)
+ @@key_cached_pid_component = nil
+ @@key_cached_ip_component = nil
+
+ def initialize(string = nil)
+ # Generate a unique key
+ if string.nil?
+ new_key = replace('_' * 12)
+
+ new_key[0..1] = self.class.key_sequence_component
+ new_key[2..3] = self.class.key_pid_component
+ new_key[4..7] = self.class.key_time_component
+ new_key[8..11] = self.class.key_ip_component
+ new_key
+ else
+ if string.size == 24
+ string.gsub!(/[[:xdigit:]]{2}/) { |x| x.hex.chr }
+ end
+ raise "string is not 12 bytes long" unless string.size == 12
+ super(string)
+ end
+ end
+
+ def inspect
+ unpack("H*").first.upcase
+ end
+
+ alias_method :to_s, :inspect
+
+ private
+
+ class << self
+ def key_sequence_component
+ seq = nil
+ @@mutex.synchronize do
+ seq = @@sequence_number
+ @@sequence_number = (@@sequence_number + 1) % 65536
+ end
+
+ sequence_component = "__"
+ sequence_component[0] = seq >> 8
+ sequence_component[1] = seq
+ sequence_component
+ end
+
+ def key_pid_component
+ if @@key_cached_pid_component.nil?
+ @@mutex.synchronize do
+ pid = $$
+ pid_component = "__"
+ pid_component[0] = pid >> 8
+ pid_component[1] = pid
+ @@key_cached_pid_component = pid_component
+ end
+ end
+ @@key_cached_pid_component
+ end
+
+ def key_time_component
+ time = Time.new.to_i
+ time_component = "____"
+ time_component[0] = (time & 0xFF000000) >> 24
+ time_component[1] = (time & 0x00FF0000) >> 16
+ time_component[2] = (time & 0x0000FF00) >> 8
+ time_component[3] = (time & 0x000000FF)
+ time_component
+ end
+
+ def key_ip_component
+ if @@key_cached_ip_component.nil?
+ @@mutex.synchronize do
+ old_lookup_flag = BasicSocket.do_not_reverse_lookup
+ BasicSocket.do_not_reverse_lookup = true
+ udpsocket = UDPSocket.new
+ udpsocket.connect("17.112.152.32",1)
+ ip_string = udpsocket.addr[3]
+ BasicSocket.do_not_reverse_lookup = old_lookup_flag
+ packed = Socket.pack_sockaddr_in(0,ip_string)
+ addr_subset = packed[4..7]
+ ip = addr_subset[0] << 24 | addr_subset[1] << 16 | addr_subset[2] << 8 | addr_subset[3]
+ ip_component = "____"
+ ip_component[0] = (ip & 0xFF000000) >> 24
+ ip_component[1] = (ip & 0x00FF0000) >> 16
+ ip_component[2] = (ip & 0x0000FF00) >> 8
+ ip_component[3] = (ip & 0x000000FF)
+ @@key_cached_ip_component = ip_component
+ end
+ end
+ @@key_cached_ip_component
+ end
+ end
+ end
+
+ class FrontBaseColumn < Column #:nodoc:
+ attr_reader :fb_autogen
+
+ def initialize(base, name, type, typename, limit, precision, scale, default, nullable)
+
+ @base = base
+ @name = name
+ @type = simplified_type(type,typename,limit)
+ @limit = limit
+ @precision = precision
+ @scale = scale
+ @default = default
+ @null = nullable == "YES"
+ @text = [:string, :text].include? @type
+ @number = [:float, :integer].include? @type
+ @fb_autogen = false
+
+ if @default
+ @default.gsub!(/^'(.*)'$/,'\1') if @text
+ @fb_autogen = @default.include?("SELECT UNIQUE FROM")
+ case @type
+ when :boolean
+ @default = @default == "TRUE"
+ when :binary
+ if @default != "X''"
+ buffer = ""
+ @default.scan(/../) { |h| buffer << h.hex.chr }
+ @default = buffer
+ else
+ @default = ""
+ end
+ else
+ @default = type_cast(@default)
+ end
+ end
+ end
+
+ # Casts value (which is a String) to an appropriate instance.
+ def type_cast(value)
+ if type == :twelvebytekey
+ ActiveRecord::ConnectionAdapters::TwelveByteKey.new(value)
+ else
+ super(value)
+ end
+ end
+
+ def type_cast_code(var_name)
+ if type == :twelvebytekey
+ "ActiveRecord::ConnectionAdapters::TwelveByteKey.new(#{var_name})"
+ else
+ super(var_name)
+ end
+ end
+
+ private
+ def simplified_type(field_type, type_name,limit)
+ ret_type = :string
+ puts "typecode: [#{field_type}] [#{type_name}]" if FB_TRACE
+
+ # 12 byte primary keys are a special case that Apple's EOF
+ # used heavily. Optimize for this case
+ if field_type == 11 && limit == 96
+ ret_type = :twelvebytekey # BIT(96)
+ else
+ ret_type = case field_type
+ when 1 then :boolean # BOOLEAN
+ when 2 then :integer # INTEGER
+ when 4 then :float # FLOAT
+ when 10 then :string # CHARACTER VARYING
+ when 11 then :bitfield # BIT
+ when 13 then :date # DATE
+ when 14 then :time # TIME
+ when 16 then :timestamp # TIMESTAMP
+ when 20 then :text # CLOB
+ when 21 then :binary # BLOB
+ when 22 then :integer # TINYINT
+ else
+ puts "ERROR: Unknown typecode: [#{field_type}] [#{type_name}]"
+ end
+ end
+ puts "ret_type: #{ret_type.inspect}" if FB_TRACE
+ ret_type
+ end
+ end
+
+ class FrontBaseAdapter < AbstractAdapter
+
+ def initialize(connection, logger, connection_options, config)
+ super(connection, logger)
+ @connection_options, @config = connection_options, config
+ @transaction_mode = :pessimistic
+
+ # threaded_connections_test.rb will fail unless we set the session
+ # to optimistic locking mode
+# set_pessimistic_transactions
+# execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC"
+ end
+
+ # Returns the human-readable name of the adapter. Use mixed case - one
+ # can always use downcase if needed.
+ def adapter_name #:nodoc:
+ 'FrontBase'
+ end
+
+ # Does this adapter support migrations? Backend specific, as the
+ # abstract adapter always returns +false+.
+ def supports_migrations? #:nodoc:
+ true
+ end
+
+ def native_database_types #:nodoc
+ {
+ :primary_key => "INTEGER DEFAULT UNIQUE PRIMARY KEY",
+ :string => { :name => "VARCHAR", :limit => 255 },
+ :text => { :name => "CLOB" },
+ :integer => { :name => "INTEGER" },
+ :float => { :name => "FLOAT" },
+ :datetime => { :name => "TIMESTAMP" },
+ :timestamp => { :name => "TIMESTAMP" },
+ :time => { :name => "TIME" },
+ :date => { :name => "DATE" },
+ :binary => { :name => "BLOB" },
+ :boolean => { :name => "BOOLEAN" },
+ :twelvebytekey => { :name => "BYTE", :limit => 12}
+ }
+ end
+
+
+ # QUOTING ==================================================
+
+ # Quotes the column value to help prevent
+ # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
+ def quote(value, column = nil)
+ retvalue = "<INVALID>"
+
+ puts "quote(#{value.inspect}(#{value.class}),#{column.type.inspect})" if FB_TRACE
+ # If a column was passed in, use column type information
+ unless value.nil?
+ if column
+ retvalue = case column.type
+ when :string
+ if value.kind_of?(String)
+ "'#{quote_string(value.to_s)}'" # ' (for ruby-mode)
+ else
+ "'#{quote_string(value.to_yaml)}'"
+ end
+ when :integer
+ if value.kind_of?(TrueClass)
+ '1'
+ elsif value.kind_of?(FalseClass)
+ '0'
+ else
+ value.to_i.to_s
+ end
+ when :float
+ value.to_f.to_s
+ when :datetime, :timestamp
+ "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
+ when :time
+ "TIME '#{value.strftime("%H:%M:%S")}'"
+ when :date
+ "DATE '#{value.strftime("%Y-%m-%d")}'"
+ when :twelvebytekey
+ value = value.to_s.unpack("H*").first unless value.kind_of?(TwelveByteKey)
+ "X'#{value.to_s}'"
+ when :boolean
+ value = quoted_true if value.kind_of?(TrueClass)
+ value = quoted_false if value.kind_of?(FalseClass)
+ value
+ when :binary
+ blob_handle = @connection.create_blob(value.to_s)
+ puts "SQL -> Insert #{value.to_s.length} byte blob as #{retvalue}" if FB_TRACE
+ blob_handle.handle
+ when :text
+ if value.kind_of?(String)
+ clobdata = value.to_s # ' (for ruby-mode)
+ else
+ clobdata = value.to_yaml
+ end
+ clob_handle = @connection.create_clob(clobdata)
+ puts "SQL -> Insert #{value.to_s.length} byte clob as #{retvalue}" if FB_TRACE
+ clob_handle.handle
+ else
+ raise "*** UNKNOWN TYPE: #{column.type.inspect}"
+ end # case
+ # Since we don't have column type info, make a best guess based
+ # on the Ruby class of the value
+ else
+ retvalue = case value
+ when ActiveRecord::ConnectionAdapters::TwelveByteKey
+ s = value.unpack("H*").first
+ "X'#{s}'"
+ when String
+ if column && column.type == :binary
+ s = value.unpack("H*").first
+ "X'#{s}'"
+ elsif column && [:integer, :float].include?(column.type)
+ value.to_s
+ else
+ "'#{quote_string(value)}'" # ' (for ruby-mode)
+ end
+ when NilClass
+ "NULL"
+ when TrueClass
+ (column && column.type == :integer ? '1' : quoted_true)
+ when FalseClass
+ (column && column.type == :integer ? '0' : quoted_false)
+ when Float, Fixnum, Bignum
+ value.to_s
+ when Time, Date, DateTime
+ if column
+ case column.type
+ when :date
+ "DATE '#{value.strftime("%Y-%m-%d")}'"
+ when :time
+ "TIME '#{value.strftime("%H:%M:%S")}'"
+ when :timestamp
+ "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
+ else
+ raise NotImplementedError, "Unknown column type!"
+ end # case
+ else # Column wasn't passed in, so try to guess the right type
+ if value.kind_of? Date
+ "DATE '#{value.strftime("%Y-%m-%d")}'"
+ else
+ if [:hour, :min, :sec].all? {|part| value.send(:part).zero? }
+ "TIME '#{value.strftime("%H:%M:%S")}'"
+ else
+ "TIMESTAMP '#{quoted_date(value)}'"
+ end
+ end
+ end #if column
+ else
+ "'#{quote_string(value.to_yaml)}'"
+ end #case
+ end
+ else
+ retvalue = "NULL"
+ end
+
+ retvalue
+ end # def
+
+ # Quotes a string, escaping any ' (single quote) characters.
+ def quote_string(s)
+ s.gsub(/'/, "''") # ' (for ruby-mode)
+ end
+
+ def quote_column_name(name) #:nodoc:
+ %( "#{name}" )
+ end
+
+ def quoted_true
+ "true"
+ end
+
+ def quoted_false
+ "false"
+ end
+
+
+ # CONNECTION MANAGEMENT ====================================
+
+ def active?
+ true if @connection.status == 1
+ rescue => e
+ false
+ end
+
+ def reconnect!
+ @connection.close rescue nil
+ @connection = FBSQL_Connect.connect(*@connection_options.first(7))
+ end
+
+ # Close this connection
+ def disconnect!
+ @connection.close rescue nil
+ @active = false
+ end
+
+ # DATABASE STATEMENTS ======================================
+
+ # Returns an array of record hashes with the column names as keys and
+ # column values as values.
+ def select_all(sql, name = nil) #:nodoc:
+ fbsql = cleanup_fb_sql(sql)
+ return_value = []
+ fbresult = execute(sql, name)
+ puts "select_all SQL -> #{fbsql}" if FB_TRACE
+ columns = fbresult.columns
+
+ fbresult.each do |row|
+ puts "SQL <- #{row.inspect}" if FB_TRACE
+ hashed_row = {}
+ colnum = 0
+ row.each do |col|
+ hashed_row[columns[colnum]] = col
+ if col.kind_of?(FBSQL_LOB)
+ hashed_row[columns[colnum]] = col.read
+ end
+ colnum += 1
+ end
+ puts "raw row: #{hashed_row.inspect}" if FB_TRACE
+ return_value << hashed_row
+ end
+ return_value
+ end
+
+ def select_one(sql, name = nil) #:nodoc:
+ fbsql = cleanup_fb_sql(sql)
+ return_value = []
+ fbresult = execute(fbsql, name)
+ puts "SQL -> #{fbsql}" if FB_TRACE
+ columns = fbresult.columns
+
+ fbresult.each do |row|
+ puts "SQL <- #{row.inspect}" if FB_TRACE
+ hashed_row = {}
+ colnum = 0
+ row.each do |col|
+ hashed_row[columns[colnum]] = col
+ if col.kind_of?(FBSQL_LOB)
+ hashed_row[columns[colnum]] = col.read
+ end
+ colnum += 1
+ end
+ return_value << hashed_row
+ break
+ end
+ fbresult.clear
+ return_value.first
+ end
+
+ def query(sql, name = nil) #:nodoc:
+ fbsql = cleanup_fb_sql(sql)
+ puts "SQL(query) -> #{fbsql}" if FB_TRACE
+ log(fbsql, name) { @connection.query(fbsql) }
+ rescue => e
+ puts "FB Exception: #{e.inspect}" if FB_TRACE
+ raise e
+ end
+
+ def execute(sql, name = nil) #:nodoc:
+ fbsql = cleanup_fb_sql(sql)
+ puts "SQL(execute) -> #{fbsql}" if FB_TRACE
+ log(fbsql, name) { @connection.query(fbsql) }
+ rescue ActiveRecord::StatementInvalid => e
+ if e.message.scan(/Table name - \w* - exists/).empty?
+ puts "FB Exception: #{e.inspect}" if FB_TRACE
+ raise e
+ end
+ end
+
+ # Returns the last auto-generated ID from the affected table.
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
+ puts "SQL -> #{sql.inspect}" if FB_TRACE
+ execute(sql, name)
+ id_value || pk
+ end
+
+ # Executes the update statement and returns the number of rows affected.
+ def update(sql, name = nil) #:nodoc:
+ puts "SQL -> #{sql.inspect}" if FB_TRACE
+ execute(sql, name).num_rows
+ end
+
+ alias_method :delete, :update #:nodoc:
+
+ def set_pessimistic_transactions
+ if @transaction_mode == :optimistic
+ execute "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, LOCKING PESSIMISTIC, READ WRITE"
+ @transaction_mode = :pessimistic
+ end
+ end
+
+ def set_optimistic_transactions
+ if @transaction_mode == :pessimistic
+ execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC"
+ @transaction_mode = :optimistic
+ end
+ end
+
+ def begin_db_transaction #:nodoc:
+ execute "SET COMMIT FALSE" rescue nil
+ end
+
+ def commit_db_transaction #:nodoc:
+ execute "COMMIT"
+ ensure
+ execute "SET COMMIT TRUE"
+ end
+
+ def rollback_db_transaction #:nodoc:
+ execute "ROLLBACK"
+ ensure
+ execute "SET COMMIT TRUE"
+ end
+
+ def add_limit_offset!(sql, options) #:nodoc
+ if limit = options[:limit]
+ offset = options[:offset] || 0
+
+# Here is the full syntax FrontBase supports:
+# (from gclem@frontbase.com)
+#
+# TOP <limit - unsigned integer>
+# TOP ( <offset expr>, <limit expr>)
+
+ # "TOP 0" is not allowed, so we have
+ # to use a cheap trick.
+ if limit.zero?
+ case sql
+ when /WHERE/i
+ sql.sub!(/WHERE/i, 'WHERE 0 = 1 AND ')
+ when /ORDER\s+BY/i
+ sql.sub!(/ORDER\s+BY/i, 'WHERE 0 = 1 ORDER BY')
+ else
+ sql << 'WHERE 0 = 1'
+ end
+ else
+ if offset.zero?
+ sql.replace sql.gsub("SELECT ","SELECT TOP #{limit} ")
+ else
+ sql.replace sql.gsub("SELECT ","SELECT TOP(#{offset},#{limit}) ")
+ end
+ end
+ end
+ end
+
+ def prefetch_primary_key?(table_name = nil)
+ true
+ end
+
+ # Returns the next sequence value from a sequence generator. Not generally
+ # called directly; used by ActiveRecord to get the next primary key value
+ # when inserting a new database record (see #prefetch_primary_key?).
+ def next_sequence_value(sequence_name)
+ unique = select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value")
+ # The test cases cannot handle a zero primary key
+ unique.zero? ? select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") : unique
+ end
+
+ def default_sequence_name(table, column)
+ table
+ end
+
+ # Set the sequence to the max value of the table's column.
+ def reset_sequence!(table, column, sequence = nil)
+ klasses = classes_for_table_name(table)
+ klass = klasses.nil? ? nil : klasses.first
+ pk = klass.primary_key unless klass.nil?
+ if pk && klass.columns_hash[pk].type == :integer
+ execute("SET UNIQUE FOR #{klass.table_name}(#{pk})")
+ end
+ end
+
+ def classes_for_table_name(table)
+ ActiveRecord::Base.send(:subclasses).select {|klass| klass.table_name == table}
+ end
+
+ def reset_pk_sequence!(table, pk = nil, sequence = nil)
+ klasses = classes_for_table_name(table)
+ klass = klasses.nil? ? nil : klasses.first
+ pk = klass.primary_key unless klass.nil?
+ if pk && klass.columns_hash[pk].type == :integer
+ mpk = select_value("SELECT MAX(#{pk}) FROM #{table}")
+ execute("SET UNIQUE FOR #{klass.table_name}(#{pk})")
+ end
+ end
+
+ # SCHEMA STATEMENTS ========================================
+
+ def structure_dump #:nodoc:
+ select_all("SHOW TABLES").inject('') do |structure, table|
+ structure << select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] << ";\n\n"
+ end
+ end
+
+ def recreate_database(name) #:nodoc:
+ drop_database(name)
+ create_database(name)
+ end
+
+ def create_database(name) #:nodoc:
+ execute "CREATE DATABASE #{name}"
+ end
+
+ def drop_database(name) #:nodoc:
+ execute "DROP DATABASE #{name}"
+ end
+
+ def current_database
+ select_value('SELECT "CATALOG_NAME" FROM INFORMATION_SCHEMA.CATALOGS').downcase
+ end
+
+ def tables(name = nil) #:nodoc:
+ select_values(<<-SQL, nil)
+ SELECT "TABLE_NAME"
+ FROM INFORMATION_SCHEMA.TABLES AS T0,
+ INFORMATION_SCHEMA.SCHEMATA AS T1
+ WHERE T0.SCHEMA_PK = T1.SCHEMA_PK
+ AND "SCHEMA_NAME" = CURRENT_SCHEMA
+ SQL
+ end
+
+ def indexes(table_name, name = nil)#:nodoc:
+ indexes = []
+ current_index = nil
+ sql = <<-SQL
+ SELECT INDEX_NAME, T2.ORDINAL_POSITION, INDEX_COLUMN_COUNT, INDEX_TYPE,
+ "COLUMN_NAME", IS_NULLABLE
+ FROM INFORMATION_SCHEMA.TABLES AS T0,
+ INFORMATION_SCHEMA.INDEXES AS T1,
+ INFORMATION_SCHEMA.INDEX_COLUMN_USAGE AS T2,
+ INFORMATION_SCHEMA.COLUMNS AS T3
+ WHERE T0."TABLE_NAME" = '#{table_name}'
+ AND INDEX_TYPE <> 0
+ AND T0.TABLE_PK = T1.TABLE_PK
+ AND T0.TABLE_PK = T2.TABLE_PK
+ AND T0.TABLE_PK = T3.TABLE_PK
+ AND T1.INDEXES_PK = T2.INDEX_PK
+ AND T2.COLUMN_PK = T3.COLUMN_PK
+ ORDER BY INDEX_NAME, T2.ORDINAL_POSITION
+ SQL
+
+ columns = []
+ query(sql).each do |row|
+ index_name = row[0]
+ ord_position = row[1]
+ ndx_colcount = row[2]
+ index_type = row[3]
+ column_name = row[4]
+
+ is_unique = index_type == 1
+
+ columns << column_name
+ if ord_position == ndx_colcount
+ indexes << IndexDefinition.new(table_name, index_name, is_unique , columns)
+ columns = []
+ end
+ end
+ indexes
+ end
+
+ def columns(table_name, name = nil)#:nodoc:
+ sql = <<-SQL
+ SELECT "TABLE_NAME", "COLUMN_NAME", ORDINAL_POSITION, IS_NULLABLE, COLUMN_DEFAULT,
+ DATA_TYPE, DATA_TYPE_CODE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION,
+ NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, DATETIME_PRECISION_LEADING
+ FROM INFORMATION_SCHEMA.TABLES T0,
+ INFORMATION_SCHEMA.COLUMNS T1,
+ INFORMATION_SCHEMA.DATA_TYPE_DESCRIPTOR T3
+ WHERE "TABLE_NAME" = '#{table_name}'
+ AND T0.TABLE_PK = T1.TABLE_PK
+ AND T0.TABLE_PK = T3.TABLE_OR_DOMAIN_PK
+ AND T1.COLUMN_PK = T3.COLUMN_NAME_PK
+ ORDER BY T1.ORDINAL_POSITION
+ SQL
+
+ rawresults = query(sql,name)
+ columns = []
+ rawresults.each do |field|
+ args = [base = field[0],
+ name = field[1],
+ typecode = field[6],
+ typestring = field[5],
+ limit = field[7],
+ precision = field[8],
+ scale = field[9],
+ default = field[4],
+ nullable = field[3]]
+ columns << FrontBaseColumn.new(*args)
+ end
+ columns
+ end
+
+ def create_table(name, options = {})
+ table_definition = TableDefinition.new(self)
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
+
+ yield table_definition
+
+ if options[:force]
+ drop_table(name) rescue nil
+ end
+
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
+ create_sql << "#{name} ("
+ create_sql << table_definition.to_sql
+ create_sql << ") #{options[:options]}"
+ begin_db_transaction
+ execute create_sql
+ commit_db_transaction
+ rescue ActiveRecord::StatementInvalid => e
+ raise e unless e.message.match(/Table name - \w* - exists/)
+ end
+
+ def rename_table(name, new_name)
+ columns = columns(name)
+ pkcol = columns.find {|c| c.fb_autogen}
+ execute "ALTER TABLE NAME #{name} TO #{new_name}"
+ if pkcol
+ change_column_default(new_name,pkcol.name,"UNIQUE")
+ begin_db_transaction
+ mpk = select_value("SELECT MAX(#{pkcol.name}) FROM #{new_name}")
+ mpk = 0 if mpk.nil?
+ execute "SET UNIQUE=#{mpk} FOR #{new_name}"
+ commit_db_transaction
+ end
+ end
+
+ # Drops a table from the database.
+ def drop_table(name)
+ execute "DROP TABLE #{name} RESTRICT"
+ rescue ActiveRecord::StatementInvalid => e
+ raise e unless e.message.match(/Referenced TABLE - \w* - does not exist/)
+ end
+
+ # Adds a new column to the named table.
+ # See TableDefinition#column for details of the options you can use.
+ def add_column(table_name, column_name, type, options = {})
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"
+ options[:type] = type
+ add_column_options!(add_column_sql, options)
+ execute(add_column_sql)
+ end
+
+ def add_column_options!(sql, options) #:nodoc:
+ default_value = quote(options[:default], options[:column])
+ if options[:default]
+ if options[:type] == :boolean
+ default_value = options[:default] == 0 ? quoted_false : quoted_true
+ end
+ end
+ sql << " DEFAULT #{default_value}" unless options[:default].nil?
+ sql << " NOT NULL" if options[:null] == false
+ end
+
+ # Removes the column from the table definition.
+ # ===== Examples
+ # remove_column(:suppliers, :qualification)
+ def remove_column(table_name, column_name)
+ execute "ALTER TABLE #{table_name} DROP #{column_name} RESTRICT"
+ end
+
+ def remove_index(table_name, options = {}) #:nodoc:
+ if options[:unique]
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{quote_column_name(index_name(table_name, options))} RESTRICT"
+ else
+ execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
+ end
+ end
+
+ def change_column_default(table_name, column_name, default) #:nodoc:
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{default}" if default != "NULL"
+ end
+
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
+ change_column_sql = %( ALTER COLUMN "#{table_name}"."#{column_name}" TO #{type_to_sql(type, options[:limit])} )
+ execute(change_column_sql)
+ change_column_sql = %( ALTER TABLE "#{table_name}" ALTER COLUMN "#{column_name}" )
+
+ default_value = quote(options[:default], options[:column])
+ if options[:default]
+ if type == :boolean
+ default_value = options[:default] == 0 ? quoted_false : quoted_true
+ end
+ end
+
+ if default_value != "NULL"
+ change_column_sql << " SET DEFAULT #{default_value}"
+ execute(change_column_sql)
+ end
+
+# change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}"
+# add_column_options!(change_column_sql, options)
+# execute(change_column_sql)
+ end
+
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
+ execute %( ALTER COLUMN NAME "#{table_name}"."#{column_name}" TO "#{new_column_name}" )
+ end
+
+ private
+
+ # Clean up sql to make it something FrontBase can digest
+ def cleanup_fb_sql(sql) #:nodoc:
+ # Turn non-standard != into standard <>
+ cleansql = sql.gsub("!=", "<>")
+ # Strip blank lines and comments
+ cleansql.split("\n").reject { |line| line.match(/^(?:\s*|--.*)$/) } * "\n"
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index f83219f508..a1033c40e4 100755
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -392,7 +392,13 @@ class Fixture #:nodoc:
end
def value_list
- @fixture.values.map { |v| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ")
+ klass = @class_name.constantize rescue nil
+
+ list = @fixture.inject([]) do |fixtures, (key, value)|
+ col = klass.columns_hash[key] unless klass.nil?
+ fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('\\n', "\n").gsub('\\r', "\r")
+ end
+ list * ', '
end
def find
diff --git a/activerecord/test/adapter_test.rb b/activerecord/test/adapter_test.rb
index c4027b31cb..0c12c4d37e 100644
--- a/activerecord/test/adapter_test.rb
+++ b/activerecord/test/adapter_test.rb
@@ -66,19 +66,21 @@ class AdapterTest < Test::Unit::TestCase
if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
require 'fixtures/movie'
require 'fixtures/subscriber'
+
def test_reset_empty_table_with_custom_pk
Movie.delete_all
Movie.connection.reset_pk_sequence! 'movies'
assert_equal 1, Movie.create(:name => 'fight club').id
end
- def test_reset_table_with_non_integer_pk
- Subscriber.delete_all
- Subscriber.connection.reset_pk_sequence! 'subscribers'
-
- sub = Subscriber.new(:name => 'robert drake')
- sub.id = 'bob drake'
- assert_nothing_raised { sub.save! }
+ if ActiveRecord::Base.connection.adapter_name != "FrontBase"
+ def test_reset_table_with_non_integer_pk
+ Subscriber.delete_all
+ Subscriber.connection.reset_pk_sequence! 'subscribers'
+ sub = Subscriber.new(:name => 'robert drake')
+ sub.id = 'bob drake'
+ assert_nothing_raised { sub.save! }
+ end
end
end
diff --git a/activerecord/test/base_test.rb b/activerecord/test/base_test.rb
index c4c0742b7b..2f834772a6 100755
--- a/activerecord/test/base_test.rb
+++ b/activerecord/test/base_test.rb
@@ -1094,21 +1094,21 @@ class BasicsTest < Test::Unit::TestCase
end
assert_equal res, res3
- res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id"
+ res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
res5 = nil
assert_nothing_raised do
- res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
- :joins => "p, comments c",
+ res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
+ :joins => "p, comments co",
:select => "p.id")
end
assert_equal res4, res5
- res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id"
+ res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
res7 = nil
assert_nothing_raised do
- res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id",
- :joins => "p, comments c",
+ res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
+ :joins => "p, comments co",
:select => "p.id",
:distinct => true)
end
diff --git a/activerecord/test/connections/native_frontbase/connection.rb b/activerecord/test/connections/native_frontbase/connection.rb
new file mode 100644
index 0000000000..e3d1d5b1e1
--- /dev/null
+++ b/activerecord/test/connections/native_frontbase/connection.rb
@@ -0,0 +1,26 @@
+puts 'Using native Frontbase'
+require_dependency 'fixtures/course'
+require 'logger'
+
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+db1 = 'activerecord_unittest'
+db2 = 'activerecord_unittest2'
+
+ActiveRecord::Base.establish_connection(
+ :adapter => "frontbase",
+ :host => "localhost",
+ :username => "rails",
+ :password => "",
+ :database => db1,
+ :session_name => "unittest-#{$$}"
+)
+
+Course.establish_connection(
+ :adapter => "frontbase",
+ :host => "localhost",
+ :username => "rails",
+ :password => "",
+ :database => db2,
+ :session_name => "unittest-#{$$}"
+)
diff --git a/activerecord/test/deprecated_finder_test.rb b/activerecord/test/deprecated_finder_test.rb
index 35c8a2379a..c1065b0026 100755
--- a/activerecord/test/deprecated_finder_test.rb
+++ b/activerecord/test/deprecated_finder_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'fixtures/company'
require 'fixtures/topic'
+require 'fixtures/reply'
require 'fixtures/entrant'
require 'fixtures/developer'
diff --git a/activerecord/test/finder_test.rb b/activerecord/test/finder_test.rb
index 98537ecad7..f23e383115 100644
--- a/activerecord/test/finder_test.rb
+++ b/activerecord/test/finder_test.rb
@@ -13,6 +13,12 @@ class FinderTest < Test::Unit::TestCase
assert_equal(topics(:first).title, Topic.find(1).title)
end
+ # find should handle strings that come from URLs
+ # (example: Category.find(params[:id]))
+ def test_find_with_string
+ assert_equal(Topic.find(1).title,Topic.find("1").title)
+ end
+
def test_exists
assert (Topic.exists?(1))
assert !(Topic.exists?(45))
diff --git a/activerecord/test/fixtures/db_definitions/frontbase.drop.sql b/activerecord/test/fixtures/db_definitions/frontbase.drop.sql
new file mode 100644
index 0000000000..231af7895c
--- /dev/null
+++ b/activerecord/test/fixtures/db_definitions/frontbase.drop.sql
@@ -0,0 +1,30 @@
+DROP TABLE accounts CASCADE;
+DROP TABLE funny_jokes CASCADE;
+DROP TABLE companies CASCADE;
+DROP TABLE topics CASCADE;
+DROP TABLE developers CASCADE;
+DROP TABLE projects CASCADE;
+DROP TABLE developers_projects CASCADE;
+DROP TABLE orders CASCADE;
+DROP TABLE customers CASCADE;
+DROP TABLE movies CASCADE;
+DROP TABLE subscribers CASCADE;
+DROP TABLE booleantests CASCADE;
+DROP TABLE auto_id_tests CASCADE;
+DROP TABLE entrants CASCADE;
+DROP TABLE colnametests CASCADE;
+DROP TABLE mixins CASCADE;
+DROP TABLE people CASCADE;
+DROP TABLE readers CASCADE;
+DROP TABLE binaries CASCADE;
+DROP TABLE computers CASCADE;
+DROP TABLE posts CASCADE;
+DROP TABLE comments CASCADE;
+DROP TABLE authors CASCADE;
+DROP TABLE tasks CASCADE;
+DROP TABLE categories CASCADE;
+DROP TABLE categories_posts CASCADE;
+DROP TABLE fk_test_has_fk CASCADE;
+DROP TABLE fk_test_has_pk CASCADE;
+DROP TABLE keyboards CASCADE;
+DROP TABLE legacy_things CASCADE;
diff --git a/activerecord/test/fixtures/db_definitions/frontbase.sql b/activerecord/test/fixtures/db_definitions/frontbase.sql
new file mode 100644
index 0000000000..13ce65297b
--- /dev/null
+++ b/activerecord/test/fixtures/db_definitions/frontbase.sql
@@ -0,0 +1,251 @@
+CREATE TABLE accounts (
+ id integer DEFAULT unique,
+ firm_id integer,
+ credit_limit integer,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR accounts(id);
+
+CREATE TABLE funny_jokes (
+ id integer DEFAULT unique,
+ firm_id integer default NULL,
+ name character varying(50),
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR funny_jokes(id);
+
+CREATE TABLE companies (
+ id integer DEFAULT unique,
+ "type" character varying(50),
+ "ruby_type" character varying(50),
+ firm_id integer,
+ name character varying(50),
+ client_of integer,
+ rating integer default 1,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR companies(id);
+
+CREATE TABLE topics (
+ id integer DEFAULT unique,
+ title character varying(255),
+ author_name character varying(255),
+ author_email_address character varying(255),
+ written_on timestamp,
+ bonus_time time,
+ last_read date,
+ content varchar(65536),
+ approved boolean default true,
+ replies_count integer default 0,
+ parent_id integer,
+ "type" character varying(50),
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR topics(id);
+
+CREATE TABLE developers (
+ id integer DEFAULT unique,
+ name character varying(100),
+ salary integer DEFAULT 70000,
+ created_at timestamp,
+ updated_at timestamp,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR developers(id);
+
+CREATE TABLE projects (
+ id integer DEFAULT unique,
+ name character varying(100),
+ type varchar(255),
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR projects(id);
+
+CREATE TABLE developers_projects (
+ developer_id integer NOT NULL,
+ project_id integer NOT NULL,
+ joined_on date,
+ access_level integer default 1
+);
+
+CREATE TABLE orders (
+ id integer DEFAULT unique,
+ name character varying(100),
+ billing_customer_id integer,
+ shipping_customer_id integer,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR orders(id);
+
+CREATE TABLE customers (
+ id integer DEFAULT unique,
+ name character varying(100),
+ balance integer default 0,
+ address_street character varying(100),
+ address_city character varying(100),
+ address_country character varying(100),
+ gps_location character varying(100),
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR customers(id);
+
+CREATE TABLE movies (
+ movieid integer DEFAULT unique,
+ name varchar(65536),
+ PRIMARY KEY (movieid)
+);
+SET UNIQUE FOR movies(movieid);
+
+CREATE TABLE subscribers (
+ nick varchar(65536) NOT NULL,
+ name varchar(65536),
+ PRIMARY KEY (nick)
+);
+
+CREATE TABLE booleantests (
+ id integer DEFAULT unique,
+ value boolean,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR booleantests(id);
+
+CREATE TABLE auto_id_tests (
+ auto_id integer DEFAULT unique,
+ value integer,
+ PRIMARY KEY (auto_id)
+);
+SET UNIQUE FOR auto_id_tests(auto_id);
+
+CREATE TABLE entrants (
+ id integer DEFAULT unique,
+ name varchar(65536),
+ course_id integer,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR entrants(id);
+
+CREATE TABLE colnametests (
+ id integer DEFAULT unique,
+ "references" integer NOT NULL,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR colnametests(id);
+
+CREATE TABLE mixins (
+ id integer DEFAULT unique,
+ parent_id integer,
+ type character varying(100),
+ pos integer,
+ lft integer,
+ rgt integer,
+ root_id integer,
+ created_at timestamp,
+ updated_at timestamp,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR mixins(id);
+
+CREATE TABLE people (
+ id integer DEFAULT unique,
+ first_name varchar(65536),
+ lock_version integer default 0,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR people(id);
+
+CREATE TABLE readers (
+ id integer DEFAULT unique,
+ post_id INTEGER NOT NULL,
+ person_id INTEGER NOT NULL,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR readers(id);
+
+CREATE TABLE binaries (
+ id integer DEFAULT unique,
+ data BLOB,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR binaries(id);
+
+CREATE TABLE computers (
+ id integer DEFAULT unique,
+ developer integer NOT NULL,
+ "extendedWarranty" integer NOT NULL,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR computers(id);
+
+CREATE TABLE posts (
+ id integer DEFAULT unique,
+ author_id integer,
+ title varchar(255),
+ type varchar(255),
+ body varchar(65536),
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR posts(id);
+
+CREATE TABLE comments (
+ id integer DEFAULT unique,
+ post_id integer,
+ type varchar(255),
+ body varchar(65536),
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR comments(id);
+
+CREATE TABLE authors (
+ id integer DEFAULT unique,
+ name varchar(255) default NULL,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR authors(id);
+
+CREATE TABLE tasks (
+ id integer DEFAULT unique,
+ starting timestamp,
+ ending timestamp,
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR tasks(id);
+
+CREATE TABLE categories (
+ id integer DEFAULT unique,
+ name varchar(255),
+ type varchar(255),
+ PRIMARY KEY (id)
+);
+SET UNIQUE FOR categories(id);
+
+CREATE TABLE categories_posts (
+ category_id integer NOT NULL,
+ post_id integer NOT NULL
+);
+
+CREATE TABLE fk_test_has_pk (
+ id INTEGER NOT NULL PRIMARY KEY
+);
+SET UNIQUE FOR fk_test_has_pk(id);
+
+CREATE TABLE fk_test_has_fk (
+ id INTEGER NOT NULL PRIMARY KEY,
+ fk_id INTEGER NOT NULL REFERENCES fk_test_has_fk(id)
+);
+SET UNIQUE FOR fk_test_has_fk(id);
+
+CREATE TABLE keyboards (
+ key_number integer DEFAULT unique,
+ "name" character varying(50),
+ PRIMARY KEY (key_number)
+);
+SET UNIQUE FOR keyboards(key_number);
+
+create table "legacy_things"
+(
+ "id" int,
+ "tps_report_number" int default NULL,
+ "version" int default 0 not null,
+ primary key ("id")
+);
+SET UNIQUE FOR legacy_things(id);
diff --git a/activerecord/test/fixtures/db_definitions/frontbase2.drop.sql b/activerecord/test/fixtures/db_definitions/frontbase2.drop.sql
new file mode 100644
index 0000000000..17b9ad46d7
--- /dev/null
+++ b/activerecord/test/fixtures/db_definitions/frontbase2.drop.sql
@@ -0,0 +1 @@
+DROP TABLE courses CASCADE;
diff --git a/activerecord/test/fixtures/db_definitions/frontbase2.sql b/activerecord/test/fixtures/db_definitions/frontbase2.sql
new file mode 100644
index 0000000000..42f4bb78c9
--- /dev/null
+++ b/activerecord/test/fixtures/db_definitions/frontbase2.sql
@@ -0,0 +1,4 @@
+CREATE TABLE courses (
+ id integer DEFAULT unique,
+ name varchar(100)
+);
diff --git a/activerecord/test/migration_test.rb b/activerecord/test/migration_test.rb
index c6a1d92481..e238eab1a2 100644
--- a/activerecord/test/migration_test.rb
+++ b/activerecord/test/migration_test.rb
@@ -58,7 +58,7 @@ if ActiveRecord::Base.connection.supports_migrations?
# quoting
assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key", :unique => true) }
- assert_nothing_raised { Person.connection.remove_index("people", :name => "key") }
+ assert_nothing_raised { Person.connection.remove_index("people", :name => "key", :unique => true) }
# Sybase adapter does not support indexes on :boolean columns
unless current_adapter?(:SybaseAdapter)
@@ -461,27 +461,29 @@ if ActiveRecord::Base.connection.supports_migrations?
Reminder.reset_sequence_name
end
- def test_create_table_with_binary_column
- Person.connection.drop_table :binary_testings rescue nil
+# FrontBase does not support default values on BLOB/CLOB columns
+ unless current_adapter?(:FrontBaseAdapter)
+ def test_create_table_with_binary_column
+ Person.connection.drop_table :binary_testings rescue nil
- assert_nothing_raised {
- Person.connection.create_table :binary_testings do |t|
- t.column "data", :binary, :default => "", :null => false
- end
- }
+ assert_nothing_raised {
+ Person.connection.create_table :binary_testings do |t|
+ t.column "data", :binary, :default => "", :null => false
+ end
+ }
- columns = Person.connection.columns(:binary_testings)
- data_column = columns.detect { |c| c.name == "data" }
+ columns = Person.connection.columns(:binary_testings)
+ data_column = columns.detect { |c| c.name == "data" }
- if current_adapter?(:OracleAdapter)
- assert_equal "empty_blob()", data_column.default
- else
- assert_equal "", data_column.default
- end
+ if current_adapter?(:OracleAdapter)
+ assert_equal "empty_blob()", data_column.default
+ else
+ assert_equal "", data_column.default
+ end
- Person.connection.drop_table :binary_testings rescue nil
+ Person.connection.drop_table :binary_testings rescue nil
+ end
end
-
def test_migrator_with_duplicates
assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_duplicate/', nil)
diff --git a/activerecord/test/pk_test.rb b/activerecord/test/pk_test.rb
index 839a784e4a..604b263571 100644
--- a/activerecord/test/pk_test.rb
+++ b/activerecord/test/pk_test.rb
@@ -1,5 +1,6 @@
require "#{File.dirname(__FILE__)}/abstract_unit"
require 'fixtures/topic'
+require 'fixtures/reply'
require 'fixtures/subscriber'
require 'fixtures/movie'
require 'fixtures/keyboard'
diff --git a/activerecord/test/reflection_test.rb b/activerecord/test/reflection_test.rb
index 4882ca246f..907d02193f 100644
--- a/activerecord/test/reflection_test.rb
+++ b/activerecord/test/reflection_test.rb
@@ -45,6 +45,12 @@ class ReflectionTest < Test::Unit::TestCase
assert_equal :string, @first.column_for_attribute("title").type
assert_equal 255, @first.column_for_attribute("title").limit
end
+
+ def test_column_null_not_null
+ subscriber = Subscriber.find(:first)
+ assert subscriber.column_for_attribute("name").null
+ assert !subscriber.column_for_attribute("nick").null
+ end
def test_human_name_for_column
assert_equal "Author name", @first.column_for_attribute("author_name").human_name
diff --git a/activerecord/test/threaded_connections_test.rb b/activerecord/test/threaded_connections_test.rb
index d9cc47eef3..a812ec642c 100644
--- a/activerecord/test/threaded_connections_test.rb
+++ b/activerecord/test/threaded_connections_test.rb
@@ -1,10 +1,11 @@
require 'abstract_unit'
require 'fixtures/topic'
-class ThreadedConnectionsTest < Test::Unit::TestCase
- self.use_transactional_fixtures = false
+unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
+ class ThreadedConnectionsTest < Test::Unit::TestCase
+ self.use_transactional_fixtures = false
- fixtures :topics
+ fixtures :topics
def setup
@connection = ActiveRecord::Base.remove_connection
@@ -25,21 +26,22 @@ class ThreadedConnectionsTest < Test::Unit::TestCase
ActiveRecord::Base.allow_concurrency = use_threaded_connections
ActiveRecord::Base.establish_connection(@connection)
- 5.times do
- Thread.new do
- Topic.find :first
- @connections << ActiveRecord::Base.active_connections.values.first
- end.join
+ 5.times do
+ Thread.new do
+ Topic.find :first
+ @connections << ActiveRecord::Base.active_connections.values.first
+ end.join
+ end
end
- end
- def test_threaded_connections
- gather_connections(true)
- assert_equal @connections.uniq.length, 5
- end
+ def test_threaded_connections
+ gather_connections(true)
+ assert_equal @connections.uniq.length, 5
+ end
- def test_unthreaded_connections
- gather_connections(false)
- assert_equal @connections.uniq.length, 1
+ def test_unthreaded_connections
+ gather_connections(false)
+ assert_equal @connections.uniq.length, 1
+ end
end
end
diff --git a/activerecord/test/transactions_test.rb b/activerecord/test/transactions_test.rb
index 0048e24bc7..421da4d7a6 100644
--- a/activerecord/test/transactions_test.rb
+++ b/activerecord/test/transactions_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'fixtures/topic'
+require 'fixtures/reply'
require 'fixtures/developer'
class TransactionTest < Test::Unit::TestCase