Skip to content

Commit

Permalink
First shot at splitting firebird adapter into specific and shared par…
Browse files Browse the repository at this point in the history
…ts, with a jdbc subadapter
  • Loading branch information
jeremyevans committed Sep 27, 2011
1 parent 7d05fed commit 59c36db
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 186 deletions.
190 changes: 4 additions & 186 deletions lib/sequel/adapters/firebird.rb
@@ -1,14 +1,15 @@
require 'fb'
Sequel.require 'adapters/shared/firebird'

module Sequel
# The Sequel Firebird adapter requires the ruby fb driver located at
# http://github.com/wishdev/fb.
module Firebird
class Database < Sequel::Database
include Sequel::Firebird::DatabaseMethods

set_adapter_scheme :firebird

AUTO_INCREMENT = ''.freeze
TEMPORARY = 'GLOBAL TEMPORARY '.freeze
DISCONNECT_ERRORS = /Unsuccessful execution caused by a system error that precludes successful execution of subsequent statements/

# Add the primary_keys instance variables.
Expand All @@ -27,18 +28,10 @@ def connect(server)
:password => opts[:password]).connect
end

def create_trigger(*args)
self << create_trigger_sql(*args)
end

def dataset(opts = nil)
Firebird::Dataset.new(self, opts)
end

def drop_sequence(name)
self << drop_sequence_sql(name)
end

def execute(sql, opts={})
begin
synchronize(opts[:server]) do |conn|
Expand All @@ -55,33 +48,6 @@ def execute(sql, opts={})
end
end

# Return primary key for the given table.
def primary_key(table)
t = dataset.send(:input_identifier, table)
@primary_keys.fetch(t) do
pk = fetch("SELECT RDB$FIELD_NAME FROM RDB$INDEX_SEGMENTS NATURAL JOIN RDB$RELATION_CONSTRAINTS WHERE RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' AND RDB$RELATION_NAME = ?", t).single_value
@primary_keys[t] = dataset.send(:output_identifier, pk.rstrip) if pk
end
end

def drop_table(*names)
clear_primary_key(*names)
super
end

def clear_primary_key(*tables)
tables.each{|t| @primary_keys.delete(dataset.send(:input_identifier, t))}
end

def restart_sequence(*args)
self << restart_sequence_sql(*args)
end

def sequences(opts={})
ds = self[:"rdb$generators"].server(opts[:server]).filter(:"rdb$system_flag" => 0).select(:"rdb$generator_name")
block_given? ? yield(ds) : ds.map{|r| ds.send(:output_identifier, r[:"rdb$generator_name"])}
end

def tables(opts={})
tables_or_views(0, opts)
end
Expand All @@ -97,26 +63,6 @@ def tables_or_views(type, opts)
ds.map{|r| ds.send(:output_identifier, r[:"rdb$relation_name"].rstrip)}
end

# Use Firebird specific syntax for add column
def alter_table_sql(table, op)
case op[:op]
when :add_column
"ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
when :drop_column
"ALTER TABLE #{quote_schema_table(table)} DROP #{column_definition_sql(op)}"
when :rename_column
"ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TO #{quote_identifier(op[:new_name])}"
when :set_column_type
"ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TYPE #{type_literal(op)}"
else
super(table, op)
end
end

def auto_increment_sql()
AUTO_INCREMENT
end

def begin_transaction(conn, opts={})
log_yield(TRANSACTION_BEGIN) do
begin
Expand All @@ -133,60 +79,6 @@ def commit_transaction(conn, opts={})
log_yield(TRANSACTION_COMMIT){conn.commit}
end

def create_sequence_sql(name, opts={})
"CREATE SEQUENCE #{quote_identifier(name)}"
end

# Firebird gets an override because of the mess of creating a
# sequence and trigger for auto-incrementing primary keys.
def create_table_from_generator(name, generator, options)
drop_statement, create_statements = create_table_sql_list(name, generator, options)
(execute_ddl(drop_statement) rescue nil) if drop_statement
create_statements.each{|sql| execute_ddl(sql)}
end

