skiima 0.1.000 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.gitignore +9 -0
- data/.travis.yml +11 -3
- data/Gemfile +12 -6
- data/Guardfile +13 -11
- data/LICENSE +20 -0
- data/Procfile.example +2 -0
- data/README.md +170 -23
- data/Rakefile +26 -22
- data/lib/skiima.rb +61 -240
- data/lib/skiima/config.rb +60 -0
- data/lib/skiima/config/struct.rb +87 -0
- data/lib/skiima/db/connector.rb +195 -0
- data/lib/skiima/db/connector/active_record.rb +11 -0
- data/lib/skiima/db/connector/active_record/base_connector.rb +34 -0
- data/lib/skiima/db/connector/active_record/mysql2_connector.rb +147 -0
- data/lib/skiima/db/connector/active_record/mysql_connector.rb +177 -0
- data/lib/skiima/db/connector/active_record/postgresql_connector.rb +39 -0
- data/lib/skiima/db/helpers/mysql.rb +230 -0
- data/lib/skiima/db/helpers/postgresql.rb +221 -0
- data/lib/skiima/db/resolver.rb +62 -0
- data/lib/skiima/dependency/reader.rb +55 -0
- data/lib/skiima/dependency/script.rb +63 -0
- data/lib/skiima/i18n.rb +24 -0
- data/lib/skiima/loader.rb +108 -0
- data/lib/skiima/locales/en.yml +2 -2
- data/lib/skiima/logger.rb +54 -0
- data/lib/skiima/railtie.rb +10 -0
- data/lib/skiima/railties/skiima.rake +31 -0
- data/lib/skiima/version.rb +2 -2
- data/skiima.gemspec +5 -5
- data/spec/config/{database.yml → database.yml.example} +16 -0
- data/spec/config/database.yml.travis +69 -0
- data/spec/db/skiima/{depends.yml → dependencies.yml} +7 -2
- data/spec/db/skiima/{empty_depends.yml → empty_dependencies.yml} +0 -0
- data/spec/db/skiima/init_test_db/database.skiima_test.mysql.current.sql +7 -0
- data/spec/db/skiima/init_test_db/database.skiima_test.postgresql.current.sql +7 -0
- data/spec/mysql2_spec.rb +61 -12
- data/spec/mysql_spec.rb +66 -27
- data/spec/postgresql_spec.rb +55 -34
- data/spec/shared_examples/config_shared_example.rb +40 -0
- data/spec/skiima/config/struct_spec.rb +78 -0
- data/spec/skiima/config_spec.rb +6 -0
- data/spec/skiima/db/connector/active_record/base_connector_spec.rb +0 -0
- data/spec/skiima/db/connector/active_record/mysql2_connector_spec.rb +3 -0
- data/spec/skiima/db/connector/active_record/mysql_connector_spec.rb +3 -0
- data/spec/skiima/db/connector/active_record/postgresql_connector_spec.rb +7 -0
- data/spec/skiima/db/connector_spec.rb +6 -0
- data/spec/skiima/db/resolver_spec.rb +54 -0
- data/spec/skiima/dependency/reader_spec.rb +52 -0
- data/spec/skiima/{dependency_spec.rb → dependency/script_spec.rb} +3 -41
- data/spec/skiima/i18n_spec.rb +29 -0
- data/spec/skiima/loader_spec.rb +102 -0
- data/spec/skiima/logger_spec.rb +0 -0
- data/spec/skiima_spec.rb +43 -64
- data/spec/spec_helper.rb +38 -4
- metadata +144 -100
- data/lib/skiima/db_adapters.rb +0 -187
- data/lib/skiima/db_adapters/base_mysql_adapter.rb +0 -308
- data/lib/skiima/db_adapters/mysql2_adapter.rb +0 -114
- data/lib/skiima/db_adapters/mysql_adapter.rb +0 -287
- data/lib/skiima/db_adapters/postgresql_adapter.rb +0 -509
- data/lib/skiima/dependency.rb +0 -84
- data/lib/skiima_helpers.rb +0 -49
- data/spec/skiima/db_adapters/mysql_adapter_spec.rb +0 -38
- data/spec/skiima/db_adapters/postgresql_adapter_spec.rb +0 -20
- data/spec/skiima/db_adapters_spec.rb +0 -31
data/lib/skiima/db_adapters.rb
DELETED
@@ -1,187 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Skiima
|
3
|
-
module DbAdapters
|
4
|
-
class Base
|
5
|
-
attr_accessor :version
|
6
|
-
|
7
|
-
def initialize(connection, logger = nil) #:nodoc:
|
8
|
-
super()
|
9
|
-
|
10
|
-
@active = nil
|
11
|
-
@connection = connection
|
12
|
-
@in_use = false
|
13
|
-
@last_use = false
|
14
|
-
@logger = logger
|
15
|
-
@visitor = nil
|
16
|
-
end
|
17
|
-
|
18
|
-
def adapter_name
|
19
|
-
'Base'
|
20
|
-
end
|
21
|
-
|
22
|
-
def supports_ddl_transactions?
|
23
|
-
false
|
24
|
-
end
|
25
|
-
|
26
|
-
def supported_objects
|
27
|
-
[] # this should be overridden by concrete adapters
|
28
|
-
end
|
29
|
-
|
30
|
-
def drop(type, name, opts = {})
|
31
|
-
send("drop_#{type}", name, opts) if supported_objects.include? type.to_sym
|
32
|
-
end
|
33
|
-
|
34
|
-
def object_exists?(type, name, opts = {})
|
35
|
-
send("#{type}_exists?", name, opts) if supported_objects.include? type.to_sym
|
36
|
-
end
|
37
|
-
|
38
|
-
# Does this adapter support savepoints? PostgreSQL and MySQL do,
|
39
|
-
# SQLite < 3.6.8 does not.
|
40
|
-
def supports_savepoints?
|
41
|
-
false
|
42
|
-
end
|
43
|
-
|
44
|
-
def active?
|
45
|
-
@active != false
|
46
|
-
end
|
47
|
-
|
48
|
-
# Disconnects from the database if already connected, and establishes a
|
49
|
-
# new connection with the database.
|
50
|
-
def reconnect!
|
51
|
-
@active = true
|
52
|
-
end
|
53
|
-
|
54
|
-
# Disconnects from the database if already connected. Otherwise, this
|
55
|
-
# method does nothing.
|
56
|
-
def disconnect!
|
57
|
-
@active = false
|
58
|
-
end
|
59
|
-
|
60
|
-
# Reset the state of this connection, directing the DBMS to clear
|
61
|
-
# transactions and other connection-related server-side state. Usually a
|
62
|
-
# database-dependent operation.
|
63
|
-
#
|
64
|
-
# The default implementation does nothing; the implementation should be
|
65
|
-
# overridden by concrete adapters.
|
66
|
-
def reset!
|
67
|
-
# this should be overridden by concrete adapters
|
68
|
-
end
|
69
|
-
|
70
|
-
###
|
71
|
-
# Clear any caching the database adapter may be doing, for example
|
72
|
-
# clearing the prepared statement cache. This is database specific.
|
73
|
-
def clear_cache!
|
74
|
-
# this should be overridden by concrete adapters
|
75
|
-
end
|
76
|
-
|
77
|
-
# Returns true if its required to reload the connection between requests for development mode.
|
78
|
-
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
|
79
|
-
def requires_reloading?
|
80
|
-
false
|
81
|
-
end
|
82
|
-
|
83
|
-
# Checks whether the connection to the database is still active (i.e. not stale).
|
84
|
-
# This is done under the hood by calling <tt>active?</tt>. If the connection
|
85
|
-
# is no longer active, then this method will reconnect to the database.
|
86
|
-
def verify!(*ignored)
|
87
|
-
reconnect! unless active?
|
88
|
-
end
|
89
|
-
|
90
|
-
# Provides access to the underlying database driver for this adapter. For
|
91
|
-
# example, this method returns a Mysql object in case of MysqlAdapter,
|
92
|
-
# and a PGconn object in case of PostgreSQLAdapter.
|
93
|
-
#
|
94
|
-
# This is useful for when you need to call a proprietary method such as
|
95
|
-
# PostgreSQL's lo_* methods.
|
96
|
-
def raw_connection
|
97
|
-
@connection
|
98
|
-
end
|
99
|
-
|
100
|
-
attr_reader :open_transactions
|
101
|
-
|
102
|
-
def increment_open_transactions
|
103
|
-
@open_transactions += 1
|
104
|
-
end
|
105
|
-
|
106
|
-
def decrement_open_transactions
|
107
|
-
@open_transactions -= 1
|
108
|
-
end
|
109
|
-
|
110
|
-
def transaction_joinable=(joinable)
|
111
|
-
@transaction_joinable = joinable
|
112
|
-
end
|
113
|
-
|
114
|
-
def create_savepoint
|
115
|
-
end
|
116
|
-
|
117
|
-
def rollback_to_savepoint
|
118
|
-
end
|
119
|
-
|
120
|
-
def release_savepoint
|
121
|
-
end
|
122
|
-
|
123
|
-
def current_savepoint_name
|
124
|
-
"active_record_#{open_transactions}"
|
125
|
-
end
|
126
|
-
|
127
|
-
# Check the connection back in to the connection pool
|
128
|
-
def close
|
129
|
-
disconnect!
|
130
|
-
end
|
131
|
-
|
132
|
-
# Disconnects from the database if already connected. Otherwise, this
|
133
|
-
# method does nothing.
|
134
|
-
def disconnect!
|
135
|
-
clear_cache!
|
136
|
-
@connection.close rescue nil
|
137
|
-
end
|
138
|
-
|
139
|
-
protected
|
140
|
-
|
141
|
-
def log(sql, name = "SQL", binds = [])
|
142
|
-
@logger.debug("Executing SQL Statement: #{name}")
|
143
|
-
@logger.debug(sql)
|
144
|
-
result = yield
|
145
|
-
@logger.debug("SUCCESS!")
|
146
|
-
result
|
147
|
-
rescue Exception => e
|
148
|
-
message = "#{e.class.name}: #{e.message}: #{sql}"
|
149
|
-
@logger.debug message if @logger
|
150
|
-
exception = translate_exception(e, message)
|
151
|
-
exception.set_backtrace e.backtrace
|
152
|
-
raise exception
|
153
|
-
end
|
154
|
-
|
155
|
-
def translate_exception(e, message)
|
156
|
-
# override in derived class
|
157
|
-
raise "override in derived class"
|
158
|
-
end
|
159
|
-
|
160
|
-
end
|
161
|
-
|
162
|
-
class Resolver
|
163
|
-
attr_accessor :db, :adapter_method
|
164
|
-
|
165
|
-
def initialize(db_config)
|
166
|
-
@db = Skiima.symbolize_keys(db_config)
|
167
|
-
adapter_specified?
|
168
|
-
load_adapter
|
169
|
-
@adapter_method = "#{db[:adapter]}_connection"
|
170
|
-
end
|
171
|
-
|
172
|
-
private
|
173
|
-
|
174
|
-
def adapter_specified?
|
175
|
-
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless db.key?(:adapter)
|
176
|
-
end
|
177
|
-
|
178
|
-
def load_adapter
|
179
|
-
begin
|
180
|
-
require "skiima/db_adapters/#{db[:adapter]}_adapter"
|
181
|
-
rescue => e
|
182
|
-
raise LoadError, "Adapter does not exist: #{db[:adapter]} - (#{e.message})", e.backtrace
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
@@ -1,308 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
module Skiima
|
3
|
-
module DbAdapters
|
4
|
-
class BaseMysqlAdapter < Base
|
5
|
-
attr_accessor :version
|
6
|
-
|
7
|
-
LOST_CONNECTION_ERROR_MESSAGES = [
|
8
|
-
"Server shutdown in progress",
|
9
|
-
"Broken pipe",
|
10
|
-
"Lost connection to MySQL server during query",
|
11
|
-
"MySQL server has gone away" ]
|
12
|
-
|
13
|
-
# FIXME: Make the first parameter more similar for the two adapters
|
14
|
-
def initialize(connection, logger, connection_options, config)
|
15
|
-
super(connection, logger)
|
16
|
-
@connection_options, @config = connection_options, config
|
17
|
-
@quoted_column_names, @quoted_table_names = {}, {}
|
18
|
-
end
|
19
|
-
|
20
|
-
def version
|
21
|
-
@version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
|
22
|
-
end
|
23
|
-
|
24
|
-
def adapter_name #:nodoc:
|
25
|
-
self.class::ADAPTER_NAME
|
26
|
-
end
|
27
|
-
|
28
|
-
# Returns true, since this connection adapter supports migrations.
|
29
|
-
def supports_migrations?
|
30
|
-
true
|
31
|
-
end
|
32
|
-
|
33
|
-
def supports_primary_key?
|
34
|
-
true
|
35
|
-
end
|
36
|
-
|
37
|
-
# Returns true, since this connection adapter supports savepoints.
|
38
|
-
def supports_savepoints?
|
39
|
-
true
|
40
|
-
end
|
41
|
-
|
42
|
-
# Must return the Mysql error number from the exception, if the exception has an
|
43
|
-
# error number.
|
44
|
-
def error_number(exception) # :nodoc:
|
45
|
-
raise NotImplementedError
|
46
|
-
end
|
47
|
-
|
48
|
-
def disable_referential_integrity(&block) #:nodoc:
|
49
|
-
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
|
50
|
-
|
51
|
-
begin
|
52
|
-
update("SET FOREIGN_KEY_CHECKS = 0")
|
53
|
-
yield
|
54
|
-
ensure
|
55
|
-
update("SET FOREIGN_KEY_CHECKS = #{old}")
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# MysqlAdapter has to free a result after using it, so we use this method to write
|
60
|
-
# stuff in a abstract way without concerning ourselves about whether it needs to be
|
61
|
-
# explicitly freed or not.
|
62
|
-
def execute_and_free(sql, name = nil) #:nodoc:
|
63
|
-
yield execute(sql, name)
|
64
|
-
end
|
65
|
-
|
66
|
-
# Executes the SQL statement in the context of this connection.
|
67
|
-
def execute(sql, name = nil)
|
68
|
-
if name == :skip_logging
|
69
|
-
@connection.query(sql)
|
70
|
-
else
|
71
|
-
log(sql, name) { @connection.query(sql) }
|
72
|
-
end
|
73
|
-
rescue StatementInvalid => exception
|
74
|
-
if exception.message.split(":").first =~ /Packets out of order/
|
75
|
-
raise StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
|
76
|
-
else
|
77
|
-
raise
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def begin_db_transaction
|
82
|
-
execute "BEGIN"
|
83
|
-
rescue Exception
|
84
|
-
# Transactions aren't supported
|
85
|
-
end
|
86
|
-
|
87
|
-
def commit_db_transaction #:nodoc:
|
88
|
-
execute "COMMIT"
|
89
|
-
rescue Exception
|
90
|
-
# Transactions aren't supported
|
91
|
-
end
|
92
|
-
|
93
|
-
def rollback_db_transaction #:nodoc:
|
94
|
-
execute "ROLLBACK"
|
95
|
-
rescue Exception
|
96
|
-
# Transactions aren't supported
|
97
|
-
end
|
98
|
-
|
99
|
-
def create_savepoint
|
100
|
-
execute("SAVEPOINT #{current_savepoint_name}")
|
101
|
-
end
|
102
|
-
|
103
|
-
def rollback_to_savepoint
|
104
|
-
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
105
|
-
end
|
106
|
-
|
107
|
-
def release_savepoint
|
108
|
-
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
109
|
-
end
|
110
|
-
|
111
|
-
def supported_objects
|
112
|
-
[:database, :table, :view, :index]
|
113
|
-
end
|
114
|
-
|
115
|
-
def tables(name = nil, database = nil, like = nil)
|
116
|
-
sql = "SHOW FULL TABLES "
|
117
|
-
sql << "IN #{database} " if database
|
118
|
-
sql << "WHERE table_type = 'BASE TABLE' "
|
119
|
-
sql << "LIKE '#{like}' " if like
|
120
|
-
|
121
|
-
execute_and_free(sql, 'SCHEMA') do |result|
|
122
|
-
result.collect { |field| field.first }
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def views(name = nil, database = nil, like = nil)
|
127
|
-
sql = "SHOW FULL TABLES "
|
128
|
-
sql << "IN #{database} " if database
|
129
|
-
sql << "WHERE table_type = 'VIEW' "
|
130
|
-
sql << "LIKE '#{like}' " if like
|
131
|
-
|
132
|
-
execute_and_free(sql, 'SCHEMA') do |result|
|
133
|
-
result.collect { |field| field.first }
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
def indexes(name = nil, database = nil, table = nil)
|
138
|
-
sql = "SHOW INDEX "
|
139
|
-
sql << "IN #{table} "
|
140
|
-
sql << "IN #{database} " if database
|
141
|
-
sql << "WHERE key_name = '#{name}'" if name
|
142
|
-
|
143
|
-
execute_and_free(sql, 'SCHEMA') do |result|
|
144
|
-
result.collect { |field| field[2] }
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
"select routine_schema, routine_name, routine_type from routines;"
|
149
|
-
|
150
|
-
def procs(name = nil, database = nil, like = nil)
|
151
|
-
sql = "SELECT r.routine_name "
|
152
|
-
sql << "FROM information_schema.routines r "
|
153
|
-
sql << "WHERE r.routine_type = 'PROCEDURE' "
|
154
|
-
sql << "AND r.routine_name LIKE '#{like}' " if like
|
155
|
-
sql << "AND r.routine_schema = #{database} " if database
|
156
|
-
|
157
|
-
execute_and_free(sql, 'SCHEMA') do |result|
|
158
|
-
result.collect { |field| field.first }
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def database_exists?(name)
|
163
|
-
#stub
|
164
|
-
end
|
165
|
-
|
166
|
-
def table_exists?(name)
|
167
|
-
return false unless name
|
168
|
-
return true if tables(nil, nil, name).any?
|
169
|
-
|
170
|
-
name = name.to_s
|
171
|
-
schema, table = name.split('.', 2)
|
172
|
-
|
173
|
-
unless table # A table was provided without a schema
|
174
|
-
table = schema
|
175
|
-
schema = nil
|
176
|
-
end
|
177
|
-
|
178
|
-
tables(nil, schema, table).any?
|
179
|
-
end
|
180
|
-
|
181
|
-
def view_exists?(name)
|
182
|
-
return false unless name
|
183
|
-
return true if views(nil, nil, name).any?
|
184
|
-
|
185
|
-
name = name.to_s
|
186
|
-
schema, view = name.split('.', 2)
|
187
|
-
|
188
|
-
unless view # A table was provided without a schema
|
189
|
-
view = schema
|
190
|
-
schema = nil
|
191
|
-
end
|
192
|
-
|
193
|
-
views(nil, schema, view).any?
|
194
|
-
end
|
195
|
-
|
196
|
-
def index_exists?(name, opts = {})
|
197
|
-
target = opts[:attr] ? opts[:attr][0] : nil
|
198
|
-
raise "requires target object" unless target
|
199
|
-
|
200
|
-
return false unless table_exists?(target) #mysql blows up when table doesn't exist
|
201
|
-
return false unless name
|
202
|
-
return true if indexes(name, nil, target).any?
|
203
|
-
|
204
|
-
name = name.to_s
|
205
|
-
schema, target = name.split('.', 2)
|
206
|
-
|
207
|
-
unless target # A table was provided without a schema
|
208
|
-
target = schema
|
209
|
-
schema = nil
|
210
|
-
end
|
211
|
-
|
212
|
-
indexes(name, schema, target).any?
|
213
|
-
end
|
214
|
-
|
215
|
-
def proc_exists?(name, opts = {})
|
216
|
-
return false unless name
|
217
|
-
return true if procs(nil, nil, name).any?
|
218
|
-
|
219
|
-
name = name.to_s
|
220
|
-
schema, proc = name.split('.', 2)
|
221
|
-
|
222
|
-
unless proc # A table was provided without a schema
|
223
|
-
proc = schema
|
224
|
-
schema = nil
|
225
|
-
end
|
226
|
-
|
227
|
-
procs(name, schema, proc).any?
|
228
|
-
end
|
229
|
-
|
230
|
-
def drop_database(name, opts = {})
|
231
|
-
"DROP DATABASE IF EXISTS #{name}"
|
232
|
-
end
|
233
|
-
|
234
|
-
def drop_table(name, opts = {})
|
235
|
-
"DROP TABLE IF EXISTS #{name}"
|
236
|
-
end
|
237
|
-
|
238
|
-
def drop_view(name, opts = {})
|
239
|
-
"DROP VIEW IF EXISTS #{name}"
|
240
|
-
end
|
241
|
-
|
242
|
-
def drop_index(name, opts = {})
|
243
|
-
target = opts[:attr].first if opts[:attr]
|
244
|
-
raise "requires target object" unless target
|
245
|
-
|
246
|
-
"DROP INDEX #{name} ON #{target}"
|
247
|
-
end
|
248
|
-
|
249
|
-
def column_definitions(table_name)
|
250
|
-
# "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
251
|
-
end
|
252
|
-
|
253
|
-
def column_names(table_name)
|
254
|
-
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
255
|
-
execute_and_free(sql, 'SCHEMA') do |result|
|
256
|
-
result.collect { |field| field.first }
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
def quote_column_name(name) #:nodoc:
|
261
|
-
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
|
262
|
-
end
|
263
|
-
|
264
|
-
def quote_table_name(name) #:nodoc:
|
265
|
-
@quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
|
266
|
-
end
|
267
|
-
|
268
|
-
def current_database
|
269
|
-
select_value 'SELECT DATABASE() as db'
|
270
|
-
end
|
271
|
-
|
272
|
-
# Returns the database character set.
|
273
|
-
def charset
|
274
|
-
show_variable 'character_set_database'
|
275
|
-
end
|
276
|
-
|
277
|
-
# Returns the database collation strategy.
|
278
|
-
def collation
|
279
|
-
show_variable 'collation_database'
|
280
|
-
end
|
281
|
-
|
282
|
-
def show_variable(name)
|
283
|
-
# variables = select_all("SHOW VARIABLES LIKE '#{name}'")
|
284
|
-
# variables.first['Value'] unless variables.empty?
|
285
|
-
end
|
286
|
-
|
287
|
-
protected
|
288
|
-
|
289
|
-
def translate_exception(exception, message)
|
290
|
-
exception
|
291
|
-
# case error_number(exception)
|
292
|
-
# when 1062
|
293
|
-
# RecordNotUnique.new(message, exception)
|
294
|
-
# when 1452
|
295
|
-
# InvalidForeignKey.new(message, exception)
|
296
|
-
# else
|
297
|
-
# super
|
298
|
-
# end
|
299
|
-
end
|
300
|
-
|
301
|
-
private
|
302
|
-
|
303
|
-
def supports_views?
|
304
|
-
version[0] >= 5
|
305
|
-
end
|
306
|
-
end
|
307
|
-
end
|
308
|
-
end
|