mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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
This commit is contained in:
parent
dd5397a57c
commit
b2c0ddf033
21 changed files with 1284 additions and 57 deletions
|
@ -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]
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
26
activerecord/test/connections/native_frontbase/connection.rb
Normal file
26
activerecord/test/connections/native_frontbase/connection.rb
Normal file
|
@ -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-#{$$}"
|
||||
)
|
|
@ -1,6 +1,7 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/company'
|
||||
require 'fixtures/topic'
|
||||
require 'fixtures/reply'
|
||||
require 'fixtures/entrant'
|
||||
require 'fixtures/developer'
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
30
activerecord/test/fixtures/db_definitions/frontbase.drop.sql
vendored
Normal file
30
activerecord/test/fixtures/db_definitions/frontbase.drop.sql
vendored
Normal file
|
@ -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;
|
251
activerecord/test/fixtures/db_definitions/frontbase.sql
vendored
Normal file
251
activerecord/test/fixtures/db_definitions/frontbase.sql
vendored
Normal file
|
@ -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);
|
1
activerecord/test/fixtures/db_definitions/frontbase2.drop.sql
vendored
Normal file
1
activerecord/test/fixtures/db_definitions/frontbase2.drop.sql
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
DROP TABLE courses CASCADE;
|
4
activerecord/test/fixtures/db_definitions/frontbase2.sql
vendored
Normal file
4
activerecord/test/fixtures/db_definitions/frontbase2.sql
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE courses (
|
||||
id integer DEFAULT unique,
|
||||
name varchar(100)
|
||||
);
|
|
@ -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
|
||||
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
|
||||
end
|
||||
|
||||
Person.connection.drop_table :binary_testings rescue nil
|
||||
end
|
||||
|
||||
def test_migrator_with_duplicates
|
||||
assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
|
||||
ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_duplicate/', nil)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
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
|
||||
end
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require 'abstract_unit'
|
||||
require 'fixtures/topic'
|
||||
require 'fixtures/reply'
|
||||
require 'fixtures/developer'
|
||||
|
||||
class TransactionTest < Test::Unit::TestCase
|
||||
|
|
Loading…
Reference in a new issue