def create_table_sql_list(name, generator, options={})
statements = [create_table_sql(name, generator, options)]
drop_seq_statement = nil
generator.columns.each do |c|
if c[:auto_increment]
c[:sequence_name] ||= "seq_#{name}_#{c[:name]}"
unless c[:create_sequence] == false
drop_seq_statement = drop_sequence_sql(c[:sequence_name])
statements << create_sequence_sql(c[:sequence_name])
statements << restart_sequence_sql(c[:sequence_name], {:restart_position => c[:sequence_start_position]}) if c[:sequence_start_position]
end
unless c[:create_trigger] == false
c[:trigger_name] ||= "BI_#{name}_#{c[:name]}"
c[:quoted_name] = quote_identifier(c[:name])
trigger_definition = <<-END
begin
if ((new.#{c[:quoted_name]} is null) or (new.#{c[:quoted_name]} = 0)) then
begin
new.#{c[:quoted_name]} = next value for #{c[:sequence_name]};
end
end
END
statements << create_trigger_sql(name, c[:trigger_name], trigger_definition, {:events => [:insert]})
end
end
end
[drop_seq_statement, statements]
end

def create_trigger_sql(table, name, definition, opts={})
events = opts[:events] ? Array(opts[:events]) : [:insert, :update, :delete]
whence = opts[:after] ? 'AFTER' : 'BEFORE'
inactive = opts[:inactive] ? 'INACTIVE' : 'ACTIVE'
position = opts.fetch(:position, 0)
sql = <<-end_sql
CREATE TRIGGER #{quote_identifier(name)} for #{quote_identifier(table)}
#{inactive} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} position #{position}
as #{definition}
end_sql
sql
end

def database_error_classes
[Fb::Error]
end
Expand All @@ -195,32 +87,14 @@ def disconnect_connection(c)
c.close
end

def drop_sequence_sql(name)
"DROP SEQUENCE #{quote_identifier(name)}"
end

def restart_sequence_sql(name, opts={})
seq_name = quote_identifier(name)
"ALTER SEQUENCE #{seq_name} RESTART WITH #{opts[:restart_position]}"
end

def rollback_transaction(conn, opts={})
log_yield(TRANSACTION_ROLLBACK){conn.rollback}
end

def type_literal_generic_string(column)
column[:text] ? :"BLOB SUB_TYPE TEXT" : super
end
end

# Dataset class for Firebird datasets
class Dataset < Sequel::Dataset
BOOL_TRUE = '1'.freeze
BOOL_FALSE = '0'.freeze
NULL = LiteralString.new('NULL').freeze
COMMA_SEPARATOR = ', '.freeze
SELECT_CLAUSE_METHODS = clause_methods(:select, %w'with distinct limit columns from join where group having compounds order')
INSERT_CLAUSE_METHODS = clause_methods(:insert, %w'into columns values returning')
include Sequel::Firebird::DatasetMethods

# Yield all rows returned by executing the given SQL and converting
# the types.
Expand All @@ -239,62 +113,6 @@ def fetch_rows(sql)
end
self
end

# Insert given values into the database.
def insert(*values)
if @opts[:sql] || @opts[:returning]
super
elsif supports_insert_select?
returning(insert_pk).insert(*values){|r| return r.values.first}
end
end

# Insert a record returning the record inserted
def insert_select(*values)
returning.insert(*values){|r| return r}
end

def requires_sql_standard_datetimes?
true
end

def supports_insert_select?
true
end

# Firebird does not support INTERSECT or EXCEPT
def supports_intersect_except?
false
end

private

def insert_clause_methods
INSERT_CLAUSE_METHODS
end

def insert_pk(*values)
pk = db.primary_key(opts[:from].first)
pk ? Sequel::SQL::Identifier.new(pk) : NULL
end

def literal_false
BOOL_FALSE
end

def literal_true
BOOL_TRUE
end

# The order of clauses in the SELECT SQL statement
def select_clause_methods
SELECT_CLAUSE_METHODS
end

def select_limit_sql(sql)
sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
end
end
end
end
5 changes: 5 additions & 0 deletions lib/sequel/adapters/jdbc.rb
Expand Up @@ -85,6 +85,11 @@ module JavaxNaming
Sequel.ts_require 'adapters/jdbc/db2'
db.extend(Sequel::JDBC::DB2::DatabaseMethods)
com.ibm.db2.jcc.DB2Driver
end,
:firebirdsql=>proc do |db|
Sequel.ts_require 'adapters/jdbc/firebird'
db.extend(Sequel::JDBC::Firebird::DatabaseMethods)
org.firebirdsql.jdbc.FBDriver
end
}

Expand Down
34 changes: 34 additions & 0 deletions lib/sequel/adapters/jdbc/firebird.rb
@@ -0,0 +1,34 @@
Sequel.require 'adapters/shared/firebird'
Sequel.require 'adapters/jdbc/transactions'

module Sequel
module JDBC
# Database and Dataset instance methods for Firebird specific
# support via JDBC.
module Firebird
# Database instance methods for Firebird databases accessed via JDBC.
module DatabaseMethods
include Sequel::Firebird::DatabaseMethods
include Sequel::JDBC::Transactions

# Add the primary_keys and primary_key_sequences instance variables,
# so we can get the correct return values for inserted rows.
def self.extended(db)
db.instance_eval do
@primary_keys = {}
end
end

# Return instance of Sequel::JDBC::Firebird::Dataset with the given opts.
def dataset(opts=nil)
Sequel::JDBC::Firebird::Dataset.new(self, opts)
end
end

# Dataset class for Firebird datasets accessed via JDBC.
class Dataset < JDBC::Dataset
include Sequel::Firebird::DatasetMethods
end
end
end
end

0 comments on commit 59c36db

Please sign in to comment.