aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/connection_adapters/openbase_adapter.rb')
-rw-r--r--activerecord/lib/active_record/connection_adapters/openbase_adapter.rb459
1 files changed, 0 insertions, 459 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb b/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb
deleted file mode 100644
index 43c7dc1ed8..0000000000
--- a/activerecord/lib/active_record/connection_adapters/openbase_adapter.rb
+++ /dev/null
@@ -1,459 +0,0 @@
-require 'active_record/connection_adapters/abstract_adapter'
-
-module ActiveRecord
- class Base
- # Establishes a connection to the database that's used by all Active Record objects
- def self.openbase_connection(config) # :nodoc:
- require_library_or_gem 'openbase' unless self.class.const_defined?(:OpenBase)
-
- config = config.symbolize_keys
- host = config[:host]
- username = config[:username].to_s
- password = config[:password].to_s
-
- if config.has_key?(:database)
- database = config[:database]
- else
- raise ArgumentError, "No database specified. Missing argument: database."
- end
-
- oba = ConnectionAdapters::OpenBaseAdapter.new(
- OpenBase.new(database, host, username, password), logger
- )
-
- if oba.raw_connection.connected?
- unless oba.tables.include?(ConnectionAdapters::OpenBaseAdapter::COLUMN_SUPPORT_TABLE)
- oba.execute(<<-SQL,"Creating OpenBase Column Support Table")
- CREATE TABLE #{ConnectionAdapters::OpenBaseAdapter::COLUMN_SUPPORT_TABLE} (name char, type char, precision int, scale int)
- SQL
- end
- oba.select_all("SELECT * FROM #{ConnectionAdapters::OpenBaseAdapter::COLUMN_SUPPORT_TABLE}").each do |col|
- ConnectionAdapters::OpenBaseAdapter::DECIMAL_COLUMNS.store(col["name"],[col["precision"],col["scale"]])
- end
- end
-
- oba
- end
- end
-
- module ConnectionAdapters
- class OpenBaseColumn < Column #:nodoc:
- private
- def simplified_type(field_type)
- return :integer if field_type.downcase =~ /long/
- return :decimal if field_type.downcase == "money"
- return :binary if field_type.downcase == "object"
- super
- end
- end
-
- # The OpenBase adapter works with the Ruby/Openbase driver by Derrick Spell,
- # provided with the distribution of OpenBase 10.0.6 and later
- # http://www.openbase.com
- #
- # Options:
- #
- # * <tt>:host</tt> -- Defaults to localhost
- # * <tt>:username</tt> -- Defaults to nothing
- # * <tt>:password</tt> -- Defaults to nothing
- # * <tt>:database</tt> -- The name of the database. No default, must be provided.
- #
- # The OpenBase adapter will make use of OpenBase's ability to generate unique ids
- # for any column with an unique index applied. Thus, if the value of a primary
- # key is not specified at the time an INSERT is performed, the adapter will prefetch
- # a unique id for the primary key. This prefetching is also necessary in order
- # to return the id after an insert.
- #
- #
- # Maintainer: derrick.spell@gmail.com
- class OpenBaseAdapter < AbstractAdapter
- DECIMAL_COLUMNS = {}
- COLUMN_SUPPORT_TABLE = "rails_openbase_column_support"
- def adapter_name
- 'OpenBase'
- end
-
- def native_database_types
- {
- :primary_key => "integer NOT NULL UNIQUE INDEX DEFAULT _rowid",
- :string => { :name => "char", :limit => 4096 },
- :text => { :name => "text" },
- :integer => { :name => "integer" },
- :float => { :name => "float" },
- :decimal => { :name => "decimal" },
- :datetime => { :name => "datetime" },
- :timestamp => { :name => "timestamp" },
- :time => { :name => "time" },
- :date => { :name => "date" },
- :binary => { :name => "object" },
- :boolean => { :name => "boolean" }
- }
- end
-
- def supports_migrations?
- true
- end
-
- def prefetch_primary_key?(table_name = nil)
- true
- end
-
- def default_sequence_name(table_name, primary_key) # :nodoc:
- "#{table_name} #{primary_key}"
- end
-
- def next_sequence_value(sequence_name)
- ary = sequence_name.split(' ')
- if (!ary[1]) then
- ary[0] =~ /(\w+)_nonstd_seq/
- ary[0] = $1
- end
- @connection.unique_row_id(ary[0], ary[1])
- end
-
-
- # QUOTING ==================================================
-
- def quote(value, column = nil)
- if value.kind_of?(String) && column && column.type == :binary
- "'#{@connection.insert_binary(value)}'"
- elsif value.kind_of?(BigDecimal)
- return "'#{value.to_s}'"
- elsif column && column.type == :integer && column.sql_type =~ /decimal/
- return "'#{value.to_s}'"
- elsif [Float,Fixnum,Bignum].include?(value.class) && column && column.type == :string
- return "'#{value.to_s}'"
- else
- super
- end
- end
-
- def quoted_true
- "1"
- end
-
- def quoted_false
- "0"
- end
-
-
-
- # DATABASE STATEMENTS ======================================
-
- def add_limit_offset!(sql, options) #:nodoc:
- return if options[:limit].nil?
- limit = options[:limit]
- offset = options[:offset]
- if limit == 0
- # Mess with the where clause to ensure we get no results
- if sql =~ /WHERE/i
- sql.sub!(/WHERE/i, 'WHERE 1 = 2 AND ')
- elsif sql =~ /ORDER\s+BY/i
- sql.sub!(/ORDER\s+BY/i, 'WHERE 1 = 2 ORDER BY')
- else
- sql << 'WHERE 1 = 2'
- end
- elsif offset.nil?
- sql << " RETURN RESULTS #{limit}"
- else
- sql << " RETURN RESULTS #{offset} TO #{limit + offset}"
- end
- end
-
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
- execute(sql, name)
- update_nulls_after_insert(sql, name, pk, id_value, sequence_name)
- id_value
- end
-
- def execute(sql, name = nil) #:nodoc:
- log(sql, name) { @connection.execute(sql) }
- end
-
- def direct_execute(sql, name = nil) #:nodoc:
- log(sql, name) { @connection.execute(sql) }
- end
-
- def update(sql, name = nil) #:nodoc:
- execute(sql, name).rows_affected
- end
-
- alias_method :delete, :update #:nodoc:
-
- def begin_db_transaction #:nodoc:
- execute "START TRANSACTION"
- rescue Exception
- # Transactions aren't supported
- end
-
- def commit_db_transaction #:nodoc:
- execute "COMMIT"
- rescue Exception
- # Transactions aren't supported
- end
-
- def rollback_db_transaction #:nodoc:
- execute "ROLLBACK"
- rescue Exception
- # Transactions aren't supported
- end
-
-
- # SCHEMA STATEMENTS ========================================
- # Return the list of all tables in the schema search path.
- def tables(name = nil) #:nodoc:
- tables = @connection.tables
- tables.reject! { |t| /\A_SYS_/ === t }
- end
-
- def columns(table_name, name = nil) #:nodoc:
- sql = "SELECT * FROM _sys_tables "
- sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
- sql << "ORDER BY columnNumber"
- columns = []
- direct_execute(sql, name).each_hash do |row|
- columns << OpenBaseColumn.new(row["fieldname"],
- default_value(row["defaultvalue"],row["typename"]),
- sql_type_name(table_name,row["fieldname"],row["typename"],row["length"]),
- row["notnull"] == 1 ? false : true)
- end
- columns
- end
-
- def column_names(table_name) #:nodoc:
- sql = "SELECT fieldname FROM _sys_tables "
- sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
- sql << "ORDER BY columnNumber"
- names = direct_execute(sql).fetch_all
- names.flatten! || names
- end
-
- def indexes(table_name, name = nil)#:nodoc:
- sql = "SELECT fieldname, notnull, searchindex, uniqueindex, clusteredindex FROM _sys_tables "
- sql << "WHERE tablename='#{table_name}' AND INDEXOF(fieldname,'_')<>0 "
- sql << "AND primarykey=0 "
- sql << "AND (searchindex=1 OR uniqueindex=1 OR clusteredindex=1) "
- sql << "ORDER BY columnNumber"
- indexes = []
- execute(sql, name).each do |row|
- indexes << IndexDefinition.new(table_name,ob_index_name(row),row[3]==1,[row[0]])
- end
- indexes
- end
-
- def create_table(name, options = {}) #:nodoc:
- return_value = super
-
- # Get my own copy of TableDefinition so that i can detect decimal columns
- table_definition = TableDefinition.new(self)
- yield table_definition
-
- table_definition.columns.each do |col|
- if col.type == :decimal
- record_decimal(name, col.name, col.precision, col.scale)
- end
- end
-
- unless options[:id] == false
- primary_key = (options[:primary_key] || "id")
- direct_execute("CREATE PRIMARY KEY #{name} (#{primary_key})")
- end
- return_value
- end
-
- def rename_table(name, new_name)
- execute "RENAME #{name} #{new_name}"
- end
-
- def add_column(table_name, column_name, type, options = {})
- return_value = super(table_name, "COLUMN " + column_name.to_s, type, options)
- if type == :decimal
- record_decimal(table_name, column_name, options[:precision], options[:scale])
- end
- end
-
- def remove_column(table_name, column_name)
- execute "ALTER TABLE #{table_name} REMOVE COLUMN #{quote_column_name(column_name)}"
- end
-
- def rename_column(table_name, column_name, new_column_name)
- execute "ALTER TABLE #{table_name} RENAME #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
- end
-
- def add_column_options!(sql, options) #:nodoc:
- sql << " NOT NULL" if options[:null] == false
- sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
- end
-
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
- unless options_include_default?(options)
- options[:default] = select_one("SELECT * FROM _sys_tables WHERE tablename='#{table_name}' AND fieldname='#{column_name}'")["defaultvalue"]
- end
-
- change_column_sql = "ALTER TABLE #{table_name} ADD COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(change_column_sql, options)
- execute(change_column_sql)
- end
-
- def change_column_default(table_name, column_name, default)
- execute "ALTER TABLE #{table_name} COLUMN #{column_name} SET DEFAULT #{quote(default)}"
- end
-
- def add_index(table_name, column_name, options = {})
- if Hash === options # legacy support, since this param was a string
- index_type = options[:unique] ? "UNIQUE" : ""
- else
- index_type = options
- end
- execute "CREATE #{index_type} INDEX #{table_name} #{column_name}"
- end
-
- def remove_index(table_name, options = {})
- execute "DROP INDEX #{table_name} #{options === Hash ? options[:column] : options}"
- end
-
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
- return super unless type.to_s == 'decimal'
-
- if (scale.to_i == 2)
- return 'money'
- elsif (scale.to_i == 0)
- return 'longlong'
- else
- return "char(#{precision.to_i + 1})"
- end
- end
-
-
- private
- def select(sql, name = nil)
- decimals = detect_decimals(sql) || []
- sql = add_order_by_rowid(sql)
-
- # OpenBase ignores the return results when there is a group by
- # so limit the result set that we return to rails if need be
- if (sql =~ /GROUP BY/i)
- sql.sub!(/RETURN RESULTS (\d+)( TO (\d+))?/i,"")
-
- results = execute(sql, name)
- if ($2)
- results.fetch_offset = $1.to_i
- results.fetch_limit = $3.to_i - $1.to_i
- elsif ($1)
- results.fetch_limit = $1.to_i
- end
- else
- results = execute(sql, name)
- end
-
- rows = []
- if ( results.rows_affected )
- results.each_hash do |row| # loop through result rows
- row.delete("_rowid") if row.key?("_rowid")
- decimals.each do |name, precision, scale|
- row[name] = BigDecimal.new(row[name]) if row[name] === String
- end
- rows << row
- end
- end
- rows
- end
-
- def default_value(value,type=nil)
- return value if value.nil?
-
- # Boolean type values
- return true if value =~ /true/
- return false if value =~ /false/
- # Alternative boolean default declarations
- return true if (value == 1 && type == "boolean")
- return false if (value == 0 && type == "boolean")
-
- # Date / Time magic values
- return Time.now.to_s if value =~ /^now\(\)/i
-
- # Empty strings should be set to nil
- return nil if value.empty?
-
- # Otherwise return what we got from OpenBase
- # and hope for the best...
- # Take off the leading space and unquote
- value.lstrip!
- value = value[1,value.length-2] if value.first.eql?("'") && value.last.eql?("'")
- return nil if value.eql?("NULL")
- return value
- end
-
- def sql_type_name(table_name, col_name, type, length)
- full_name = table_name.to_s + "." + col_name.to_s
- if DECIMAL_COLUMNS.include?(full_name) && type != "longlong"
- return "decimal(#{DECIMAL_COLUMNS[full_name][0]},#{DECIMAL_COLUMNS[full_name][1]})"
- end
- return "#{type}(#{length})" if ( type =~ /char/ )
- type
- end
-
- def ob_index_name(row = [])
- name = ""
- name << "UNIQUE " if row[3]
- name << "CLUSTERED " if row[4]
- name << "INDEX"
- name
- end
-
- def detect_decimals(sql)
- # Detect any decimal columns that will need to be cast when fetched
- decimals = []
- sql =~ /SELECT\s+(.*)\s+FROM\s+(\w+)/i
- select_clause = $1
- main_table = $2
- if select_clause == "*"
- column_names(main_table).each do |col|
- full_name = main_table + "." + col
- if DECIMAL_COLUMNS.include?(full_name)
- decimals << [col,DECIMAL_COLUMNS[full_name][0].to_i,DECIMAL_COLUMNS[full_name][1].to_i]
- end
- end
- end
- return decimals
- end
-
- def add_order_by_rowid(sql)
- # ORDER BY _rowid if no explicit ORDER BY
- # This will ensure that find(:first) returns the first inserted row
- if (sql !~ /(ORDER BY)|(GROUP BY)/)
- if (sql =~ /RETURN RESULTS/)
- sql.sub!(/RETURN RESULTS/,"ORDER BY _rowid RETURN RESULTS")
- else
- sql << " ORDER BY _rowid"
- end
- end
- sql
- end
-
- def record_decimal(table_name, column_name, precision, scale)
- full_name = table_name.to_s + "." + column_name.to_s
- DECIMAL_COLUMNS.store(full_name, [precision.to_i,scale.to_i])
- direct_execute("INSERT INTO #{COLUMN_SUPPORT_TABLE} (name,type,precision,scale) VALUES ('#{full_name}','decimal',#{precision.to_i},#{scale.to_i})")
- end
-
- def update_nulls_after_insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- sql =~ /INSERT INTO (\w+) \((.*)\) VALUES\s*\((.*)\)/m
- table = $1
- cols = $2
- values = $3
- cols = cols.split(',')
- values.gsub!(/'[^']*'/,"''")
- values.gsub!(/"[^"]*"/,"\"\"")
- values = values.split(',')
- update_cols = []
- values.each_index { |index| update_cols << cols[index] if values[index] =~ /\s*NULL\s*/ }
- update_sql = "UPDATE #{table} SET"
- update_cols.each { |col| update_sql << " #{col}=NULL," unless col.empty? }
- update_sql.chop!()
- update_sql << " WHERE #{pk}=#{quote(id_value)}"
- direct_execute(update_sql,"Null Correction") if update_cols.size > 0
- end
- end
- end
-end