activerecord-fb-adapter 0.9.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -3
- data/lib/active_record/connection_adapters/fb/database_statements.rb +55 -56
- data/lib/active_record/connection_adapters/fb/quoting.rb +47 -53
- data/lib/active_record/connection_adapters/fb/schema_statements.rb +118 -60
- data/lib/active_record/connection_adapters/fb_adapter.rb +31 -33
- data/lib/active_record/connection_adapters/fb_column.rb +37 -53
- data/lib/active_record/fb_base.rb +1 -1
- data/lib/active_record/tasks/fb_database_tasks.rb +75 -0
- data/lib/activerecord-fb-adapter.rb +14 -0
- data/lib/arel/visitors/fb_collector.rb +96 -0
- metadata +83 -10
- checksums.yaml +0 -7
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# ActiveRecord Firebird Adapter
|
2
2
|
[![Gem Version](https://badge.fury.io/rb/activerecord-fb-adapter.svg)](http://badge.fury.io/rb/activerecord-fb-adapter)
|
3
|
+
[![Build Status](https://travis-ci.org/rowland/activerecord-fb-adapter.png?branch=master)](https://travis-ci.org/rowland/activerecord-fb-adapter)
|
3
4
|
|
4
5
|
<img src="/project_logo.png" align="left" hspace="10">
|
5
6
|
This is the ActiveRecord adapter for working with the [Firebird SQL Server](http://firebirdsql.org/). It currently supports Rails 3.2.x and 4.x. Although this adapter may not yet have feature parity with the 1st tier databases supported by Rails, it has been used in production by different people for several months without issues and may be considered stable. It uses under the hood the [Ruby Firebird Extension Library](https://github.com/rowland/fb).
|
@@ -30,10 +31,10 @@ gem 'activerecord-fb-adapter'
|
|
30
31
|
Then run:
|
31
32
|
|
32
33
|
```
|
33
|
-
bundle
|
34
|
+
bundle install
|
34
35
|
```
|
35
36
|
|
36
|
-
|
37
|
+
Bundler will install the gem and it's dependency, Fb, which is "native" (has C code) and will be compiled the first time. Be sure you have a Firebird installation with access to the "ibase.h" file for this to succeed.
|
37
38
|
|
38
39
|
4) Edit the **database.yml** for configuring your database connection:
|
39
40
|
|
@@ -50,7 +51,7 @@ development:
|
|
50
51
|
|
51
52
|
The default Firebird administrator username and password are **SYSDBA** and **masterkey**, you may have to adjust this to your installation.
|
52
53
|
|
53
|
-
|
54
|
+
With the "create: true" option, a database will be created the first time the adapter tries to connect if it doesn't exist. Alternatively, if you're using Rails 4 or greater, you can use the "rake db:create" task.
|
54
55
|
|
55
56
|
5) Start the rails server in development mode
|
56
57
|
|
@@ -8,16 +8,9 @@ module ActiveRecord
|
|
8
8
|
exec_query(sql, name, binds).to_a.map(&:values)
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
if (name == :skip_logging) || skip_logging
|
15
|
-
@connection.execute(translated, *args)
|
16
|
-
else
|
17
|
-
log(sql, args, name) do
|
18
|
-
@connection.execute(translated, *args)
|
19
|
-
end
|
20
|
-
end
|
11
|
+
def execute(sql, name = nil)
|
12
|
+
translate_and_log(sql, [], name) do |args|
|
13
|
+
@connection.execute(*args)
|
21
14
|
end
|
22
15
|
end
|
23
16
|
|
@@ -25,15 +18,13 @@ module ActiveRecord
|
|
25
18
|
# +binds+ as the bind substitutes. +name+ is logged along with
|
26
19
|
# the executed +sql+ statement.
|
27
20
|
def exec_query(sql, name = 'SQL', binds = [])
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
[cursor.fields, cursor.fetchall]
|
32
|
-
end
|
33
|
-
next result unless result.respond_to?(:map)
|
34
|
-
cols = result.map { |col| col.name }
|
35
|
-
ActiveRecord::Result.new(cols, rows)
|
21
|
+
translate_and_log(sql, binds, name) do |args|
|
22
|
+
result, rows = @connection.execute(*args) do |cursor|
|
23
|
+
[cursor.fields, cursor.fetchall]
|
36
24
|
end
|
25
|
+
next result unless result.respond_to?(:map)
|
26
|
+
cols = result.map { |col| col.name }
|
27
|
+
ActiveRecord::Result.new(cols, rows)
|
37
28
|
end
|
38
29
|
end
|
39
30
|
|
@@ -41,54 +32,38 @@ module ActiveRecord
|
|
41
32
|
to_sql(arel, binds)
|
42
33
|
end
|
43
34
|
|
44
|
-
# Checks whether there is currently no transaction active. This is done
|
45
|
-
# by querying the database driver, and does not use the transaction
|
46
|
-
# house-keeping information recorded by #increment_open_transactions and
|
47
|
-
# friends.
|
48
|
-
#
|
49
|
-
# Returns true if there is no transaction active, false if there is a
|
50
|
-
# transaction active, and nil if this information is unknown.
|
51
|
-
def outside_transaction?
|
52
|
-
!@connection.transaction_started
|
53
|
-
end
|
54
|
-
|
55
35
|
# Begins the transaction (and turns off auto-committing).
|
56
36
|
def begin_db_transaction
|
57
|
-
|
37
|
+
log('begin transaction', nil) do
|
38
|
+
begin_isolated_db_transaction(default_transaction_isolation)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Default isolation levels for transactions. This method exists
|
43
|
+
# in 4.0.2+, so it's here for backward compatibility with AR 3
|
44
|
+
def transaction_isolation_levels
|
45
|
+
{
|
46
|
+
read_committed: "READ COMMITTED",
|
47
|
+
repeatable_read: "REPEATABLE READ",
|
48
|
+
serializable: "SERIALIZABLE"
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Allows providing the :transaction option to ActiveRecord::Base.transaction
|
53
|
+
# in 4.0.2+. Can accept verbatim isolation options like 'WAIT READ COMMITTED'
|
54
|
+
def begin_isolated_db_transaction(isolation)
|
55
|
+
@connection.transaction transaction_isolation_levels.fetch(isolation, isolation)
|
58
56
|
end
|
59
57
|
|
60
58
|
# Commits the transaction (and turns on auto-committing).
|
61
59
|
def commit_db_transaction
|
62
|
-
@connection.commit
|
60
|
+
log('commit transaction', nil) { @connection.commit }
|
63
61
|
end
|
64
62
|
|
65
63
|
# Rolls back the transaction (and turns on auto-committing). Must be
|
66
64
|
# done if the transaction block raises an exception or returns false.
|
67
65
|
def rollback_db_transaction
|
68
|
-
@connection.rollback
|
69
|
-
end
|
70
|
-
|
71
|
-
# Appends +LIMIT+ and +OFFSET+ options to an SQL statement, or some SQL
|
72
|
-
# fragment that has the same semantics as LIMIT and OFFSET.
|
73
|
-
#
|
74
|
-
# +options+ must be a Hash which contains a +:limit+ option
|
75
|
-
# and an +:offset+ option.
|
76
|
-
#
|
77
|
-
# This method *modifies* the +sql+ parameter.
|
78
|
-
#
|
79
|
-
# ===== Examples
|
80
|
-
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
|
81
|
-
# generates
|
82
|
-
# SELECT * FROM suppliers LIMIT 10 OFFSET 50
|
83
|
-
def add_limit_offset!(sql, options) # :nodoc:
|
84
|
-
if limit = options[:limit]
|
85
|
-
if offset = options[:offset]
|
86
|
-
sql << " ROWS #{offset.to_i + 1} TO #{offset.to_i + limit.to_i}"
|
87
|
-
else
|
88
|
-
sql << " ROWS #{limit.to_i}"
|
89
|
-
end
|
90
|
-
end
|
91
|
-
sql
|
66
|
+
log('rollback transaction', nil) { @connection.rollback }
|
92
67
|
end
|
93
68
|
|
94
69
|
def default_sequence_name(table_name, _column = nil)
|
@@ -113,7 +88,7 @@ module ActiveRecord
|
|
113
88
|
# column values as values. ActiveRecord >= 4 returns an ActiveRecord::Result.
|
114
89
|
def select(sql, name = nil, binds = [])
|
115
90
|
result = exec_query(sql, name, binds)
|
116
|
-
ActiveRecord::VERSION::MAJOR > 3 ? result : result.to_a
|
91
|
+
::ActiveRecord::VERSION::MAJOR > 3 ? result : result.to_a
|
117
92
|
end
|
118
93
|
|
119
94
|
# Since the ID is prefetched and passed to #insert, this method is useless.
|
@@ -121,6 +96,30 @@ module ActiveRecord
|
|
121
96
|
def last_inserted_id(_result)
|
122
97
|
nil
|
123
98
|
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def translate_and_log(sql, binds = [], name = nil)
|
103
|
+
if ActiveRecord::VERSION::STRING < "4.2.0"
|
104
|
+
values = binds.map { |b| type_cast(*b.reverse) }
|
105
|
+
else
|
106
|
+
values = []
|
107
|
+
end
|
108
|
+
|
109
|
+
if sql =~ /(CREATE TABLE|ALTER TABLE)/
|
110
|
+
sql.gsub!(/(\@BINDDATE|BINDDATE\@)/m, '\'')
|
111
|
+
else
|
112
|
+
sql.gsub!(/\@BINDBINARY(.*?)BINDBINARY\@/m) do |extract|
|
113
|
+
values << decode(extract[11...-11]) and '?'
|
114
|
+
end
|
115
|
+
|
116
|
+
sql.gsub!(/\@BINDDATE(.*?)BINDDATE\@/m) do |extract|
|
117
|
+
values << extract[9...-9] and '?'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
log(sql, name, binds) { yield [sql, *values] }
|
122
|
+
end
|
124
123
|
end
|
125
124
|
end
|
126
125
|
end
|
@@ -3,7 +3,6 @@ module ActiveRecord
|
|
3
3
|
module Fb
|
4
4
|
module Quoting
|
5
5
|
def quote(value, column = nil)
|
6
|
-
# records are quoted as their primary key
|
7
6
|
return value.quoted_id if value.respond_to?(:quoted_id)
|
8
7
|
type = column && column.type
|
9
8
|
|
@@ -11,81 +10,66 @@ module ActiveRecord
|
|
11
10
|
when String, ActiveSupport::Multibyte::Chars
|
12
11
|
value = value.to_s
|
13
12
|
if [:integer, :float].include?(type)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
"'#{quote_string(value)}'"
|
18
|
-
else
|
19
|
-
"@#{Base64.encode64(value).chop}@"
|
20
|
-
end
|
21
|
-
when nil then "NULL"
|
22
|
-
when true then quoted_true
|
23
|
-
when false then quoted_false
|
24
|
-
when Numeric, ActiveSupport::Duration then value.to_s
|
25
|
-
# BigDecimals need to be output in a non-normalized form and quoted.
|
26
|
-
when BigDecimal then value.to_s('F')
|
27
|
-
when Symbol then "'#{quote_string(value.to_s)}'"
|
28
|
-
when Class then "'#{value}'"
|
29
|
-
else
|
30
|
-
if value.acts_like?(:date)
|
31
|
-
quote_date(value)
|
32
|
-
elsif value.acts_like?(:time)
|
33
|
-
quote_timestamp(value)
|
13
|
+
(type == :integer ? value.to_i : value.to_f).to_s
|
14
|
+
elsif type && type == :binary
|
15
|
+
"@BINDBINARY#{Base64.encode64(value.to_s)}BINDBINARY@"
|
34
16
|
else
|
35
|
-
|
17
|
+
"'#{quote_string(value)}'"
|
36
18
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
def quote_date(value)
|
41
|
-
"@#{Base64.encode64(value.strftime('%Y-%m-%d')).chop}@"
|
42
|
-
end
|
43
|
-
|
44
|
-
def quote_timestamp(value)
|
45
|
-
get = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
46
|
-
value = value.respond_to?(get) ? value.send(get) : value
|
47
|
-
"@#{Base64.encode64(value.strftime('%Y-%m-%d %H:%M:%S')).chop}@"
|
48
|
-
end
|
49
|
-
|
50
|
-
def quote_string(string) # :nodoc:
|
51
|
-
string.gsub(/'/, "''")
|
52
|
-
end
|
53
|
-
|
54
|
-
def quote_object(obj)
|
55
|
-
if obj.respond_to?(:to_str)
|
56
|
-
"@#{Base64.encode64(obj.to_str).chop}@"
|
19
|
+
when Date, Time
|
20
|
+
"@BINDDATE#{quoted_date(value)}BINDDATE@"
|
57
21
|
else
|
58
|
-
|
22
|
+
super
|
59
23
|
end
|
60
|
-
end
|
24
|
+
end if ActiveRecord::VERSION::STRING < "4.2.0"
|
61
25
|
|
62
26
|
def quote_column_name(column_name) # :nodoc:
|
63
|
-
|
64
|
-
|
65
|
-
else
|
66
|
-
%Q("#{ar_to_fb_case(column_name.to_s)}")
|
67
|
-
end
|
27
|
+
name = ar_to_fb_case(column_name.to_s).gsub('"', '')
|
28
|
+
@connection.dialect == 1 ? %Q(#{name}) : %Q("#{name}")
|
68
29
|
end
|
69
30
|
|
70
31
|
def quote_table_name_for_assignment(_table, attr)
|
71
32
|
quote_column_name(attr)
|
72
33
|
end if ::ActiveRecord::VERSION::MAJOR >= 4
|
73
34
|
|
35
|
+
def unquoted_true
|
36
|
+
boolean_domain[:true]
|
37
|
+
end
|
38
|
+
|
74
39
|
def quoted_true # :nodoc:
|
75
|
-
quote
|
40
|
+
quote unquoted_true
|
41
|
+
end
|
42
|
+
|
43
|
+
def unquoted_false
|
44
|
+
boolean_domain[:false]
|
76
45
|
end
|
77
46
|
|
78
47
|
def quoted_false # :nodoc:
|
79
|
-
quote
|
48
|
+
quote unquoted_false
|
80
49
|
end
|
81
50
|
|
82
51
|
def type_cast(value, column)
|
83
|
-
|
84
|
-
|
52
|
+
if [true, false].include?(value)
|
53
|
+
value ? quoted_true : quoted_false
|
54
|
+
else
|
55
|
+
super
|
56
|
+
end
|
85
57
|
end
|
86
58
|
|
87
59
|
private
|
88
60
|
|
61
|
+
# Types that are bind parameters will not be quoted
|
62
|
+
def _quote(value)
|
63
|
+
case value
|
64
|
+
when Type::Binary::Data
|
65
|
+
"@BINDBINARY#{Base64.encode64(value.to_s)}BINDBINARY@"
|
66
|
+
when Date, Time
|
67
|
+
"@BINDDATE#{quoted_date(value)}BINDDATE@"
|
68
|
+
else
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
89
73
|
# Maps uppercase Firebird column names to lowercase for ActiveRecord;
|
90
74
|
# mixed-case columns retain their original case.
|
91
75
|
def fb_to_ar_case(column_name)
|
@@ -97,6 +81,16 @@ module ActiveRecord
|
|
97
81
|
def ar_to_fb_case(column_name)
|
98
82
|
column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
|
99
83
|
end
|
84
|
+
|
85
|
+
if defined? Encoding
|
86
|
+
def decode(s)
|
87
|
+
Base64.decode64(s).force_encoding(@connection.encoding)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
def decode(s)
|
91
|
+
Base64.decode64(s)
|
92
|
+
end
|
93
|
+
end
|
100
94
|
end
|
101
95
|
end
|
102
96
|
end
|
@@ -29,66 +29,69 @@ module ActiveRecord
|
|
29
29
|
# Returns an array of indexes for the given table.
|
30
30
|
def indexes(table_name, _name = nil)
|
31
31
|
@connection.indexes.values.map { |ix|
|
32
|
-
if ix.table_name == table_name && ix.index_name !~ /^rdb\$/
|
32
|
+
if ix.table_name == table_name.to_s && ix.index_name !~ /^rdb\$/
|
33
33
|
IndexDefinition.new(table_name, ix.index_name, ix.unique, ix.columns)
|
34
34
|
end
|
35
35
|
}.compact
|
36
36
|
end
|
37
37
|
|
38
38
|
def primary_key(table_name) #:nodoc:
|
39
|
-
|
39
|
+
row = @connection.query(<<-end_sql)
|
40
40
|
SELECT s.rdb$field_name
|
41
41
|
FROM rdb$indices i
|
42
42
|
JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
|
43
43
|
LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name
|
44
|
-
WHERE i.rdb$relation_name = '#{ar_to_fb_case(table_name)}'
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
WHERE i.rdb$relation_name = '#{ar_to_fb_case(table_name)}'
|
45
|
+
AND c.rdb$constraint_type = 'PRIMARY KEY';
|
46
|
+
end_sql
|
47
|
+
|
48
|
+
row.first && fb_to_ar_case(row.first[0].rstrip)
|
48
49
|
end
|
49
50
|
|
50
51
|
# Returns an array of Column objects for the table specified by +table_name+.
|
51
52
|
# See the concrete implementation for details on the expected parameter values.
|
52
|
-
def columns(table_name,
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
|
60
|
-
WHERE r.rdb$relation_name = '#{ar_to_fb_case(table_name)}'
|
61
|
-
ORDER BY r.rdb$field_position
|
62
|
-
END_SQL
|
63
|
-
select_rows(sql, name).map do |field|
|
64
|
-
FbColumn.new(*field.map { |value|
|
65
|
-
value.is_a?(String) ? value.rstrip : value
|
66
|
-
})
|
53
|
+
def columns(table_name, _name = nil)
|
54
|
+
column_definitions(table_name).map do |field|
|
55
|
+
field.symbolize_keys!.each { |k, v| v.rstrip! if v.is_a?(String) }
|
56
|
+
properties = field.values_at(:name, :default_source)
|
57
|
+
properties += column_type_for(field)
|
58
|
+
properties << !field[:null_flag]
|
59
|
+
FbColumn.new(*properties, field.slice(:domain, :sub_type))
|
67
60
|
end
|
68
61
|
end
|
69
62
|
|
70
63
|
def create_table(name, options = {}) # :nodoc:
|
64
|
+
if options.key? :temporary
|
65
|
+
fail ActiveRecordError, 'Firebird does not support temporary tables'
|
66
|
+
end
|
67
|
+
|
68
|
+
if options.key? :as
|
69
|
+
fail ActiveRecordError, 'Firebird does not support creating tables with a select'
|
70
|
+
end
|
71
|
+
|
71
72
|
needs_sequence = options[:id] != false
|
72
73
|
while_ensuring_boolean_domain do
|
73
|
-
super
|
74
|
+
super name, options do |table_def|
|
74
75
|
yield table_def if block_given?
|
75
76
|
needs_sequence ||= table_def.needs_sequence
|
76
77
|
end
|
77
78
|
end
|
79
|
+
|
78
80
|
return if options[:sequence] == false || !needs_sequence
|
79
81
|
create_sequence(options[:sequence] || default_sequence_name(name))
|
80
82
|
end
|
81
83
|
|
82
|
-
# Unfortunately, this is a limitation of Firebird.
|
83
84
|
def rename_table(name, new_name)
|
84
|
-
fail 'Firebird does not support renaming tables.'
|
85
|
+
fail ActiveRecordError, 'Firebird does not support renaming tables.'
|
85
86
|
end
|
86
87
|
|
87
88
|
def drop_table(name, options = {}) # :nodoc:
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
89
|
+
unless options[:sequence] == false
|
90
|
+
sequence_name = options[:sequence] || default_sequence_name(name)
|
91
|
+
drop_sequence(sequence_name) if sequence_exists?(sequence_name)
|
92
|
+
end
|
93
|
+
|
94
|
+
super
|
92
95
|
end
|
93
96
|
|
94
97
|
# Creates a sequence
|
@@ -108,16 +111,29 @@ module ActiveRecord
|
|
108
111
|
# Adds a new column to the named table.
|
109
112
|
# See TableDefinition#column for details of the options you can use.
|
110
113
|
def add_column(table_name, column_name, type, options = {})
|
111
|
-
|
112
|
-
|
113
|
-
while_ensuring_boolean_domain { execute(add_column_sql) }
|
114
|
+
while_ensuring_boolean_domain { super }
|
115
|
+
|
114
116
|
if type == :primary_key && options[:sequence] != false
|
115
117
|
create_sequence(options[:sequence] || default_sequence_name(table_name))
|
116
118
|
end
|
119
|
+
|
117
120
|
return unless options[:position]
|
118
121
|
# position is 1-based but add 1 to skip id column
|
119
|
-
|
120
|
-
|
122
|
+
execute(squish_sql(<<-end_sql))
|
123
|
+
ALTER TABLE #{quote_table_name(table_name)}
|
124
|
+
ALTER COLUMN #{quote_column_name(column_name)}
|
125
|
+
POSITION #{options[:position] + 1}
|
126
|
+
end_sql
|
127
|
+
end
|
128
|
+
|
129
|
+
def remove_column(table_name, column_name, type = nil, options = {})
|
130
|
+
indexes(table_name).each do |i|
|
131
|
+
if i.columns.any? { |c| c == column_name.to_s }
|
132
|
+
remove_index! i.table, i.name
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
super
|
121
137
|
end
|
122
138
|
|
123
139
|
# Changes the column's definition according to the new options.
|
@@ -126,10 +142,15 @@ module ActiveRecord
|
|
126
142
|
# change_column(:suppliers, :name, :string, :limit => 80)
|
127
143
|
# change_column(:accounts, :description, :text)
|
128
144
|
def change_column(table_name, column_name, type, options = {})
|
129
|
-
|
130
|
-
|
145
|
+
type_sql = type_to_sql(type, *options.values_at(:limit, :precision, :scale))
|
146
|
+
|
147
|
+
execute(squish_sql(<<-end_sql))
|
148
|
+
ALTER TABLE #{quote_table_name(table_name)}
|
149
|
+
ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_sql}
|
150
|
+
end_sql
|
151
|
+
|
131
152
|
change_column_null(table_name, column_name, !!options[:null]) if options.key?(:null)
|
132
|
-
change_column_default(table_name, column_name, options[:default]) if options
|
153
|
+
change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
|
133
154
|
end
|
134
155
|
|
135
156
|
# Sets a new default value for a column. If you want to set the default
|
@@ -139,23 +160,39 @@ module ActiveRecord
|
|
139
160
|
# change_column_default(:suppliers, :qualification, 'new')
|
140
161
|
# change_column_default(:accounts, :authorized, 1)
|
141
162
|
def change_column_default(table_name, column_name, default)
|
142
|
-
execute(
|
163
|
+
execute(squish_sql(<<-end_sql))
|
164
|
+
ALTER TABLE #{quote_table_name(table_name)}
|
165
|
+
ALTER #{quote_column_name(column_name)}
|
166
|
+
SET DEFAULT #{quote(default)}
|
167
|
+
end_sql
|
143
168
|
end
|
144
169
|
|
145
170
|
def change_column_null(table_name, column_name, null, default = nil)
|
146
|
-
|
171
|
+
change_column_default(table_name, column_name, default) if default
|
172
|
+
|
173
|
+
execute(squish_sql(<<-end_sql))
|
174
|
+
UPDATE RDB$RELATION_FIELDS
|
175
|
+
SET RDB$NULL_FLAG=#{quote(null ? nil : 1)}
|
176
|
+
WHERE RDB$FIELD_NAME='#{ar_to_fb_case(column_name)}'
|
177
|
+
AND RDB$RELATION_NAME='#{ar_to_fb_case(table_name)}'
|
178
|
+
end_sql
|
147
179
|
end
|
148
180
|
|
149
181
|
# Renames a column.
|
150
182
|
# ===== Example
|
151
183
|
# rename_column(:suppliers, :description, :name)
|
152
184
|
def rename_column(table_name, column_name, new_column_name)
|
153
|
-
execute
|
185
|
+
execute(squish_sql(<<-end_sql))
|
186
|
+
ALTER TABLE #{quote_table_name(table_name)}
|
187
|
+
ALTER #{quote_column_name(column_name)}
|
188
|
+
TO #{quote_column_name(new_column_name)}
|
189
|
+
end_sql
|
190
|
+
|
154
191
|
rename_column_indexes(table_name, column_name, new_column_name)
|
155
192
|
end
|
156
193
|
|
157
194
|
def remove_index!(_table_name, index_name) #:nodoc:
|
158
|
-
execute
|
195
|
+
execute "DROP INDEX #{quote_column_name(index_name)}"
|
159
196
|
end
|
160
197
|
|
161
198
|
def index_name(table_name, options) #:nodoc:
|
@@ -175,23 +212,44 @@ module ActiveRecord
|
|
175
212
|
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
176
213
|
case type
|
177
214
|
when :integer then integer_to_sql(limit)
|
178
|
-
when :float
|
215
|
+
when :float then float_to_sql(limit)
|
179
216
|
else super
|
180
217
|
end
|
181
218
|
end
|
182
219
|
|
183
|
-
# Deprecated in Rails 4.1. Backports functionality.
|
184
|
-
def add_column_options!(sql, options)
|
185
|
-
if options_include_default?(options)
|
186
|
-
sql << " DEFAULT #{quote(options[:default], options[:column])}"
|
187
|
-
end
|
188
|
-
# must explicitly check for :null to allow change_column to work on migrations
|
189
|
-
sql << ' NOT NULL' if options[:null] == false
|
190
|
-
end if ActiveRecord::VERSION::MAJOR > 3
|
191
|
-
|
192
220
|
private
|
193
221
|
|
194
|
-
|
222
|
+
def column_definitions(table_name)
|
223
|
+
exec_query(squish_sql(<<-end_sql), 'SCHEMA')
|
224
|
+
SELECT
|
225
|
+
r.rdb$field_name name,
|
226
|
+
r.rdb$field_source domain,
|
227
|
+
f.rdb$field_type type,
|
228
|
+
f.rdb$field_sub_type sub_type,
|
229
|
+
f.rdb$field_length "limit",
|
230
|
+
f.rdb$field_precision "precision",
|
231
|
+
f.rdb$field_scale "scale",
|
232
|
+
COALESCE(r.rdb$default_source, f.rdb$default_source) default_source,
|
233
|
+
COALESCE(r.rdb$null_flag, f.rdb$null_flag) null_flag
|
234
|
+
FROM rdb$relation_fields r
|
235
|
+
JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
|
236
|
+
WHERE r.rdb$relation_name = '#{ar_to_fb_case(table_name)}'
|
237
|
+
ORDER BY r.rdb$field_position
|
238
|
+
end_sql
|
239
|
+
end
|
240
|
+
|
241
|
+
# We need to be very precise about our sql types.
|
242
|
+
def column_type_for(field)
|
243
|
+
sql_type = FbColumn.sql_type_for(field)
|
244
|
+
|
245
|
+
if ActiveRecord::VERSION::STRING < "4.2.0"
|
246
|
+
[sql_type]
|
247
|
+
else
|
248
|
+
[lookup_cast_type(sql_type), sql_type]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
if ::ActiveRecord::VERSION::MAJOR > 3
|
195
253
|
def create_table_definition(*args)
|
196
254
|
TableDefinition.new(native_database_types, *args)
|
197
255
|
end
|
@@ -209,33 +267,33 @@ module ActiveRecord
|
|
209
267
|
when 3..4 then 'integer'
|
210
268
|
when 5..8 then 'bigint'
|
211
269
|
else
|
212
|
-
fail ActiveRecordError, "No integer type has byte size #{limit}.
|
270
|
+
fail ActiveRecordError, "No integer type has byte size #{limit}. "\
|
271
|
+
"Use a NUMERIC with PRECISION 0 instead."
|
213
272
|
end
|
214
273
|
end
|
215
274
|
|
216
275
|
def float_to_sql(limit)
|
217
|
-
|
218
|
-
'float'
|
219
|
-
else
|
220
|
-
'double precision'
|
221
|
-
end
|
276
|
+
(limit.nil? || limit <= 4) ? 'float' : 'double precision'
|
222
277
|
end
|
223
278
|
|
224
279
|
# Creates a domain for boolean fields as needed
|
225
280
|
def while_ensuring_boolean_domain(&block)
|
226
281
|
block.call
|
227
|
-
rescue
|
282
|
+
rescue ActiveRecordError => e
|
228
283
|
raise unless e.message =~ /Specified domain or source column \w+ does not exist/
|
229
284
|
create_boolean_domain
|
230
285
|
block.call
|
231
286
|
end
|
232
287
|
|
288
|
+
def squish_sql(sql)
|
289
|
+
sql.strip.gsub(/\s+/, ' ')
|
290
|
+
end
|
291
|
+
|
233
292
|
def create_boolean_domain
|
234
|
-
|
293
|
+
execute(squish_sql(<<-end_sql))
|
235
294
|
CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]}
|
236
295
|
CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL)
|
237
296
|
end_sql
|
238
|
-
execute(sql)
|
239
297
|
end
|
240
298
|
|
241
299
|
def sequence_exists?(sequence_name)
|
@@ -1,10 +1,18 @@
|
|
1
|
+
|
1
2
|
# Rails 3 & 4 specific database adapter for Firebird (http://firebirdsql.org)
|
2
3
|
# Author: Brent Rowland <rowland@rowlandresearch.com>
|
3
4
|
# Based originally on FireRuby extension by Ken Kunz <kennethkunz@gmail.com>
|
4
5
|
|
6
|
+
require 'fb'
|
5
7
|
require 'base64'
|
6
8
|
require 'arel'
|
7
|
-
|
9
|
+
|
10
|
+
if Arel::VERSION < "6.0.0"
|
11
|
+
require 'arel/visitors/fb'
|
12
|
+
else
|
13
|
+
require 'arel/visitors/fb_collector'
|
14
|
+
end
|
15
|
+
|
8
16
|
require 'arel/visitors/bind_visitor'
|
9
17
|
require 'active_record'
|
10
18
|
require 'active_record/base'
|
@@ -125,11 +133,19 @@ module ActiveRecord
|
|
125
133
|
include Fb::SchemaStatements
|
126
134
|
|
127
135
|
@@boolean_domain = { :true => 1, :false => 0, :name => 'BOOLEAN', :type => 'integer' }
|
128
|
-
|
136
|
+
cattr_reader :boolean_domain
|
137
|
+
|
138
|
+
def self.boolean_domain=(domain)
|
139
|
+
FbColumn::TRUE_VALUES << domain[:true]
|
140
|
+
@@boolean_domain = domain
|
141
|
+
end
|
142
|
+
|
143
|
+
@@default_transaction_isolation = :read_committed
|
144
|
+
cattr_accessor :default_transaction_isolation
|
129
145
|
|
130
146
|
class BindSubstitution < Arel::Visitors::Fb # :nodoc:
|
131
147
|
include Arel::Visitors::BindVisitor
|
132
|
-
end
|
148
|
+
end if ActiveRecord::VERSION::STRING < "4.2.0"
|
133
149
|
|
134
150
|
def initialize(connection, logger, config=nil)
|
135
151
|
super(connection, logger)
|
@@ -137,10 +153,6 @@ module ActiveRecord
|
|
137
153
|
@visitor = Arel::Visitors::Fb.new(self)
|
138
154
|
end
|
139
155
|
|
140
|
-
def self.visitor_for(pool) # :nodoc:
|
141
|
-
Arel::Visitors::Fb.new(pool)
|
142
|
-
end
|
143
|
-
|
144
156
|
# Returns the human-readable name of the adapter. Use mixed case - one
|
145
157
|
# can always use downcase if needed.
|
146
158
|
def adapter_name
|
@@ -170,7 +182,11 @@ module ActiveRecord
|
|
170
182
|
# CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
|
171
183
|
# SQL Server, and others support this. MySQL and others do not.
|
172
184
|
def supports_ddl_transactions?
|
173
|
-
|
185
|
+
true
|
186
|
+
end
|
187
|
+
|
188
|
+
def supports_transaction_isolation?
|
189
|
+
true
|
174
190
|
end
|
175
191
|
|
176
192
|
# Does this adapter support savepoints? FirebirdSQL does
|
@@ -249,31 +265,11 @@ module ActiveRecord
|
|
249
265
|
|
250
266
|
protected
|
251
267
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
def decode(s)
|
258
|
-
Base64.decode64(s)
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
|
-
def translate(sql, binds = [])
|
263
|
-
sql.gsub!(/\sIN\s+\([^\)]*\)/mi) do |m|
|
264
|
-
m.gsub(/\(([^\)]*)\)/m) do |n|
|
265
|
-
n.gsub(/\@(.*?)\@/m) do |o|
|
266
|
-
"'#{quote_string(decode(o[1..-1]))}'"
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end
|
270
|
-
args = binds.map { |col, val| type_cast(val, col) }
|
271
|
-
sql.gsub!(/\@(.*?)\@/m) { |m| args << decode(m[1..-1]); '?' }
|
272
|
-
yield(sql, args) if block_given?
|
273
|
-
end
|
274
|
-
|
275
|
-
def expand(sql, args)
|
276
|
-
([sql] + args) * ', '
|
268
|
+
# Maps SQL types to ActiveRecord 4.2+ type objects
|
269
|
+
def initialize_type_map(m)
|
270
|
+
super
|
271
|
+
m.register_type %r(timestamp)i, Type::DateTime.new
|
272
|
+
m.alias_type %r(blob sub_type text)i, 'text'
|
277
273
|
end
|
278
274
|
|
279
275
|
def translate_exception(e, message)
|
@@ -282,6 +278,8 @@ module ActiveRecord
|
|
282
278
|
InvalidForeignKey.new(message, e)
|
283
279
|
when /violation of PRIMARY or UNIQUE KEY constraint/, /attempt to store duplicate value/
|
284
280
|
RecordNotUnique.new(message, e)
|
281
|
+
when /This operation is not defined for system tables/
|
282
|
+
ActiveRecordError.new(message)
|
285
283
|
else
|
286
284
|
super
|
287
285
|
end
|
@@ -1,72 +1,56 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
3
|
class FbColumn < Column # :nodoc:
|
4
|
-
|
5
|
-
|
6
|
-
super(name.downcase, nil, @firebird_type, !null_flag)
|
7
|
-
@default = parse_default(default_source) if default_source
|
8
|
-
case @firebird_type
|
9
|
-
when 'VARCHAR', 'CHAR'
|
10
|
-
@limit = length
|
11
|
-
when 'DECIMAL', 'NUMERIC'
|
12
|
-
@precision, @scale = precision, scale.abs
|
13
|
-
end
|
14
|
-
@domain, @sub_type = domain, sub_type
|
15
|
-
end
|
4
|
+
class << self
|
5
|
+
delegate :boolean_domain, to: 'ActiveRecord::ConnectionAdapters::FbAdapter'
|
16
6
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
7
|
+
# When detecting types, ActiveRecord expects strings in a certain format.
|
8
|
+
# In 4.2, these strings are converted to ActiveRecord::Type::Value objects
|
9
|
+
# using the type_map (see #initialize_type_map). Prior to 4.2, the sql_type
|
10
|
+
# could be coerced to a certain ActiveRecord type in Column#simplified_type.
|
11
|
+
def sql_type_for(field)
|
12
|
+
type, sub_type, domain = field.values_at(:type, :sub_type, :domain)
|
13
|
+
sql_type = ::Fb::SqlType.from_code(type, sub_type || 0).downcase
|
26
14
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE"
|
33
|
-
connection = ActiveRecord::Base.connection
|
34
|
-
if connection
|
35
|
-
value = connection.raw_connection.query(:hash, sql)[0]['cast']
|
36
|
-
return nil if value.acts_like?(:date) || value.acts_like?(:time)
|
37
|
-
type_cast(value)
|
38
|
-
else
|
39
|
-
raise ConnectionNotEstablished, "No Firebird connections established."
|
15
|
+
case sql_type
|
16
|
+
when /(numeric|decimal)/
|
17
|
+
sql_type << "(#{field[:precision]},#{field[:scale].abs})"
|
18
|
+
when /(int|float|double|char|varchar)/
|
19
|
+
sql_type << "(#{field[:limit]})"
|
40
20
|
end
|
21
|
+
|
22
|
+
sql_type << ' sub_type text' if sql_type =~ /blob/ && sub_type == 1
|
23
|
+
sql_type = 'boolean' if domain =~ %r(#{boolean_domain[:name]})i
|
24
|
+
sql_type
|
41
25
|
end
|
42
26
|
end
|
43
27
|
|
44
|
-
|
45
|
-
|
28
|
+
attr_reader :sub_type, :domain
|
29
|
+
|
30
|
+
if ActiveRecord::VERSION::STRING < "4.2.0"
|
31
|
+
def initialize(name, default, sql_type = nil, null = true, fb_options = {})
|
32
|
+
@domain, @sub_type = fb_options.values_at(:domain, :sub_type)
|
33
|
+
super(name.downcase, parse_default(default), sql_type, null)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
def initialize(name, default, cast_type, sql_type = nil, null = true, fb_options = {})
|
37
|
+
@domain, @sub_type = fb_options.values_at(:domain, :sub_type)
|
38
|
+
super(name.downcase, parse_default(default), cast_type, sql_type, null)
|
39
|
+
end
|
46
40
|
end
|
47
41
|
|
48
42
|
private
|
49
43
|
|
50
|
-
def parse_default(
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
def column_def
|
56
|
-
case @firebird_type
|
57
|
-
when 'CHAR', 'VARCHAR' then "#{@firebird_type}(#{@limit})"
|
58
|
-
when 'NUMERIC', 'DECIMAL' then "#{@firebird_type}(#{@precision},#{@scale.abs})"
|
59
|
-
#when 'DOUBLE' then "DOUBLE PRECISION"
|
60
|
-
else @firebird_type
|
61
|
-
end
|
44
|
+
def parse_default(default)
|
45
|
+
return if default.nil? || default =~ /null/i
|
46
|
+
default.gsub(/^\s*DEFAULT\s+/i, '').gsub(/(^'|'$)/, '')
|
62
47
|
end
|
63
48
|
|
49
|
+
# Type conversion prior to 4.2
|
64
50
|
def simplified_type(field_type)
|
65
|
-
if field_type
|
66
|
-
|
67
|
-
|
68
|
-
super
|
69
|
-
end
|
51
|
+
return :datetime if field_type =~ /timestamp/
|
52
|
+
return :text if field_type =~ /blob sub_type text/
|
53
|
+
super
|
70
54
|
end
|
71
55
|
end
|
72
56
|
end
|
@@ -18,7 +18,7 @@ module ActiveRecord
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.fb_connection_config(config)
|
21
|
-
config = config.symbolize_keys.reverse_merge(:downcase_names => true, :port => 3050)
|
21
|
+
config = config.symbolize_keys.dup.reverse_merge(:downcase_names => true, :port => 3050)
|
22
22
|
fail ArgumentError, 'No database specified. Missing argument: database.' if !config[:database]
|
23
23
|
if config[:host].nil? || config[:host] =~ /localhost/i
|
24
24
|
config[:database] = File.expand_path(config[:database], defined?(Rails) && Rails.root)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Tasks # :nodoc:
|
3
|
+
class FbDatabaseTasks # :nodoc:
|
4
|
+
delegate :fb_connection_config, :establish_connection, to: ::ActiveRecord::Base
|
5
|
+
|
6
|
+
def initialize(configuration, root = ::ActiveRecord::Tasks::DatabaseTasks.root)
|
7
|
+
@root, @configuration = root, fb_connection_config(configuration)
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
fb_database.create
|
12
|
+
establish_connection configuration
|
13
|
+
rescue ::Fb::Error => e
|
14
|
+
raise unless e.message.include?('database or file exists')
|
15
|
+
raise DatabaseAlreadyExists
|
16
|
+
end
|
17
|
+
|
18
|
+
def drop
|
19
|
+
fb_database.drop
|
20
|
+
rescue ::Fb::Error => e
|
21
|
+
raise ::ActiveRecord::ConnectionNotEstablished, e.message
|
22
|
+
end
|
23
|
+
|
24
|
+
def purge
|
25
|
+
drop
|
26
|
+
create
|
27
|
+
end
|
28
|
+
|
29
|
+
def structure_dump(filename)
|
30
|
+
isql :extract, output: filename
|
31
|
+
end
|
32
|
+
|
33
|
+
def structure_load(filename)
|
34
|
+
isql input: filename
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def fb_database
|
40
|
+
::Fb::Database.new(configuration)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Executes isql commands to load/dump the schema.
|
44
|
+
# The generated command might look like this:
|
45
|
+
# isql db/development.fdb -user SYSDBA -password masterkey -extract
|
46
|
+
def isql(*args)
|
47
|
+
opts = args.extract_options!
|
48
|
+
user, pass = configuration.values_at(:username, :password)
|
49
|
+
user ||= configuration[:user]
|
50
|
+
opts.reverse_merge!(user: user, password: pass)
|
51
|
+
cmd = [isql_executable, configuration[:database]]
|
52
|
+
cmd += opts.map { |name, val| "-#{name} #{val}" }
|
53
|
+
cmd += args.map { |flag| "-#{flag}" }
|
54
|
+
cmd = cmd.join(' ')
|
55
|
+
raise "Error running: #{cmd}" unless Kernel.system(cmd)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Finds the isql command line utility from the PATH
|
59
|
+
# Many linux distros call this program isql-fb, instead of isql
|
60
|
+
def isql_executable
|
61
|
+
require 'mkmf'
|
62
|
+
exe = ['isql-fb', 'isql'].detect { |c| find_executable0(c) }
|
63
|
+
exe || abort("Unable to find isql or isql-fb in your $PATH")
|
64
|
+
end
|
65
|
+
|
66
|
+
def configuration
|
67
|
+
@configuration
|
68
|
+
end
|
69
|
+
|
70
|
+
def root
|
71
|
+
@root
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'active_record/connection_adapters/fb_adapter'
|
2
|
+
|
3
|
+
module ActiveRecordFbAdapter
|
4
|
+
|
5
|
+
if defined?(::Rails::Railtie) && ::ActiveRecord::VERSION::MAJOR > 3
|
6
|
+
class Railtie < ::Rails::Railtie
|
7
|
+
rake_tasks do
|
8
|
+
load 'active_record/tasks/fb_database_tasks.rb'
|
9
|
+
ActiveRecord::Tasks::DatabaseTasks.register_task /fb/, ActiveRecord::Tasks::FbDatabaseTasks
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Arel
|
2
|
+
module Visitors
|
3
|
+
class Fb < Arel::Visitors::ToSql
|
4
|
+
private
|
5
|
+
|
6
|
+
def visit_Arel_Nodes_SelectStatement o, collector
|
7
|
+
collector << "SELECT "
|
8
|
+
collector = visit o.offset, collector if o.offset && !o.limit
|
9
|
+
|
10
|
+
collector = o.cores.inject(collector) { |c,x|
|
11
|
+
visit_Arel_Nodes_SelectCore(x, c)
|
12
|
+
}
|
13
|
+
|
14
|
+
unless o.orders.empty?
|
15
|
+
collector << ORDER_BY
|
16
|
+
len = o.orders.length - 1
|
17
|
+
o.orders.each_with_index { |x, i|
|
18
|
+
collector = visit(x, collector)
|
19
|
+
collector << COMMA unless len == i
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
if o.limit && o.offset
|
24
|
+
collector = limit_with_rows o, collector
|
25
|
+
elsif o.limit && !o.offset
|
26
|
+
collector = visit o.limit, collector
|
27
|
+
end
|
28
|
+
|
29
|
+
collector = maybe_visit o.lock, collector
|
30
|
+
collector
|
31
|
+
end
|
32
|
+
|
33
|
+
def visit_Arel_Nodes_SelectCore o, collector
|
34
|
+
if o.set_quantifier
|
35
|
+
collector = visit o.set_quantifier, collector
|
36
|
+
collector << SPACE
|
37
|
+
end
|
38
|
+
|
39
|
+
unless o.projections.empty?
|
40
|
+
len = o.projections.length - 1
|
41
|
+
o.projections.each_with_index do |x, i|
|
42
|
+
collector = visit(x, collector)
|
43
|
+
collector << COMMA unless len == i
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if o.source && !o.source.empty?
|
48
|
+
collector << " FROM "
|
49
|
+
collector = visit o.source, collector
|
50
|
+
end
|
51
|
+
|
52
|
+
unless o.wheres.empty?
|
53
|
+
collector << WHERE
|
54
|
+
len = o.wheres.length - 1
|
55
|
+
o.wheres.each_with_index do |x, i|
|
56
|
+
collector = visit(x, collector)
|
57
|
+
collector << AND unless len == i
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
unless o.groups.empty?
|
62
|
+
collector << GROUP_BY
|
63
|
+
len = o.groups.length - 1
|
64
|
+
o.groups.each_with_index do |x, i|
|
65
|
+
collector = visit(x, collector)
|
66
|
+
collector << COMMA unless len == i
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
collector = maybe_visit o.having, collector
|
71
|
+
collector
|
72
|
+
end
|
73
|
+
|
74
|
+
def visit_Arel_Nodes_Limit o, collector
|
75
|
+
collector << " ROWS "
|
76
|
+
visit o.expr, collector
|
77
|
+
end
|
78
|
+
|
79
|
+
def visit_Arel_Nodes_Offset o, collector
|
80
|
+
collector << " SKIP "
|
81
|
+
visit o.expr, collector
|
82
|
+
collector << SPACE
|
83
|
+
end
|
84
|
+
|
85
|
+
# Firebird helper
|
86
|
+
def limit_with_rows o, collector
|
87
|
+
collector << " ROWS "
|
88
|
+
visit o.offset.expr + 1, collector
|
89
|
+
collector << " TO "
|
90
|
+
visit o.offset.expr + o.limit.expr.expr, collector
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
Arel::Visitors::VISITORS['fb'] = Arel::Visitors::Fb
|
metadata
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-fb-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Brent Rowland
|
@@ -13,31 +14,99 @@ dependencies:
|
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: fb
|
15
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
16
18
|
requirements:
|
17
|
-
- -
|
19
|
+
- - ! '>='
|
18
20
|
- !ruby/object:Gem::Version
|
19
21
|
version: 0.7.4
|
20
22
|
type: :runtime
|
21
23
|
prerelease: false
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
23
26
|
requirements:
|
24
|
-
- -
|
27
|
+
- - ! '>='
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: 0.7.4
|
27
30
|
- !ruby/object:Gem::Dependency
|
28
31
|
name: activerecord
|
29
32
|
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
30
34
|
requirements:
|
31
|
-
- -
|
35
|
+
- - ! '>='
|
32
36
|
- !ruby/object:Gem::Version
|
33
37
|
version: 3.2.0
|
34
38
|
type: :runtime
|
35
39
|
prerelease: false
|
36
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
37
42
|
requirements:
|
38
|
-
- -
|
43
|
+
- - ! '>='
|
39
44
|
- !ruby/object:Gem::Version
|
40
45
|
version: 3.2.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mocha
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: pry-byebug
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: minitest-spec-rails
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: minitest-reporters
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
41
110
|
description:
|
42
111
|
email: rowland@rowlandresearch.com
|
43
112
|
executables: []
|
@@ -53,30 +122,34 @@ files:
|
|
53
122
|
- lib/active_record/connection_adapters/fb_adapter.rb
|
54
123
|
- lib/active_record/connection_adapters/fb_column.rb
|
55
124
|
- lib/active_record/fb_base.rb
|
125
|
+
- lib/active_record/tasks/fb_database_tasks.rb
|
126
|
+
- lib/activerecord-fb-adapter.rb
|
56
127
|
- lib/arel/visitors/fb.rb
|
128
|
+
- lib/arel/visitors/fb_collector.rb
|
57
129
|
homepage: http://github.com/rowland/activerecord-fb-adapter
|
58
130
|
licenses:
|
59
131
|
- MIT
|
60
|
-
metadata: {}
|
61
132
|
post_install_message:
|
62
133
|
rdoc_options: []
|
63
134
|
require_paths:
|
64
135
|
- lib
|
65
136
|
required_ruby_version: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
66
138
|
requirements:
|
67
|
-
- -
|
139
|
+
- - ! '>='
|
68
140
|
- !ruby/object:Gem::Version
|
69
141
|
version: '0'
|
70
142
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
71
144
|
requirements:
|
72
|
-
- -
|
145
|
+
- - ! '>='
|
73
146
|
- !ruby/object:Gem::Version
|
74
147
|
version: '0'
|
75
148
|
requirements:
|
76
149
|
- Firebird library fb
|
77
150
|
rubyforge_project:
|
78
|
-
rubygems_version:
|
151
|
+
rubygems_version: 1.8.23
|
79
152
|
signing_key:
|
80
|
-
specification_version:
|
153
|
+
specification_version: 3
|
81
154
|
summary: ActiveRecord Firebird Adapter for Rails 3 and 4 with support for migrations.
|
82
155
|
test_files: []
|
checksums.yaml
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz: fd91257c22642692e91cfd7a15de3bc0684d3148
|
4
|
-
data.tar.gz: 28371fa7615afc66fcbf322ea08b77810390fe05
|
5
|
-
SHA512:
|
6
|
-
metadata.gz: 76431db22cf5caf52185ab189ab48295dcbe62e8bba1863ecb21c7213c5ab228d78c87b19a10d3f872741e2247ab40ef3130404db3bb0b8c93acdcdc9083a2ca
|
7
|
-
data.tar.gz: 732da63410df05ae6416c6f38d7c4e3ff322a0b5a49a66cd61930db5a4ec2835508c26c081477c48b962e96daf2bd76e423d82524aade7d3f404ee012a65250f
|