activerecord-rdb-adapter 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +3 -0
- data/lib/active_record/connection_adapters/rdb/database_limits.rb +35 -0
- data/lib/active_record/connection_adapters/rdb/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/rdb/quoting.rb +147 -0
- data/lib/active_record/connection_adapters/rdb/schema_creation.rb +53 -0
- data/lib/active_record/connection_adapters/rdb/schema_dumper.rb +23 -0
- data/lib/active_record/connection_adapters/rdb/schema_statements.rb +425 -0
- data/lib/active_record/connection_adapters/rdb/table_definition.rb +28 -0
- data/lib/active_record/connection_adapters/rdb_adapter.rb +152 -0
- data/lib/active_record/connection_adapters/rdb_column.rb +69 -0
- data/lib/active_record/rdb_base.rb +33 -0
- data/lib/active_record/tasks/rdb_database_tasks.rb +82 -0
- data/lib/activerecord-rdb-adapter.rb +10 -0
- data/lib/arel/visitors/rdb_visitor.rb +117 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: eae56962a83ba7f51da156eb041ef6b9961e450d29088e2481b3085c4913d1b8
|
4
|
+
data.tar.gz: 38fcc781c4852ef2dd1897d0affebaac9006e038dbb90a67efe4ba9995a4e408
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1f68f8293992979c5f6168746cabed86d7c68290424aa566134db01e40cf3ec9097e53628c05bf71906197ebaeb4f6dec9271a062ba6e9d6b4b6050e00ebd064
|
7
|
+
data.tar.gz: c5dc9601b8205894ca3f668f25848643a69b79093b2069d87d9a60e2d80eb889f77e00b465e797ee765fea0d77b8c5eeb5746776017ca0a5d432c153f6b94890
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Rdb
|
4
|
+
module DatabaseLimits # :nodoc:
|
5
|
+
def table_alias_length
|
6
|
+
31
|
7
|
+
end
|
8
|
+
|
9
|
+
def column_name_length
|
10
|
+
31
|
11
|
+
end
|
12
|
+
|
13
|
+
def table_name_length
|
14
|
+
31
|
15
|
+
end
|
16
|
+
|
17
|
+
def index_name_length
|
18
|
+
31
|
19
|
+
end
|
20
|
+
|
21
|
+
def indexes_per_table
|
22
|
+
65_535
|
23
|
+
end
|
24
|
+
|
25
|
+
def in_clause_length
|
26
|
+
1_499
|
27
|
+
end
|
28
|
+
|
29
|
+
def sql_query_length
|
30
|
+
32_767
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Rdb
|
4
|
+
module DatabaseStatements # :nodoc:
|
5
|
+
def execute(sql, name = nil)
|
6
|
+
log(sql, name) do
|
7
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
8
|
+
@connection.query(sql)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def exec_query(sql, name = 'SQL', binds = [], prepare: false)
|
14
|
+
type_casted_binds = type_casted_binds(binds)
|
15
|
+
|
16
|
+
log(sql, name, binds, type_casted_binds) do
|
17
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
18
|
+
result = @connection.execute(sql, *type_casted_binds)
|
19
|
+
if result.is_a?(Fb::Cursor)
|
20
|
+
fields = result.fields.map(&:name)
|
21
|
+
rows = result.fetchall.map do |row|
|
22
|
+
row.map do |col|
|
23
|
+
col.encode('UTF-8', @connection.encoding)
|
24
|
+
rescue StandardError
|
25
|
+
col
|
26
|
+
end
|
27
|
+
end
|
28
|
+
result.close
|
29
|
+
ActiveRecord::Result.new(fields, rows)
|
30
|
+
else
|
31
|
+
result
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rescue StandardError => e
|
36
|
+
raise e.message.encode('UTF-8', @connection.encoding)
|
37
|
+
end
|
38
|
+
|
39
|
+
def explain(arel, binds = [])
|
40
|
+
to_sql(arel, binds)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Begins the transaction (and turns off auto-committing).
|
44
|
+
def begin_db_transaction
|
45
|
+
log('begin transaction', nil) do
|
46
|
+
begin_isolated_db_transaction(default_transaction_isolation)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Default isolation levels for transactions. This method exists
|
51
|
+
# in 4.0.2+, so it's here for backward compatibility with AR 3
|
52
|
+
def transaction_isolation_levels
|
53
|
+
{
|
54
|
+
read_committed: 'READ COMMITTED',
|
55
|
+
repeatable_read: 'REPEATABLE READ',
|
56
|
+
serializable: 'SERIALIZABLE'
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Allows providing the :transaction option to ActiveRecord::Base.transaction
|
61
|
+
# in 4.0.2+. Can accept verbatim isolation options like 'WAIT READ COMMITTED'
|
62
|
+
def begin_isolated_db_transaction(isolation)
|
63
|
+
@connection.transaction transaction_isolation_levels.fetch(isolation, isolation)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Commits the transaction (and turns on auto-committing).
|
67
|
+
def commit_db_transaction
|
68
|
+
log('commit transaction', nil) { @connection.commit }
|
69
|
+
end
|
70
|
+
|
71
|
+
# Rolls back the transaction (and turns on auto-committing). Must be
|
72
|
+
# done if the transaction block raises an exception or returns false.
|
73
|
+
def rollback_db_transaction
|
74
|
+
log('rollback transaction', nil) { @connection.rollback }
|
75
|
+
end
|
76
|
+
|
77
|
+
def default_sequence_name(table_name, _column = nil)
|
78
|
+
"#{table_name.to_s.tr('-', '_')[0, table_name_length - 4]}_seq"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Set the sequence to the max value of the table's column.
|
82
|
+
def reset_sequence!(table, column, sequence = nil)
|
83
|
+
sequence ||= default_sequence_name(table, column)
|
84
|
+
max_id = select_value("select max(#{column}) from #{table}")
|
85
|
+
execute("alter sequence #{sequence} restart with #{max_id}")
|
86
|
+
end
|
87
|
+
|
88
|
+
# Uses the raw connection to get the next sequence value.
|
89
|
+
def next_sequence_value(sequence_name)
|
90
|
+
@connection.query("SELECT NEXT VALUE FOR #{sequence_name} FROM RDB$DATABASE")[0][0]
|
91
|
+
end
|
92
|
+
|
93
|
+
def last_inserted_id(_result)
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Rdb
|
4
|
+
module Quoting # :nodoc:
|
5
|
+
QUOTED_FALSE = "'false'".freeze
|
6
|
+
QUOTED_TRUE = "'true'".freeze
|
7
|
+
|
8
|
+
QUOTED_POSITION = '"POSITION"'.freeze
|
9
|
+
QUOTED_VALUE = '"VALUE"'.freeze
|
10
|
+
|
11
|
+
def quote_string(string) # :nodoc:
|
12
|
+
string.gsub(/'/, "''")
|
13
|
+
end
|
14
|
+
|
15
|
+
def quoted_date(time)
|
16
|
+
if time.is_a?(Time) || time.is_a?(DateTime)
|
17
|
+
time.localtime.strftime('%d.%m.%Y %H:%M:%S')
|
18
|
+
else
|
19
|
+
time.strftime('%d.%m.%Y')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def quote_column_name(column_name) # :nodoc:
|
24
|
+
column = column_name.dup.to_s
|
25
|
+
column.gsub!(/(?<=[^\"\w]|^)position(?=[^\"\w]|$)/i, QUOTED_POSITION)
|
26
|
+
column.gsub!(/(?<=[^\"\w]|^)value(?=[^\"\w]|$)/i, QUOTED_VALUE)
|
27
|
+
column.delete!('"')
|
28
|
+
column.upcase!
|
29
|
+
@connection.dialect == 1 ? column.to_s : %("#{column}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def quote_table_name_for_assignment(_table, attr)
|
33
|
+
quote_column_name(attr)
|
34
|
+
end
|
35
|
+
|
36
|
+
def unquoted_true
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def quoted_true # :nodoc:
|
41
|
+
QUOTED_TRUE
|
42
|
+
end
|
43
|
+
|
44
|
+
def unquoted_false
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def quoted_false # :nodoc:
|
49
|
+
QUOTED_FALSE
|
50
|
+
end
|
51
|
+
|
52
|
+
def type_cast_from_column(column, value) # :nodoc:
|
53
|
+
if column
|
54
|
+
type = column.type || lookup_cast_type_from_column(column)
|
55
|
+
type.serialize(value)
|
56
|
+
else
|
57
|
+
value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def lookup_cast_type_from_column(column) # :nodoc:
|
62
|
+
type = column.try(:sql_type) || column.try(:type)
|
63
|
+
lookup_cast_type(type)
|
64
|
+
end
|
65
|
+
|
66
|
+
def type_casted_binds(binds) # :nodoc:
|
67
|
+
if binds.first.is_a?(Array)
|
68
|
+
binds.map { |column, value| type_cast(value, column) }
|
69
|
+
else
|
70
|
+
binds.map { |attr| type_cast(attr.value_for_database, attr) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def id_value_for_database(value)
|
77
|
+
if primary_key = value.class.primary_key
|
78
|
+
value.instance_variable_get(:@attributes)[primary_key].value_for_database
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def _quote(value)
|
83
|
+
case value
|
84
|
+
when Time, DateTime
|
85
|
+
"'#{value.strftime('%d.%m.%Y %H:%M')}'"
|
86
|
+
when Date
|
87
|
+
"'#{value.strftime('%d.%m.%Y')}'"
|
88
|
+
else
|
89
|
+
super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def _type_cast(value)
|
94
|
+
case value
|
95
|
+
when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data
|
96
|
+
value.to_s
|
97
|
+
when Array
|
98
|
+
value.to_yaml
|
99
|
+
when Hash then
|
100
|
+
encode_hash(value)
|
101
|
+
when true then
|
102
|
+
unquoted_true
|
103
|
+
when false then
|
104
|
+
unquoted_false
|
105
|
+
# BigDecimals need to be put in a non-normalized form and quoted.
|
106
|
+
when BigDecimal then
|
107
|
+
value.to_s('F')
|
108
|
+
when Type::Time::Value then
|
109
|
+
quoted_time(value)
|
110
|
+
when Date, Time, DateTime then
|
111
|
+
quoted_date(value)
|
112
|
+
when *types_which_need_no_typecasting
|
113
|
+
value
|
114
|
+
else
|
115
|
+
raise TypeError
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def rdb_to_ar_case(column_name)
|
120
|
+
/[[:lower:]]/.match?(column_name) ? column_name : column_name.downcase
|
121
|
+
end
|
122
|
+
|
123
|
+
def ar_to_rdb_case(column_name)
|
124
|
+
/[[:upper:]]/.match?(column_name) ? column_name : column_name.upcase
|
125
|
+
end
|
126
|
+
|
127
|
+
def encode_hash(value)
|
128
|
+
if value.is_a?(Hash)
|
129
|
+
value.to_yaml
|
130
|
+
else
|
131
|
+
value
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
if defined? Encoding
|
136
|
+
def decode(str)
|
137
|
+
Base64.decode64(str).force_encoding(@connection.encoding)
|
138
|
+
end
|
139
|
+
else
|
140
|
+
def decode(str)
|
141
|
+
Base64.decode64(str)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Rdb
|
4
|
+
class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def visit_ColumnDefinition(o)
|
9
|
+
o.sql_type = type_to_sql(o.type, o.options)
|
10
|
+
column_sql = "#{quote_column_name(o.name)} #{o.sql_type}"
|
11
|
+
add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key
|
12
|
+
column_sql
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_column_options!(sql, options)
|
16
|
+
sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
|
17
|
+
# must explicitly check for :null to allow change_column to work on migrations
|
18
|
+
if !options[:null]
|
19
|
+
sql << " NOT NULL"
|
20
|
+
end
|
21
|
+
if options[:auto_increment]
|
22
|
+
sql << " AUTO_INCREMENT"
|
23
|
+
end
|
24
|
+
if options[:primary_key]
|
25
|
+
sql << " PRIMARY KEY"
|
26
|
+
end
|
27
|
+
sql
|
28
|
+
end
|
29
|
+
|
30
|
+
def visit_TableDefinition(o)
|
31
|
+
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} "
|
32
|
+
|
33
|
+
statements = o.columns.map(&method(:accept))
|
34
|
+
statements << accept(o.primary_keys) if o.primary_keys
|
35
|
+
|
36
|
+
if supports_indexes_in_create?
|
37
|
+
statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
|
38
|
+
end
|
39
|
+
|
40
|
+
if supports_foreign_keys_in_create?
|
41
|
+
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
|
42
|
+
end
|
43
|
+
|
44
|
+
create_sql << "(#{statements.join(', ')})" if statements.present?
|
45
|
+
add_table_options!(create_sql, table_options(o))
|
46
|
+
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
|
47
|
+
create_sql
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Rdb
|
4
|
+
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
|
5
|
+
private
|
6
|
+
|
7
|
+
def column_spec_for_primary_key(column)
|
8
|
+
spec = super
|
9
|
+
spec.delete(:auto_increment) if column.type == :integer && column.auto_increment?
|
10
|
+
spec
|
11
|
+
end
|
12
|
+
|
13
|
+
def schema_type(column)
|
14
|
+
if column.bigint?
|
15
|
+
:bigint
|
16
|
+
else
|
17
|
+
column.type.type
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,425 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Rdb
|
4
|
+
module SchemaStatements # :nodoc:
|
5
|
+
methods_to_commit = %i[add_column
|
6
|
+
create_table
|
7
|
+
rename_column
|
8
|
+
remove_column
|
9
|
+
change_column
|
10
|
+
change_column_default
|
11
|
+
change_column_null
|
12
|
+
remove_index
|
13
|
+
remove_index!
|
14
|
+
drop_table
|
15
|
+
create_sequence
|
16
|
+
drop_sequence
|
17
|
+
drop_trigger]
|
18
|
+
|
19
|
+
def tables(_name = nil)
|
20
|
+
@connection.table_names
|
21
|
+
end
|
22
|
+
|
23
|
+
def views
|
24
|
+
@connection.view_names
|
25
|
+
end
|
26
|
+
|
27
|
+
def indexes(table_name, _name = nil)
|
28
|
+
@connection.indexes.values.map do |ix|
|
29
|
+
IndexDefinition.new(table_name, ix.index_name, ix.unique, ix.columns) if ix.table_name == table_name.to_s && ix.index_name !~ /^rdb\$/
|
30
|
+
end.compact
|
31
|
+
end
|
32
|
+
|
33
|
+
def index_name_exists?(table_name, index_name)
|
34
|
+
index_name = index_name.to_s.upcase
|
35
|
+
indexes(table_name).detect { |i| i.name.upcase == index_name }
|
36
|
+
end
|
37
|
+
|
38
|
+
def columns(table_name, _name = nil)
|
39
|
+
@col_definitions ||= {}
|
40
|
+
@col_definitions[table_name] = column_definitions(table_name).map do |field|
|
41
|
+
sql_type_metadata = column_type_for(field)
|
42
|
+
rdb_opt = { domain: field[:domain], sub_type: field[:sql_subtype] }
|
43
|
+
RdbColumn.new(field[:name], field[:default], sql_type_metadata, field[:nullable], table_name, rdb_opt)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_table(name, options = {}) # :nodoc:
|
48
|
+
raise ActiveRecordError, 'Firebird does not support temporary tables' if options.key? :temporary
|
49
|
+
|
50
|
+
raise ActiveRecordError, 'Firebird does not support creating tables with a select' if options.key? :as
|
51
|
+
|
52
|
+
drop_table name, if_exists: true if options.key? :force
|
53
|
+
|
54
|
+
needs_sequence = options[:id]
|
55
|
+
|
56
|
+
super name, options do |table_def|
|
57
|
+
yield table_def if block_given?
|
58
|
+
needs_sequence ||= table_def.needs_sequence
|
59
|
+
end
|
60
|
+
|
61
|
+
return if options[:sequence] == false || !needs_sequence
|
62
|
+
create_sequence(options[:sequence] || default_sequence_name(name))
|
63
|
+
trg_sql = <<-END_SQL
|
64
|
+
CREATE TRIGGER N$#{name.upcase} FOR #{name.upcase}
|
65
|
+
ACTIVE BEFORE INSERT
|
66
|
+
AS
|
67
|
+
declare variable gen_val bigint;
|
68
|
+
BEGIN
|
69
|
+
if (new.ID is null) then
|
70
|
+
new.ID = next value for #{options[:sequence] || default_sequence_name(name)};
|
71
|
+
else begin
|
72
|
+
gen_val = gen_id(#{options[:sequence] || default_sequence_name(name)}, 1);
|
73
|
+
if (new.ID > gen_val) then
|
74
|
+
gen_val = gen_id(#{options[:sequence] || default_sequence_name(name)}, new.ID - gen_val);
|
75
|
+
end
|
76
|
+
END
|
77
|
+
END_SQL
|
78
|
+
execute(trg_sql)
|
79
|
+
end
|
80
|
+
|
81
|
+
def drop_table(name, options = {}) # :nodoc:
|
82
|
+
drop_sql = "DROP TABLE #{quote_table_name(name)}"
|
83
|
+
if options[:if_exists]
|
84
|
+
drop = !execute(squish_sql(<<-END_SQL))
|
85
|
+
select 1 from rdb$relations where rdb$relation_name = #{quote_table_name(name).tr('"', '\'')}
|
86
|
+
END_SQL
|
87
|
+
.empty?
|
88
|
+
end
|
89
|
+
|
90
|
+
trigger_name = "N$#{name.upcase}"
|
91
|
+
drop_trigger(trigger_name) if trigger_exists?(trigger_name)
|
92
|
+
|
93
|
+
sequence_name = options[:sequence] || default_sequence_name(name)
|
94
|
+
drop_sequence(sequence_name) if sequence_exists?(sequence_name)
|
95
|
+
|
96
|
+
execute(drop_sql) if drop
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_sequence(sequence_name)
|
100
|
+
execute("CREATE SEQUENCE #{sequence_name}")
|
101
|
+
rescue StandardError
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
def drop_sequence(sequence_name)
|
106
|
+
execute("DROP SEQUENCE #{sequence_name}")
|
107
|
+
rescue StandardError
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def drop_trigger(trigger_name)
|
112
|
+
execute("DROP TRIGGER #{trigger_name}")
|
113
|
+
rescue StandardError
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
|
117
|
+
def trigger_exists?(trigger_name)
|
118
|
+
!execute(squish_sql(<<-END_SQL))
|
119
|
+
select 1
|
120
|
+
from rdb$triggers
|
121
|
+
where rdb$trigger_name = '#{trigger_name}'
|
122
|
+
END_SQL
|
123
|
+
.empty?
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_column(table_name, column_name, type, options = {})
|
127
|
+
super
|
128
|
+
|
129
|
+
create_sequence(options[:sequence] || default_sequence_name(table_name)) if type == :primary_key && options[:sequence] != false
|
130
|
+
|
131
|
+
return unless options[:position]
|
132
|
+
# position is 1-based but add 1 to skip id column
|
133
|
+
execute(squish_sql(<<-end_sql))
|
134
|
+
ALTER TABLE #{quote_table_name(table_name)}
|
135
|
+
ALTER COLUMN #{quote_column_name(column_name)}
|
136
|
+
POSITION #{options[:position] + 1}
|
137
|
+
end_sql
|
138
|
+
end
|
139
|
+
|
140
|
+
def remove_column(table_name, column_name, type = nil, options = {})
|
141
|
+
indexes(table_name).each do |i|
|
142
|
+
remove_index! i.table, i.name if i.columns.any? { |c| c == column_name.to_s }
|
143
|
+
end
|
144
|
+
|
145
|
+
column_exist = !execute(squish_sql(<<-END_SQL))
|
146
|
+
select 1 from RDB$RELATION_FIELDS rf
|
147
|
+
where lower(rf.RDB$RELATION_NAME) = '#{table_name.downcase}' and lower(rf.RDB$FIELD_NAME) = '#{column_name.downcase}'
|
148
|
+
END_SQL
|
149
|
+
.empty?
|
150
|
+
super if column_exist
|
151
|
+
end
|
152
|
+
|
153
|
+
def remove_column_for_alter(_table_name, column_name, _type = nil, _options = {})
|
154
|
+
"DROP #{quote_column_name(column_name)}"
|
155
|
+
end
|
156
|
+
|
157
|
+
def change_column(table_name, column_name, type, options = {})
|
158
|
+
type_sql = type_to_sql(type, *options.values_at(:limit, :precision, :scale))
|
159
|
+
|
160
|
+
if %i[text string].include?(type)
|
161
|
+
copy_column = 'c_temp'
|
162
|
+
add_column table_name, copy_column, type, options
|
163
|
+
execute(squish_sql(<<-END_SQL))
|
164
|
+
UPDATE #{table_name} SET #{copy_column.quote_column_name} = #{column_name.to_s.quote_column_name};
|
165
|
+
END_SQL
|
166
|
+
remove_column table_name, column_name
|
167
|
+
rename_column table_name, copy_column, column_name
|
168
|
+
else
|
169
|
+
execute(squish_sql(<<-END_SQL))
|
170
|
+
ALTER TABLE #{quote_table_name(table_name)}
|
171
|
+
ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_sql}
|
172
|
+
END_SQL
|
173
|
+
end
|
174
|
+
change_column_null(table_name, column_name, !!options[:null]) if options.key?(:null)
|
175
|
+
change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
|
176
|
+
end
|
177
|
+
|
178
|
+
def change_column_default(table_name, column_name, default)
|
179
|
+
execute(squish_sql(<<-END_SQL))
|
180
|
+
ALTER TABLE #{quote_table_name(table_name)}
|
181
|
+
ALTER #{quote_column_name(column_name)}
|
182
|
+
SET DEFAULT #{quote(default)}
|
183
|
+
END_SQL
|
184
|
+
end
|
185
|
+
|
186
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
187
|
+
change_column_default(table_name, column_name, default) if default
|
188
|
+
|
189
|
+
db_column = columns(table_name).find { |c| c.name == column_name.to_s }
|
190
|
+
options = { null: null }
|
191
|
+
options[:default] = db_column.default if !default && db_column.default
|
192
|
+
options[:default] = default if default
|
193
|
+
ar_type = db_column.type
|
194
|
+
type = type_to_sql(ar_type.type, ar_type.limit, ar_type.precision, ar_type.scale)
|
195
|
+
|
196
|
+
copy_column = 'c_temp'
|
197
|
+
add_column table_name, copy_column, type, options
|
198
|
+
execute(squish_sql(<<-END_SQL))
|
199
|
+
UPDATE #{table_name} SET #{copy_column.quote_column_name} = #{column_name.to_s.quote_column_name};
|
200
|
+
END_SQL
|
201
|
+
remove_column table_name, column_name
|
202
|
+
rename_column table_name, copy_column, column_name
|
203
|
+
end
|
204
|
+
|
205
|
+
def rename_column(table_name, column_name, new_column_name)
|
206
|
+
execute(squish_sql(<<-END_SQL))
|
207
|
+
ALTER TABLE #{quote_table_name(table_name)}
|
208
|
+
ALTER #{quote_column_name(column_name)}
|
209
|
+
TO #{quote_column_name(new_column_name)}
|
210
|
+
END_SQL
|
211
|
+
|
212
|
+
rename_column_indexes(table_name, column_name, new_column_name)
|
213
|
+
end
|
214
|
+
|
215
|
+
def remove_index!(_table_name, index_name)
|
216
|
+
execute "DROP INDEX #{quote_column_name(index_name)}"
|
217
|
+
end
|
218
|
+
|
219
|
+
def remove_index(table_name, options = {})
|
220
|
+
index_name = index_name(table_name, options)
|
221
|
+
execute "DROP INDEX #{quote_column_name(index_name)}"
|
222
|
+
end
|
223
|
+
|
224
|
+
def index_name(table_name, options) #:nodoc:
|
225
|
+
if options.respond_to?(:keys) # legacy support
|
226
|
+
if options[:column]
|
227
|
+
index_name = "#{table_name}_#{Array.wrap(options[:column]) * '_'}"
|
228
|
+
if index_name.length > 31
|
229
|
+
"IDX_#{Digest::SHA256.hexdigest(index_name)[0..22]}"
|
230
|
+
else
|
231
|
+
index_name
|
232
|
+
end
|
233
|
+
elsif options[:name]
|
234
|
+
options[:name]
|
235
|
+
else
|
236
|
+
raise ArgumentError 'You must specify the index name'
|
237
|
+
end
|
238
|
+
else
|
239
|
+
index_name(table_name, column: options)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def index_exists?(table_name, column_name, options = {})
|
244
|
+
column_names = Array(column_name).map(&:to_s)
|
245
|
+
checks = []
|
246
|
+
checks << lambda { |i| i.columns == column_names }
|
247
|
+
checks << lambda(&:unique) if options[:unique]
|
248
|
+
checks << lambda { |i| i.name.upcase == options[:name].to_s.upcase } if options[:name]
|
249
|
+
|
250
|
+
indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
|
251
|
+
end
|
252
|
+
|
253
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil, **args)
|
254
|
+
if !args.nil? && !args.empty?
|
255
|
+
limit = args[:limit] if limit.nil?
|
256
|
+
precision = args[:precision] if precision.nil?
|
257
|
+
scale = args[:scale] if scale.nil?
|
258
|
+
end
|
259
|
+
case type
|
260
|
+
when :integer
|
261
|
+
integer_to_sql(limit)
|
262
|
+
when :float
|
263
|
+
float_to_sql(limit)
|
264
|
+
when :text
|
265
|
+
text_to_sql(limit)
|
266
|
+
# when :blob
|
267
|
+
# binary_to_sql(limit)
|
268
|
+
when :string
|
269
|
+
string_to_sql(limit)
|
270
|
+
else
|
271
|
+
type = type.to_sym if type
|
272
|
+
if native = native_database_types[type]
|
273
|
+
column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
|
274
|
+
|
275
|
+
if type == :decimal # ignore limit, use precision and scale
|
276
|
+
scale ||= native[:scale]
|
277
|
+
|
278
|
+
if precision ||= native[:precision]
|
279
|
+
column_type_sql << if scale
|
280
|
+
"(#{precision},#{scale})"
|
281
|
+
else
|
282
|
+
"(#{precision})"
|
283
|
+
end
|
284
|
+
elsif scale
|
285
|
+
raise ArgumentError, 'Error adding decimal column: precision cannot be empty if scale is specified'
|
286
|
+
end
|
287
|
+
|
288
|
+
elsif %i[datetime timestamp time interval].include?(type) && precision ||= native[:precision]
|
289
|
+
if (0..6) === precision
|
290
|
+
column_type_sql << "(#{precision})"
|
291
|
+
else
|
292
|
+
raise(ActiveRecordError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6")
|
293
|
+
end
|
294
|
+
elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
|
295
|
+
column_type_sql << "(#{limit})"
|
296
|
+
end
|
297
|
+
|
298
|
+
column_type_sql
|
299
|
+
else
|
300
|
+
type.to_s
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def primary_key(table_name)
|
306
|
+
row = @connection.query(<<-END_SQL)
|
307
|
+
SELECT s.rdb$field_name
|
308
|
+
FROM rdb$indices i
|
309
|
+
JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
|
310
|
+
LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name
|
311
|
+
WHERE i.rdb$relation_name = '#{ar_to_rdb_case(table_name)}'
|
312
|
+
AND c.rdb$constraint_type = 'PRIMARY KEY';
|
313
|
+
END_SQL
|
314
|
+
|
315
|
+
row.first && rdb_to_ar_case(row.first[0].rstrip)
|
316
|
+
end
|
317
|
+
|
318
|
+
def native_database_types
|
319
|
+
@native_database_types ||= initialize_native_database_types.freeze
|
320
|
+
end
|
321
|
+
|
322
|
+
def create_schema_dumper(options)
|
323
|
+
Rdb::SchemaDumper.create(self, options)
|
324
|
+
end
|
325
|
+
|
326
|
+
private
|
327
|
+
|
328
|
+
def column_definitions(table_name)
|
329
|
+
@connection.columns(table_name)
|
330
|
+
end
|
331
|
+
|
332
|
+
def new_column_from_field(table_name, field)
|
333
|
+
type_metadata = fetch_type_metadata(field['sql_type'])
|
334
|
+
ActiveRecord::ConnectionAdapters::Column.new(field['name'], field['default'], type_metadata, field['nullable'], table_name)
|
335
|
+
end
|
336
|
+
|
337
|
+
def column_type_for(field)
|
338
|
+
sql_type = RdbColumn.sql_type_for(field)
|
339
|
+
type = lookup_cast_type(sql_type)
|
340
|
+
{ type: type, sql_type: type.type }
|
341
|
+
end
|
342
|
+
|
343
|
+
def integer_to_sql(limit)
|
344
|
+
return 'integer' if limit.nil?
|
345
|
+
case limit
|
346
|
+
when 1..2 then
|
347
|
+
'smallint'
|
348
|
+
when 3..4 then
|
349
|
+
'integer'
|
350
|
+
when 5..8 then
|
351
|
+
'bigint'
|
352
|
+
else
|
353
|
+
raise ActiveRecordError "No integer type has byte size #{limit}. " \
|
354
|
+
'Use a NUMERIC with PRECISION 0 instead.'
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def float_to_sql(limit)
|
359
|
+
limit.nil? || limit <= 4 ? 'float' : 'double precision'
|
360
|
+
end
|
361
|
+
|
362
|
+
def text_to_sql(limit)
|
363
|
+
if limit && limit > 0
|
364
|
+
"VARCHAR(#{limit})"
|
365
|
+
else
|
366
|
+
'BLOB SUB_TYPE TEXT'
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def string_to_sql(limit)
|
371
|
+
if limit && limit > 0
|
372
|
+
"VARCHAR(#{limit})"
|
373
|
+
else
|
374
|
+
'VARCHAR(150)'
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def initialize_native_database_types
|
379
|
+
{ primary_key: 'integer not null primary key',
|
380
|
+
string: { name: 'varchar', limit: 255 },
|
381
|
+
text: { name: 'blob sub_type text' },
|
382
|
+
integer: { name: 'integer' },
|
383
|
+
bigint: { name: 'bigint' },
|
384
|
+
float: { name: 'float' },
|
385
|
+
decimal: { name: 'decimal' },
|
386
|
+
datetime: { name: 'timestamp' },
|
387
|
+
timestamp: { name: 'timestamp' },
|
388
|
+
time: { name: 'time' },
|
389
|
+
date: { name: 'date' },
|
390
|
+
binary: { name: 'blob' },
|
391
|
+
boolean: { name: 'boolean' } }
|
392
|
+
end
|
393
|
+
|
394
|
+
def sequence_exists?(sequence_name)
|
395
|
+
@connection.generator_names.include?(sequence_name)
|
396
|
+
end
|
397
|
+
|
398
|
+
def create_table_definition(*args)
|
399
|
+
Rdb::TableDefinition.new(*args)
|
400
|
+
end
|
401
|
+
|
402
|
+
def squish_sql(sql)
|
403
|
+
sql.strip.gsub(/\s+/, ' ')
|
404
|
+
end
|
405
|
+
|
406
|
+
class << self
|
407
|
+
def after(*names)
|
408
|
+
names.flatten.each do |name|
|
409
|
+
m = ActiveRecord::ConnectionAdapters::Rdb::SchemaStatements.instance_method(name)
|
410
|
+
define_method(name) do |*args, &block|
|
411
|
+
m.bind(self).call(*args, &block)
|
412
|
+
yield
|
413
|
+
commit_db_transaction
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
after(methods_to_commit) do
|
420
|
+
puts 'Commiting transaction'
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Rdb
|
4
|
+
module ColumnMethods # :nodoc:
|
5
|
+
|
6
|
+
attr_accessor :needs_sequence
|
7
|
+
|
8
|
+
def primary_key(name, type = :primary_key, **options)
|
9
|
+
self.needs_sequence = true
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition # :nodoc:
|
16
|
+
include ColumnMethods
|
17
|
+
|
18
|
+
def new_column_definition(name, type, **options)
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Table < ActiveRecord::ConnectionAdapters::Table # :nodoc:
|
24
|
+
include ColumnMethods
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'fb'
|
2
|
+
require 'base64'
|
3
|
+
require 'arel'
|
4
|
+
require 'arel/visitors/rdb_visitor'
|
5
|
+
|
6
|
+
require 'active_record'
|
7
|
+
require 'active_record/base'
|
8
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
9
|
+
require 'active_record/connection_adapters/rdb/database_statements'
|
10
|
+
require 'active_record/connection_adapters/rdb/database_limits'
|
11
|
+
require 'active_record/connection_adapters/rdb/schema_creation'
|
12
|
+
require 'active_record/connection_adapters/rdb/schema_dumper'
|
13
|
+
require 'active_record/connection_adapters/rdb/schema_statements'
|
14
|
+
require 'active_record/connection_adapters/rdb/quoting'
|
15
|
+
require 'active_record/connection_adapters/rdb/table_definition'
|
16
|
+
require 'active_record/connection_adapters/rdb_column'
|
17
|
+
require 'active_record/rdb_base'
|
18
|
+
|
19
|
+
module ActiveRecord
|
20
|
+
module ConnectionAdapters
|
21
|
+
class RdbAdapter < AbstractAdapter # :nodoc:
|
22
|
+
include Rdb::DatabaseLimits
|
23
|
+
include Rdb::DatabaseStatements
|
24
|
+
include Rdb::Quoting
|
25
|
+
include Rdb::SchemaStatements
|
26
|
+
|
27
|
+
@@default_transaction_isolation = :read_committed
|
28
|
+
cattr_accessor :default_transaction_isolation
|
29
|
+
|
30
|
+
ADAPTER_NAME = 'rdb'.freeze
|
31
|
+
|
32
|
+
def initialize(connection, logger = nil, config = {})
|
33
|
+
super(connection, logger, config)
|
34
|
+
# Our Responsibility
|
35
|
+
@config = config
|
36
|
+
@visitor = Arel::Visitors::Rdb.new self
|
37
|
+
end
|
38
|
+
|
39
|
+
def arel_visitor
|
40
|
+
Arel::Visitors::Rdb.new self
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid_type?(type)
|
44
|
+
!native_database_types[type].nil? || !native_database_types[type.type].nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
def adapter_name
|
48
|
+
ADAPTER_NAME
|
49
|
+
end
|
50
|
+
|
51
|
+
def schema_creation
|
52
|
+
Rdb::SchemaCreation.new self
|
53
|
+
end
|
54
|
+
|
55
|
+
def supports_migrations?
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def supports_primary_key?
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def supports_count_distinct?
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def supports_ddl_transactions?
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
def supports_transaction_isolation?
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def supports_savepoints?
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
def prefetch_primary_key?(_table_name = nil)
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
def ids_in_list_limit
|
84
|
+
1499
|
85
|
+
end
|
86
|
+
|
87
|
+
def active?
|
88
|
+
return false unless @connection.open?
|
89
|
+
# return true if @connection.transaction_started
|
90
|
+
@connection.query('SELECT 1 FROM RDB$DATABASE')
|
91
|
+
true
|
92
|
+
rescue StandardError
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
96
|
+
def reconnect!
|
97
|
+
disconnect!
|
98
|
+
@connection = ::Fb::Database.connect(@config)
|
99
|
+
end
|
100
|
+
|
101
|
+
def disconnect!
|
102
|
+
super
|
103
|
+
begin
|
104
|
+
@connection.close
|
105
|
+
rescue StandardError
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def reset!
|
111
|
+
reconnect!
|
112
|
+
end
|
113
|
+
|
114
|
+
def requires_reloading?
|
115
|
+
false
|
116
|
+
end
|
117
|
+
|
118
|
+
def create_savepoint(name = current_savepoint_name)
|
119
|
+
execute("SAVEPOINT #{name}")
|
120
|
+
end
|
121
|
+
|
122
|
+
def rollback_to_savepoint(name = current_savepoint_name)
|
123
|
+
execute("ROLLBACK TO SAVEPOINT #{name}")
|
124
|
+
end
|
125
|
+
|
126
|
+
def release_savepoint(name = current_savepoint_name)
|
127
|
+
execute("RELEASE SAVEPOINT #{name}")
|
128
|
+
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
|
132
|
+
def initialize_type_map(map)
|
133
|
+
super
|
134
|
+
map.register_type(/timestamp/i, Type::DateTime.new)
|
135
|
+
map.alias_type(/blob sub_type text/i, 'text')
|
136
|
+
end
|
137
|
+
|
138
|
+
def translate_exception(e, message)
|
139
|
+
case e.message
|
140
|
+
when /violation of FOREIGN KEY constraint/
|
141
|
+
ActiveRecord::InvalidForeignKey.new(message)
|
142
|
+
when /violation of PRIMARY or UNIQUE KEY constraint/, /attempt to store duplicate value/
|
143
|
+
ActiveRecord::RecordNotUnique.new(message)
|
144
|
+
when /This operation is not defined for system tables/
|
145
|
+
ActiveRecord::ActiveRecordError.new(message)
|
146
|
+
else
|
147
|
+
super
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class RdbColumn < Column # :nodoc:
|
4
|
+
class << self
|
5
|
+
def sql_type_for(field)
|
6
|
+
sql_type = field[:sql_type]
|
7
|
+
sub_type = field[:sql_subtype]
|
8
|
+
|
9
|
+
sql_type << case sql_type
|
10
|
+
when /(numeric|decimal)/i
|
11
|
+
"(#{field[:precision]},#{field[:scale].abs})"
|
12
|
+
when /(int|float|double|char|varchar|bigint)/i
|
13
|
+
"(#{field[:length]})"
|
14
|
+
else
|
15
|
+
''
|
16
|
+
end
|
17
|
+
|
18
|
+
sql_type << ' sub_type text' if /blob/i.match?(sql_type) && sub_type == 1
|
19
|
+
sql_type
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :sub_type, :domain
|
24
|
+
|
25
|
+
def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, rdb_options = {})
|
26
|
+
@domain, @sub_type = rdb_options.values_at(:domain, :sub_type)
|
27
|
+
name = name.dup
|
28
|
+
name.downcase!
|
29
|
+
super(name, parse_default(default), sql_type_metadata, null, table_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def sql_type
|
33
|
+
@sql_type_metadata[:sql_type]
|
34
|
+
end
|
35
|
+
|
36
|
+
def type
|
37
|
+
@sql_type_metadata[:type]
|
38
|
+
end
|
39
|
+
|
40
|
+
def precision
|
41
|
+
@sql_type_metadata[:precision]
|
42
|
+
end
|
43
|
+
|
44
|
+
def scale
|
45
|
+
@sql_type_metadata[:scale]
|
46
|
+
end
|
47
|
+
|
48
|
+
def limit
|
49
|
+
@sql_type_metadata[:limit]
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def parse_default(default)
|
55
|
+
return if default.nil? || /null/i.match?(default)
|
56
|
+
d = default.dup
|
57
|
+
d.gsub!(/^\s*DEFAULT\s+/i, '')
|
58
|
+
d.gsub!(/(^'|'$)/, '')
|
59
|
+
d
|
60
|
+
end
|
61
|
+
|
62
|
+
def simplified_type(field_type)
|
63
|
+
return :datetime if /timestamp/i.match?(field_type)
|
64
|
+
return :text if /blob sub_type text/i.match?(field_type)
|
65
|
+
super
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionHandling # :nodoc:
|
3
|
+
def rdb_connection(config)
|
4
|
+
require 'fb'
|
5
|
+
config = rdb_connection_config(config)
|
6
|
+
db = ::Fb::Database.new(config)
|
7
|
+
begin
|
8
|
+
connection = db.connect
|
9
|
+
rescue StandardError
|
10
|
+
unless config[:create]
|
11
|
+
require 'pp'
|
12
|
+
pp config
|
13
|
+
raise ConnectionNotEstablished, 'No Firebird connections established.'
|
14
|
+
end
|
15
|
+
connection = db.create.connect
|
16
|
+
end
|
17
|
+
ConnectionAdapters::RdbAdapter.new(connection, logger, config)
|
18
|
+
end
|
19
|
+
|
20
|
+
def rdb_connection_config(config)
|
21
|
+
config = config.symbolize_keys.dup.reverse_merge(downcase_names: true)
|
22
|
+
port = config[:port] || 3050
|
23
|
+
raise ArgumentError, 'No database specified. Missing argument: database.' unless config[:database]
|
24
|
+
config[:database] = File.expand_path(config[:database], defined?(Rails) && Rails.root) if config[:host].nil? || /localhost/i.match?(config[:host])
|
25
|
+
config[:database] = "#{config[:host]}/#{port}:#{config[:database]}" if config[:host]
|
26
|
+
# config[:charset] = config[:charset].gsub(/-/, '') if config[:charset]
|
27
|
+
# config[:encoding] = config[:encoding].gsub(/-/, '') if config[:encoding]
|
28
|
+
config[:page_size] = 8192 unless config[:page_size]
|
29
|
+
config[:readonly_selects] = true unless config[:readonly_selects].present?
|
30
|
+
config
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'active_record/tasks/database_tasks'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Tasks
|
5
|
+
class RdbDatabaseTasks # :nodoc:
|
6
|
+
delegate :rdb_connection_config, :establish_connection, to: ::ActiveRecord::Base
|
7
|
+
|
8
|
+
def initialize(configuration, root = ::ActiveRecord::Tasks::DatabaseTasks.root)
|
9
|
+
@root = root
|
10
|
+
@configuration = rdb_connection_config(configuration)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
rdb_database.create
|
15
|
+
establish_connection configuration
|
16
|
+
rescue ::Fb::Error => e
|
17
|
+
raise unless e.message.include?('database or file exists')
|
18
|
+
raise DatabaseAlreadyExists
|
19
|
+
end
|
20
|
+
|
21
|
+
def drop
|
22
|
+
rdb_database.drop
|
23
|
+
rescue ::Fb::Error => e
|
24
|
+
raise ::ActiveRecord::ConnectionNotEstablished, e.message
|
25
|
+
end
|
26
|
+
|
27
|
+
def purge
|
28
|
+
begin
|
29
|
+
drop
|
30
|
+
rescue StandardError
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
create
|
34
|
+
end
|
35
|
+
|
36
|
+
def structure_dump(filename)
|
37
|
+
isql :extract, output: filename
|
38
|
+
end
|
39
|
+
|
40
|
+
def structure_load(filename)
|
41
|
+
isql input: filename
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def rdb_database
|
47
|
+
::Fb::Database.new(configuration)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Executes isql commands to load/dump the schema.
|
51
|
+
# The generated command might look like this:
|
52
|
+
# isql db/development.fdb -user SYSDBA -password masterkey -extract
|
53
|
+
def isql(*args)
|
54
|
+
opts = args.extract_options!
|
55
|
+
user, pass = configuration.values_at(:username, :password)
|
56
|
+
user ||= configuration[:user]
|
57
|
+
opts.reverse_merge!(user: user, password: pass)
|
58
|
+
cmd = [isql_executable, configuration[:database]]
|
59
|
+
cmd += opts.map { |name, val| "-#{name} #{val}" }
|
60
|
+
cmd += args.map { |flag| "-#{flag}" }
|
61
|
+
cmd = cmd.join(' ')
|
62
|
+
raise "Error running: #{cmd}" unless Kernel.system(cmd)
|
63
|
+
end
|
64
|
+
|
65
|
+
def isql_create(*_args)
|
66
|
+
"#{isql_executable} -input "
|
67
|
+
end
|
68
|
+
|
69
|
+
# Finds the isql command line utility from the PATH
|
70
|
+
# Many linux distros call this program isql-fb, instead of isql
|
71
|
+
def isql_executable
|
72
|
+
require 'mkmf'
|
73
|
+
exe = %w[isql-fb isql].detect(&method(:find_executable0))
|
74
|
+
exe || abort('Unable to find isql or isql-fb in your $PATH')
|
75
|
+
end
|
76
|
+
|
77
|
+
attr_reader :configuration
|
78
|
+
|
79
|
+
attr_reader :root
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'active_record/connection_adapters/rdb_adapter'
|
2
|
+
|
3
|
+
if defined?(::Rails::Railtie) && ::ActiveRecord::VERSION::MAJOR > 3
|
4
|
+
class Railtie < ::Rails::Railtie # :nodoc:
|
5
|
+
rake_tasks do
|
6
|
+
load 'active_record/tasks/rdb_database_tasks.rb'
|
7
|
+
ActiveRecord::Tasks::DatabaseTasks.register_task(/rdb/, ActiveRecord::Tasks::RdbDatabaseTasks)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Arel
|
2
|
+
module Visitors
|
3
|
+
class Rdb < Arel::Visitors::ToSql # :nodoc
|
4
|
+
|
5
|
+
def preparable
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def visit_Arel_Nodes_SelectStatement o, collector
|
12
|
+
collector << "SELECT "
|
13
|
+
collector = visit o.offset, collector if o.offset && !o.limit
|
14
|
+
|
15
|
+
collector = o.cores.inject(collector) {|c, x|
|
16
|
+
visit_Arel_Nodes_SelectCore(x, c)
|
17
|
+
}
|
18
|
+
|
19
|
+
unless o.orders.empty?
|
20
|
+
collector << ORDER_BY
|
21
|
+
len = o.orders.length - 1
|
22
|
+
o.orders.each_with_index {|x, i|
|
23
|
+
collector = visit(x, collector)
|
24
|
+
collector << COMMA unless len == i
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
if o.limit && o.offset
|
29
|
+
collector = limit_with_rows o, collector
|
30
|
+
elsif o.limit && !o.offset
|
31
|
+
collector = visit o.limit, collector
|
32
|
+
end
|
33
|
+
|
34
|
+
maybe_visit o.lock, collector
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_Arel_Nodes_SelectCore o, collector
|
38
|
+
if o.set_quantifier
|
39
|
+
collector = visit o.set_quantifier, collector
|
40
|
+
collector << SPACE
|
41
|
+
end
|
42
|
+
|
43
|
+
unless o.projections.empty?
|
44
|
+
len = o.projections.length - 1
|
45
|
+
o.projections.each_with_index do |x, i|
|
46
|
+
collector = visit(x, collector)
|
47
|
+
collector << COMMA unless len == i
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if o.source && !o.source.empty?
|
52
|
+
collector << " FROM "
|
53
|
+
collector = visit o.source, collector
|
54
|
+
end
|
55
|
+
|
56
|
+
unless o.wheres.empty?
|
57
|
+
collector << WHERE
|
58
|
+
len = o.wheres.length - 1
|
59
|
+
o.wheres.each_with_index do |x, i|
|
60
|
+
collector = visit(x, collector)
|
61
|
+
collector << AND unless len == i
|
62
|
+
end
|
63
|
+
end
|
64
|
+
unless o.groups.empty?
|
65
|
+
collector << GROUP_BY
|
66
|
+
len = o.groups.length - 1
|
67
|
+
o.groups.each_with_index do |x, i|
|
68
|
+
collector = visit(x, collector)
|
69
|
+
collector << COMMA unless len == i
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if Rails::VERSION::MAJOR < 5
|
74
|
+
collector = maybe_visit o.having, collector
|
75
|
+
else
|
76
|
+
unless o.havings.empty?
|
77
|
+
collector << " HAVING "
|
78
|
+
inject_join o.havings, collector, AND
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
collector
|
83
|
+
end
|
84
|
+
|
85
|
+
def visit_Arel_Nodes_Limit o, collector
|
86
|
+
collector << " ROWS "
|
87
|
+
visit o.expr, collector
|
88
|
+
end
|
89
|
+
|
90
|
+
def visit_Arel_Nodes_Offset o, collector
|
91
|
+
collector << " SKIP "
|
92
|
+
visit o.expr, collector
|
93
|
+
end
|
94
|
+
|
95
|
+
def limit_with_rows o, collector
|
96
|
+
o.offset.expr.value = ActiveModel::Attribute.with_cast_value("OFFSET".freeze,
|
97
|
+
o.offset.expr.value.value + 1,
|
98
|
+
ActiveModel::Type.default_value)
|
99
|
+
offset = o.offset.expr.value
|
100
|
+
o.limit.expr.value = ActiveModel::Attribute.with_cast_value("LIMIT".freeze,
|
101
|
+
(o.limit.expr.value.value) + (offset.value - 1),
|
102
|
+
ActiveModel::Type.default_value)
|
103
|
+
limit = o.limit.expr.value
|
104
|
+
collector << " ROWS "
|
105
|
+
collector.add_bind(offset) {|i| "?"}
|
106
|
+
collector << " TO "
|
107
|
+
collector.add_bind(limit) {|i| "?"}
|
108
|
+
end
|
109
|
+
|
110
|
+
def quote_column_name name
|
111
|
+
return name if Arel::Nodes::SqlLiteral === name
|
112
|
+
@connection.quote_column_name(name)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord-rdb-adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrey Lobanov (RedSoft)
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-03-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: fb
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.1'
|
41
|
+
description: ActiveRecord Firebird and RedDatabase Adapter for Rails 5+
|
42
|
+
email: andrey.lobanov@red-soft.ru
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- README.md
|
48
|
+
- lib/active_record/connection_adapters/rdb/database_limits.rb
|
49
|
+
- lib/active_record/connection_adapters/rdb/database_statements.rb
|
50
|
+
- lib/active_record/connection_adapters/rdb/quoting.rb
|
51
|
+
- lib/active_record/connection_adapters/rdb/schema_creation.rb
|
52
|
+
- lib/active_record/connection_adapters/rdb/schema_dumper.rb
|
53
|
+
- lib/active_record/connection_adapters/rdb/schema_statements.rb
|
54
|
+
- lib/active_record/connection_adapters/rdb/table_definition.rb
|
55
|
+
- lib/active_record/connection_adapters/rdb_adapter.rb
|
56
|
+
- lib/active_record/connection_adapters/rdb_column.rb
|
57
|
+
- lib/active_record/rdb_base.rb
|
58
|
+
- lib/active_record/tasks/rdb_database_tasks.rb
|
59
|
+
- lib/activerecord-rdb-adapter.rb
|
60
|
+
- lib/arel/visitors/rdb_visitor.rb
|
61
|
+
homepage: http://gitlab.red-soft.biz/andrey.lobanov/activerecord-rdb-adapter
|
62
|
+
licenses:
|
63
|
+
- MIT
|
64
|
+
metadata: {}
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements:
|
80
|
+
- Firebird library fb
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 2.7.6
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: ActiveRecord Firebird and RedDatabase Adapter
|
86
|
+
test_files: []
|