activerecord-fb-adapter 0.8.9 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2678bd6b3b5635e1bdae1638babacb97d42af498
4
+ data.tar.gz: ee672fe8d0e86cc4ae2e86b633492cc4d7db888e
5
+ SHA512:
6
+ metadata.gz: fc9b1a2711bfca2a744eb83066f3b79ca96f4ddfd64f302c656cd77ec023fa1484b848d028a2f4c90daa6474fa3058ebc13dcdecb4a70a73d0815e38c7a64e1c
7
+ data.tar.gz: d720204fe3cca4a0689089c7841f329776e4bd693b9ca536cf70e1042fc04ec4a294b745a615d71b062e52335cbb8d482ae1f3bd651db62c28f9c1edc453cdb4
@@ -0,0 +1,82 @@
1
+ # ActiveRecord Firebird Adapter
2
+ [![Gem Version](https://badge.fury.io/rb/activerecord-fb-adapter.svg)](http://badge.fury.io/rb/activerecord-fb-adapter)
3
+
4
+ <img src="/project_logo.png" align="left" hspace="10">
5
+ 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).
6
+
7
+ ## What's supported
8
+
9
+ - Datatypes: string, integer, datetime, boolean, float, decimal, text (blob).
10
+ - Rails migrations and db/schema.rb generation.
11
+ - Linux, OS X, and Windows supported.
12
+
13
+ ## Getting started
14
+
15
+ 1) Install and start the Firebird Server in your machine (varies across operating systems).
16
+
17
+ 2) Create a new Rails project.
18
+
19
+ ```
20
+ rails new firebird_test
21
+ cd firebird_test
22
+ ```
23
+
24
+ 3) Edit the project Gemfile and add the **activerecord-fb-adapter** gem:
25
+
26
+ ```ruby
27
+ gem 'activerecord-fb-adapter'
28
+ ```
29
+
30
+ Then run:
31
+
32
+ ```
33
+ bundle update
34
+ ```
35
+
36
+ which will make bundler to get the gem with it's only dependency: the Fb gem 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
+ 4) Edit the **database.yml** for configuring your database connection:
39
+
40
+ ```ruby
41
+ development:
42
+ adapter: fb
43
+ database: db/development.fdb
44
+ username: SYSDBA
45
+ password: masterkey
46
+ host: localhost
47
+ encoding: UTF-8
48
+ create: true
49
+ ```
50
+
51
+ The default Firebird administrator username and password are **SYSDBA** and **masterkey**, you may have to adjust this to your installation.
52
+
53
+ Currently the adapter does not supports the "rake db:create" task, so in order to create the database you must add the "create: true" option; with this switch the first time the adapter tries to connect to the database it will be created if it doesn't exists.
54
+
55
+ 5) Start the rails server in development mode
56
+
57
+ ```
58
+ bundle exec rails server
59
+ ```
60
+
61
+ Open your browser at http://localhost:3000, this will create the database on the first connection.
62
+
63
+ On Linux you may get:
64
+
65
+ ```
66
+ Fb::Error (Unsuccessful execution caused by a system error that precludes successful execution of subsequent statements
67
+ I/O error during "open O_CREAT" operation for file "db/development.fdb"
68
+ Error while trying to create file
69
+ Permission denied
70
+ ```
71
+
72
+ This is because, by default, the Firebird Server runs under the "firebird" user and group, which has no write access to the "db" folder of the project. To fix it run:
73
+
74
+ ```
75
+ chmod o+w db
76
+ ```
77
+ which will add write permission to "others" group.
78
+
79
+ 6) Now you can start generating scaffolds or models and rails will create the corresponding migrations. Use **bundle exec rake db:migrate** and **bundle exec rake db:rollback** for migrating the database; this will update your **db/schema.rb** file automatically.
80
+
81
+ ## License
82
+ It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
@@ -0,0 +1,42 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Fb
4
+ module DatabaseLimits
5
+ # the maximum length of a table alias
6
+ def table_alias_length
7
+ 31
8
+ end
9
+
10
+ # the maximum length of a column name
11
+ def column_name_length
12
+ 31
13
+ end
14
+
15
+ # the maximum length of a table name
16
+ def table_name_length
17
+ 31
18
+ end
19
+
20
+ # the maximum length of an index name
21
+ def index_name_length
22
+ 31
23
+ end
24
+
25
+ # the maximum number of indexes per table
26
+ def indexes_per_table
27
+ 65_535
28
+ end
29
+
30
+ # the maximum number of elements in an IN (x,y,z) clause
31
+ def in_clause_length
32
+ 1_499
33
+ end
34
+
35
+ # the maximum length of an SQL query
36
+ def sql_query_length
37
+ 32_767
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,127 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Fb
4
+ module DatabaseStatements
5
+ # Returns an array of arrays containing the field values.
6
+ # Order is the same as that returned by +columns+.
7
+ def select_rows(sql, name = nil, binds = [])
8
+ exec_query(sql, name, binds).to_a.map(&:values)
9
+ end
10
+
11
+ # Executes the SQL statement in the context of this connection.
12
+ def execute(sql, name = nil, skip_logging = false)
13
+ translate(sql) do |translated, args|
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
21
+ end
22
+ end
23
+
24
+ # Executes +sql+ statement in the context of this connection using
25
+ # +binds+ as the bind substitutes. +name+ is logged along with
26
+ # the executed +sql+ statement.
27
+ def exec_query(sql, name = 'SQL', binds = [])
28
+ translate(sql, binds) do |translated, args|
29
+ log(expand(translated, args), name) do
30
+ result, rows = @connection.execute(translated, *args) do |cursor|
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)
36
+ end
37
+ end
38
+ end
39
+
40
+ def explain(arel, binds = [])
41
+ to_sql(arel, binds)
42
+ end
43
+
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
+ # Begins the transaction (and turns off auto-committing).
56
+ def begin_db_transaction
57
+ @connection.transaction('READ COMMITTED')
58
+ end
59
+
60
+ # Commits the transaction (and turns on auto-committing).
61
+ def commit_db_transaction
62
+ @connection.commit
63
+ end
64
+
65
+ # Rolls back the transaction (and turns on auto-committing). Must be
66
+ # done if the transaction block raises an exception or returns false.
67
+ 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
92
+ end
93
+
94
+ def default_sequence_name(table_name, _column = nil)
95
+ "#{table_name.to_s.tr('-', '_')[0, table_name_length - 4]}_seq"
96
+ end
97
+
98
+ # Set the sequence to the max value of the table's column.
99
+ def reset_sequence!(table, column, sequence = nil)
100
+ sequence ||= default_sequence_name(table, column)
101
+ max_id = select_value("select max(#{column}) from #{table}")
102
+ execute("alter sequence #{sequence} restart with #{max_id}")
103
+ end
104
+
105
+ # Uses the raw connection to get the next sequence value.
106
+ def next_sequence_value(sequence_name)
107
+ @connection.query("SELECT NEXT VALUE FOR #{sequence_name} FROM RDB$DATABASE")[0][0]
108
+ end
109
+
110
+ protected
111
+
112
+ # Returns an array of record hashes with the column names as keys and
113
+ # column values as values. ActiveRecord >= 4 returns an ActiveRecord::Result.
114
+ def select(sql, name = nil, binds = [])
115
+ result = exec_query(sql, name, binds)
116
+ ActiveRecord::VERSION::MAJOR > 3 ? result : result.to_a
117
+ end
118
+
119
+ # Since the ID is prefetched and passed to #insert, this method is useless.
120
+ # Overriding this method allows us to avoid overriding #insert.
121
+ def last_inserted_id(_result)
122
+ nil
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,103 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Fb
4
+ module Quoting
5
+ def quote(value, column = nil)
6
+ # records are quoted as their primary key
7
+ return value.quoted_id if value.respond_to?(:quoted_id)
8
+ type = column && column.type
9
+
10
+ case value
11
+ when String, ActiveSupport::Multibyte::Chars
12
+ value = value.to_s
13
+ if [:integer, :float].include?(type)
14
+ value = type == :integer ? value.to_i : value.to_f
15
+ value.to_s
16
+ elsif type && type != :binary && value.size < 256 && !value.include?('@')
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)
34
+ else
35
+ quote_object(value)
36
+ end
37
+ end
38
+ end
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}@"
57
+ else
58
+ "@#{Base64.encode64(obj.to_yaml).chop}@"
59
+ end
60
+ end
61
+
62
+ def quote_column_name(column_name) # :nodoc:
63
+ if @connection.dialect == 1
64
+ %Q(#{ar_to_fb_case(column_name.to_s)})
65
+ else
66
+ %Q("#{ar_to_fb_case(column_name.to_s)}")
67
+ end
68
+ end
69
+
70
+ def quote_table_name_for_assignment(_table, attr)
71
+ quote_column_name(attr)
72
+ end if ::ActiveRecord::VERSION::MAJOR >= 4
73
+
74
+ def quoted_true # :nodoc:
75
+ quote(boolean_domain[:true])
76
+ end
77
+
78
+ def quoted_false # :nodoc:
79
+ quote(boolean_domain[:false])
80
+ end
81
+
82
+ def type_cast(value, column)
83
+ return super unless value == true || value == false
84
+ value ? quoted_true : quoted_false
85
+ end
86
+
87
+ private
88
+
89
+ # Maps uppercase Firebird column names to lowercase for ActiveRecord;
90
+ # mixed-case columns retain their original case.
91
+ def fb_to_ar_case(column_name)
92
+ column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase
93
+ end
94
+
95
+ # Maps lowercase ActiveRecord column names to uppercase for Fierbird;
96
+ # mixed-case columns retain their original case.
97
+ def ar_to_fb_case(column_name)
98
+ column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,247 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Fb
4
+ module SchemaStatements
5
+ # Returns a Hash of mappings from the abstract data types to the native
6
+ # database types. See TableDefinition#column for details on the recognized
7
+ # abstract data types.
8
+ def native_database_types
9
+ {
10
+ :primary_key => 'integer not null primary key',
11
+ :string => { :name => 'varchar', :limit => 255 },
12
+ :text => { :name => 'blob sub_type text' },
13
+ :integer => { :name => 'integer' },
14
+ :float => { :name => 'float' },
15
+ :decimal => { :name => 'decimal' },
16
+ :datetime => { :name => 'timestamp' },
17
+ :timestamp => { :name => 'timestamp' },
18
+ :time => { :name => 'time' },
19
+ :date => { :name => 'date' },
20
+ :binary => { :name => 'blob' },
21
+ :boolean => { :name => boolean_domain[:name] }
22
+ }
23
+ end
24
+
25
+ def tables(_name = nil)
26
+ @connection.table_names
27
+ end
28
+
29
+ # Returns an array of indexes for the given table.
30
+ def indexes(table_name, _name = nil)
31
+ @connection.indexes.values.map { |ix|
32
+ if ix.table_name == table_name && ix.index_name !~ /^rdb\$/
33
+ IndexDefinition.new(table_name, ix.index_name, ix.unique, ix.columns)
34
+ end
35
+ }.compact
36
+ end
37
+
38
+ def primary_key(table_name) #:nodoc:
39
+ sql = <<-END_SQL
40
+ SELECT s.rdb$field_name
41
+ FROM rdb$indices i
42
+ JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
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)}' and c.rdb$constraint_type = 'PRIMARY KEY';
45
+ END_SQL
46
+ row = select_one(sql)
47
+ row && fb_to_ar_case(row.values.first.rstrip)
48
+ end
49
+
50
+ # Returns an array of Column objects for the table specified by +table_name+.
51
+ # See the concrete implementation for details on the expected parameter values.
52
+ def columns(table_name, name = nil)
53
+ sql = <<-END_SQL
54
+ SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
55
+ f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
56
+ COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
57
+ COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
58
+ FROM rdb$relation_fields r
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
+ })
67
+ end
68
+ end
69
+
70
+ def create_table(name, options = {}) # :nodoc:
71
+ needs_sequence = options[:id] != false
72
+ while_ensuring_boolean_domain do
73
+ super(name, options) do |table_def|
74
+ yield table_def if block_given?
75
+ needs_sequence ||= table_def.needs_sequence
76
+ end
77
+ end
78
+ return if options[:sequence] == false || !needs_sequence
79
+ create_sequence(options[:sequence] || default_sequence_name(name))
80
+ end
81
+
82
+ # Unfortunately, this is a limitation of Firebird.
83
+ def rename_table(name, new_name)
84
+ fail 'Firebird does not support renaming tables.'
85
+ end
86
+
87
+ def drop_table(name, options = {}) # :nodoc:
88
+ super(name)
89
+ return if 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
+ # Creates a sequence
95
+ # ===== Examples
96
+ # create_sequence('DOGS_SEQ')
97
+ def create_sequence(sequence_name)
98
+ execute("CREATE SEQUENCE #{sequence_name}") rescue nil
99
+ end
100
+
101
+ # Drops a sequence
102
+ # ===== Examples
103
+ # drop_sequence('DOGS_SEQ')
104
+ def drop_sequence(sequence_name)
105
+ execute("DROP SEQUENCE #{sequence_name}") rescue nil
106
+ end
107
+
108
+ # Adds a new column to the named table.
109
+ # See TableDefinition#column for details of the options you can use.
110
+ def add_column(table_name, column_name, type, options = {})
111
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
112
+ add_column_options!(add_column_sql, options)
113
+ while_ensuring_boolean_domain { execute(add_column_sql) }
114
+ if type == :primary_key && options[:sequence] != false
115
+ create_sequence(options[:sequence] || default_sequence_name(table_name))
116
+ end
117
+ return unless options[:position]
118
+ # position is 1-based but add 1 to skip id column
119
+ alter_position_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} POSITION #{options[:position] + 1}"
120
+ execute(alter_position_sql)
121
+ end
122
+
123
+ # Changes the column's definition according to the new options.
124
+ # See TableDefinition#column for details of the options you can use.
125
+ # ===== Examples
126
+ # change_column(:suppliers, :name, :string, :limit => 80)
127
+ # change_column(:accounts, :description, :text)
128
+ def change_column(table_name, column_name, type, options = {})
129
+ sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
130
+ execute(sql)
131
+ 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[:default]
133
+ end
134
+
135
+ # Sets a new default value for a column. If you want to set the default
136
+ # value to +NULL+, you are out of luck. You need to
137
+ # DatabaseStatements#execute the appropriate SQL statement yourself.
138
+ # ===== Examples
139
+ # change_column_default(:suppliers, :qualification, 'new')
140
+ # change_column_default(:accounts, :authorized, 1)
141
+ def change_column_default(table_name, column_name, default)
142
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}")
143
+ end
144
+
145
+ def change_column_null(table_name, column_name, null, default = nil)
146
+ fail 'Firebird cannot change the nullability of a column using ALTER COLUMN.'
147
+ end
148
+
149
+ # Renames a column.
150
+ # ===== Example
151
+ # rename_column(:suppliers, :description, :name)
152
+ def rename_column(table_name, column_name, new_column_name)
153
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
154
+ rename_column_indexes(table_name, column_name, new_column_name)
155
+ end
156
+
157
+ def remove_index!(_table_name, index_name) #:nodoc:
158
+ execute("DROP INDEX #{quote_column_name(index_name)}")
159
+ end
160
+
161
+ def index_name(table_name, options) #:nodoc:
162
+ if options.respond_to?(:keys) # legacy support
163
+ if options[:column]
164
+ "#{table_name}_#{Array.wrap(options[:column]) * '_'}"
165
+ elsif options[:name]
166
+ options[:name]
167
+ else
168
+ fail ArgumentError, "You must specify the index name"
169
+ end
170
+ else
171
+ index_name(table_name, :column => options)
172
+ end
173
+ end
174
+
175
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
176
+ case type
177
+ when :integer then integer_to_sql(limit)
178
+ when :float then float_to_sql(limit)
179
+ else super
180
+ end
181
+ end
182
+
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
+ private
193
+
194
+ if ActiveRecord::VERSION::MAJOR > 3
195
+ def create_table_definition(*args)
196
+ TableDefinition.new(native_database_types, *args)
197
+ end
198
+ else
199
+ def table_definition
200
+ TableDefinition.new(self)
201
+ end
202
+ end
203
+
204
+ # Map logical Rails types to Firebird-specific data types.
205
+ def integer_to_sql(limit)
206
+ return 'integer' if limit.nil?
207
+ case limit
208
+ when 1..2 then 'smallint'
209
+ when 3..4 then 'integer'
210
+ when 5..8 then 'bigint'
211
+ else
212
+ fail ActiveRecordError, "No integer type has byte size #{limit}. Use a NUMERIC with PRECISION 0 instead."
213
+ end
214
+ end
215
+
216
+ def float_to_sql(limit)
217
+ if limit.nil? || limit <= 4
218
+ 'float'
219
+ else
220
+ 'double precision'
221
+ end
222
+ end
223
+
224
+ # Creates a domain for boolean fields as needed
225
+ def while_ensuring_boolean_domain(&block)
226
+ block.call
227
+ rescue ActiveRecord::StatementInvalid => e
228
+ raise unless e.message =~ /Specified domain or source column \w+ does not exist/
229
+ create_boolean_domain
230
+ block.call
231
+ end
232
+
233
+ def create_boolean_domain
234
+ sql = <<-end_sql
235
+ CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]}
236
+ CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL)
237
+ end_sql
238
+ execute(sql)
239
+ end
240
+
241
+ def sequence_exists?(sequence_name)
242
+ @connection.generator_names.include?(sequence_name)
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end