activerecord-jdbc-alt-adapter 50.3.0-java
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 +7 -0
- data/.gitignore +35 -0
- data/.travis.yml +100 -0
- data/.yardopts +4 -0
- data/CONTRIBUTING.md +50 -0
- data/Gemfile +92 -0
- data/History.md +1191 -0
- data/LICENSE.txt +26 -0
- data/README.md +240 -0
- data/RUNNING_TESTS.md +127 -0
- data/Rakefile +336 -0
- data/Rakefile.jdbc +20 -0
- data/activerecord-jdbc-adapter.gemspec +55 -0
- data/activerecord-jdbc-alt-adapter.gemspec +56 -0
- data/lib/active_record/connection_adapters/as400_adapter.rb +2 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/derby_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/firebird_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/h2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/hsqldb_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/informix_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/jdbc_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/jndi_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mariadb_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mssql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +1 -0
- data/lib/activerecord-jdbc-adapter.rb +1 -0
- data/lib/arel/visitors/compat.rb +60 -0
- data/lib/arel/visitors/db2.rb +137 -0
- data/lib/arel/visitors/derby.rb +112 -0
- data/lib/arel/visitors/firebird.rb +79 -0
- data/lib/arel/visitors/h2.rb +25 -0
- data/lib/arel/visitors/hsqldb.rb +32 -0
- data/lib/arel/visitors/postgresql_jdbc.rb +6 -0
- data/lib/arel/visitors/sql_server.rb +225 -0
- data/lib/arel/visitors/sql_server/ng42.rb +294 -0
- data/lib/arel/visitors/sqlserver.rb +214 -0
- data/lib/arjdbc.rb +19 -0
- data/lib/arjdbc/abstract/connection_management.rb +35 -0
- data/lib/arjdbc/abstract/core.rb +74 -0
- data/lib/arjdbc/abstract/database_statements.rb +64 -0
- data/lib/arjdbc/abstract/statement_cache.rb +58 -0
- data/lib/arjdbc/abstract/transaction_support.rb +86 -0
- data/lib/arjdbc/db2.rb +4 -0
- data/lib/arjdbc/db2/adapter.rb +789 -0
- data/lib/arjdbc/db2/as400.rb +130 -0
- data/lib/arjdbc/db2/column.rb +167 -0
- data/lib/arjdbc/db2/connection_methods.rb +44 -0
- data/lib/arjdbc/derby.rb +3 -0
- data/lib/arjdbc/derby/active_record_patch.rb +13 -0
- data/lib/arjdbc/derby/adapter.rb +540 -0
- data/lib/arjdbc/derby/connection_methods.rb +20 -0
- data/lib/arjdbc/derby/schema_creation.rb +15 -0
- data/lib/arjdbc/discover.rb +104 -0
- data/lib/arjdbc/firebird.rb +4 -0
- data/lib/arjdbc/firebird/adapter.rb +434 -0
- data/lib/arjdbc/firebird/connection_methods.rb +23 -0
- data/lib/arjdbc/h2.rb +3 -0
- data/lib/arjdbc/h2/adapter.rb +303 -0
- data/lib/arjdbc/h2/connection_methods.rb +27 -0
- data/lib/arjdbc/hsqldb.rb +3 -0
- data/lib/arjdbc/hsqldb/adapter.rb +297 -0
- data/lib/arjdbc/hsqldb/connection_methods.rb +28 -0
- data/lib/arjdbc/hsqldb/explain_support.rb +35 -0
- data/lib/arjdbc/hsqldb/schema_creation.rb +11 -0
- data/lib/arjdbc/informix.rb +5 -0
- data/lib/arjdbc/informix/adapter.rb +162 -0
- data/lib/arjdbc/informix/connection_methods.rb +9 -0
- data/lib/arjdbc/jdbc.rb +59 -0
- data/lib/arjdbc/jdbc/adapter.rb +475 -0
- data/lib/arjdbc/jdbc/adapter_require.rb +46 -0
- data/lib/arjdbc/jdbc/base_ext.rb +15 -0
- data/lib/arjdbc/jdbc/callbacks.rb +53 -0
- data/lib/arjdbc/jdbc/column.rb +97 -0
- data/lib/arjdbc/jdbc/connection.rb +14 -0
- data/lib/arjdbc/jdbc/connection_methods.rb +37 -0
- data/lib/arjdbc/jdbc/error.rb +65 -0
- data/lib/arjdbc/jdbc/extension.rb +59 -0
- data/lib/arjdbc/jdbc/java.rb +13 -0
- data/lib/arjdbc/jdbc/railtie.rb +2 -0
- data/lib/arjdbc/jdbc/rake_tasks.rb +3 -0
- data/lib/arjdbc/jdbc/serialized_attributes_helper.rb +3 -0
- data/lib/arjdbc/jdbc/type_cast.rb +166 -0
- data/lib/arjdbc/jdbc/type_converter.rb +142 -0
- data/lib/arjdbc/mssql.rb +7 -0
- data/lib/arjdbc/mssql/adapter.rb +384 -0
- data/lib/arjdbc/mssql/column.rb +29 -0
- data/lib/arjdbc/mssql/connection_methods.rb +79 -0
- data/lib/arjdbc/mssql/database_statements.rb +134 -0
- data/lib/arjdbc/mssql/errors.rb +6 -0
- data/lib/arjdbc/mssql/explain_support.rb +129 -0
- data/lib/arjdbc/mssql/extensions.rb +36 -0
- data/lib/arjdbc/mssql/limit_helpers.rb +231 -0
- data/lib/arjdbc/mssql/lock_methods.rb +77 -0
- data/lib/arjdbc/mssql/old_adapter.rb +804 -0
- data/lib/arjdbc/mssql/old_column.rb +200 -0
- data/lib/arjdbc/mssql/quoting.rb +101 -0
- data/lib/arjdbc/mssql/schema_creation.rb +31 -0
- data/lib/arjdbc/mssql/schema_definitions.rb +74 -0
- data/lib/arjdbc/mssql/schema_statements.rb +329 -0
- data/lib/arjdbc/mssql/transaction.rb +69 -0
- data/lib/arjdbc/mssql/types.rb +52 -0
- data/lib/arjdbc/mssql/types/binary_types.rb +33 -0
- data/lib/arjdbc/mssql/types/date_and_time_types.rb +134 -0
- data/lib/arjdbc/mssql/types/deprecated_types.rb +40 -0
- data/lib/arjdbc/mssql/types/numeric_types.rb +71 -0
- data/lib/arjdbc/mssql/types/string_types.rb +56 -0
- data/lib/arjdbc/mssql/utils.rb +66 -0
- data/lib/arjdbc/mysql.rb +3 -0
- data/lib/arjdbc/mysql/adapter.rb +140 -0
- data/lib/arjdbc/mysql/connection_methods.rb +166 -0
- data/lib/arjdbc/oracle/adapter.rb +863 -0
- data/lib/arjdbc/postgresql.rb +3 -0
- data/lib/arjdbc/postgresql/adapter.rb +687 -0
- data/lib/arjdbc/postgresql/base/array_decoder.rb +26 -0
- data/lib/arjdbc/postgresql/base/array_encoder.rb +25 -0
- data/lib/arjdbc/postgresql/base/array_parser.rb +95 -0
- data/lib/arjdbc/postgresql/base/pgconn.rb +11 -0
- data/lib/arjdbc/postgresql/column.rb +51 -0
- data/lib/arjdbc/postgresql/connection_methods.rb +67 -0
- data/lib/arjdbc/postgresql/name.rb +24 -0
- data/lib/arjdbc/postgresql/oid_types.rb +266 -0
- data/lib/arjdbc/railtie.rb +11 -0
- data/lib/arjdbc/sqlite3.rb +3 -0
- data/lib/arjdbc/sqlite3/adapter.rb +678 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +59 -0
- data/lib/arjdbc/sybase.rb +2 -0
- data/lib/arjdbc/sybase/adapter.rb +47 -0
- data/lib/arjdbc/tasks.rb +13 -0
- data/lib/arjdbc/tasks/database_tasks.rb +31 -0
- data/lib/arjdbc/tasks/databases.rake +48 -0
- data/lib/arjdbc/tasks/db2_database_tasks.rb +104 -0
- data/lib/arjdbc/tasks/derby_database_tasks.rb +95 -0
- data/lib/arjdbc/tasks/h2_database_tasks.rb +31 -0
- data/lib/arjdbc/tasks/hsqldb_database_tasks.rb +70 -0
- data/lib/arjdbc/tasks/jdbc_database_tasks.rb +169 -0
- data/lib/arjdbc/tasks/mssql_database_tasks.rb +46 -0
- data/lib/arjdbc/util/quoted_cache.rb +60 -0
- data/lib/arjdbc/util/serialized_attributes.rb +98 -0
- data/lib/arjdbc/util/table_copier.rb +110 -0
- data/lib/arjdbc/version.rb +3 -0
- data/lib/generators/jdbc/USAGE +9 -0
- data/lib/generators/jdbc/jdbc_generator.rb +17 -0
- data/lib/jdbc_adapter.rb +2 -0
- data/lib/jdbc_adapter/rake_tasks.rb +4 -0
- data/lib/jdbc_adapter/version.rb +4 -0
- data/pom.xml +114 -0
- data/rails_generators/jdbc_generator.rb +15 -0
- data/rails_generators/templates/config/initializers/jdbc.rb +10 -0
- data/rails_generators/templates/lib/tasks/jdbc.rake +11 -0
- data/rakelib/01-tomcat.rake +51 -0
- data/rakelib/02-test.rake +132 -0
- data/rakelib/bundler_ext.rb +11 -0
- data/rakelib/db.rake +75 -0
- data/rakelib/rails.rake +223 -0
- data/src/java/arjdbc/ArJdbcModule.java +276 -0
- data/src/java/arjdbc/db2/DB2Module.java +76 -0
- data/src/java/arjdbc/db2/DB2RubyJdbcConnection.java +126 -0
- data/src/java/arjdbc/derby/DerbyModule.java +178 -0
- data/src/java/arjdbc/derby/DerbyRubyJdbcConnection.java +152 -0
- data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +174 -0
- data/src/java/arjdbc/h2/H2Module.java +50 -0
- data/src/java/arjdbc/h2/H2RubyJdbcConnection.java +85 -0
- data/src/java/arjdbc/hsqldb/HSQLDBModule.java +73 -0
- data/src/java/arjdbc/informix/InformixRubyJdbcConnection.java +75 -0
- data/src/java/arjdbc/jdbc/AdapterJavaService.java +43 -0
- data/src/java/arjdbc/jdbc/Callable.java +44 -0
- data/src/java/arjdbc/jdbc/ConnectionFactory.java +45 -0
- data/src/java/arjdbc/jdbc/DataSourceConnectionFactory.java +156 -0
- data/src/java/arjdbc/jdbc/DriverConnectionFactory.java +63 -0
- data/src/java/arjdbc/jdbc/DriverWrapper.java +119 -0
- data/src/java/arjdbc/jdbc/JdbcResult.java +130 -0
- data/src/java/arjdbc/jdbc/RubyConnectionFactory.java +61 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +3979 -0
- data/src/java/arjdbc/mssql/MSSQLModule.java +90 -0
- data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +508 -0
- data/src/java/arjdbc/mysql/MySQLModule.java +152 -0
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +294 -0
- data/src/java/arjdbc/oracle/OracleModule.java +80 -0
- data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +455 -0
- data/src/java/arjdbc/postgresql/ByteaUtils.java +157 -0
- data/src/java/arjdbc/postgresql/PgDateTimeUtils.java +52 -0
- data/src/java/arjdbc/postgresql/PostgreSQLModule.java +77 -0
- data/src/java/arjdbc/postgresql/PostgreSQLResult.java +192 -0
- data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +948 -0
- data/src/java/arjdbc/sqlite3/SQLite3Module.java +73 -0
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +525 -0
- data/src/java/arjdbc/util/CallResultSet.java +826 -0
- data/src/java/arjdbc/util/DateTimeUtils.java +699 -0
- data/src/java/arjdbc/util/ObjectSupport.java +65 -0
- data/src/java/arjdbc/util/QuotingUtils.java +137 -0
- data/src/java/arjdbc/util/StringCache.java +63 -0
- data/src/java/arjdbc/util/StringHelper.java +145 -0
- metadata +269 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
ArJdbc.load_java_part :SQLite3
|
|
2
|
+
|
|
3
|
+
require "arjdbc/abstract/core"
|
|
4
|
+
require "arjdbc/abstract/database_statements"
|
|
5
|
+
require 'arjdbc/abstract/statement_cache'
|
|
6
|
+
require "arjdbc/abstract/transaction_support"
|
|
7
|
+
require "active_record/connection_adapters/statement_pool"
|
|
8
|
+
require "active_record/connection_adapters/abstract/database_statements"
|
|
9
|
+
require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
|
|
10
|
+
require "active_record/connection_adapters/sqlite3/quoting"
|
|
11
|
+
require "active_record/connection_adapters/sqlite3/schema_creation"
|
|
12
|
+
|
|
13
|
+
module ArJdbc
|
|
14
|
+
# All the code in this module is a copy of ConnectionAdapters::SQLite3Adapter from active_record 5.
|
|
15
|
+
# The constants at the front of this file are to allow the rest of the file to remain with no modifications
|
|
16
|
+
# from its original source. If you hack on this file try not to modify this module and instead try and
|
|
17
|
+
# put those overrides in SQL3Adapter below. We try and keep a copy of the Rails this adapter supports
|
|
18
|
+
# with the current goal of being able to diff changes easily over time and to also eventually remove
|
|
19
|
+
# this module from ARJDBC altogether.
|
|
20
|
+
module SQLite3
|
|
21
|
+
# DIFFERENCE: Some common constant names to reduce differences in rest of this module from AR5 version
|
|
22
|
+
ConnectionAdapters = ::ActiveRecord::ConnectionAdapters
|
|
23
|
+
IndexDefinition = ::ActiveRecord::ConnectionAdapters::IndexDefinition
|
|
24
|
+
Quoting = ::ActiveRecord::ConnectionAdapters::SQLite3::Quoting
|
|
25
|
+
RecordNotUnique = ::ActiveRecord::RecordNotUnique
|
|
26
|
+
SchemaCreation = ConnectionAdapters::SQLite3::SchemaCreation
|
|
27
|
+
SQLite3Adapter = ConnectionAdapters::AbstractAdapter
|
|
28
|
+
|
|
29
|
+
ADAPTER_NAME = 'SQLite'.freeze
|
|
30
|
+
|
|
31
|
+
include Quoting
|
|
32
|
+
|
|
33
|
+
NATIVE_DATABASE_TYPES = {
|
|
34
|
+
primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL",
|
|
35
|
+
string: { name: "varchar" },
|
|
36
|
+
text: { name: "text" },
|
|
37
|
+
integer: { name: "integer" },
|
|
38
|
+
float: { name: "float" },
|
|
39
|
+
decimal: { name: "decimal" },
|
|
40
|
+
datetime: { name: "datetime" },
|
|
41
|
+
time: { name: "time" },
|
|
42
|
+
date: { name: "date" },
|
|
43
|
+
binary: { name: "blob" },
|
|
44
|
+
boolean: { name: "boolean" }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class StatementPool < ConnectionAdapters::StatementPool
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def dealloc(stmt)
|
|
51
|
+
stmt[:stmt].close unless stmt[:stmt].closed?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def schema_creation # :nodoc:
|
|
56
|
+
SQLite3::SchemaCreation.new self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def arel_visitor # :nodoc:
|
|
60
|
+
Arel::Visitors::SQLite.new(self)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def initialize(connection, logger, connection_options, config)
|
|
64
|
+
super(connection, logger, config)
|
|
65
|
+
|
|
66
|
+
@active = nil
|
|
67
|
+
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def supports_ddl_transactions?
|
|
71
|
+
true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def supports_savepoints?
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def supports_partial_index?
|
|
79
|
+
sqlite_version >= "3.8.0"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns true, since this connection adapter supports prepared statement
|
|
83
|
+
# caching.
|
|
84
|
+
def supports_statement_cache?
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Returns true, since this connection adapter supports migrations.
|
|
89
|
+
def supports_migrations? #:nodoc:
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def supports_primary_key? #:nodoc:
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def requires_reloading?
|
|
98
|
+
true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def supports_views?
|
|
102
|
+
true
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def supports_datetime_with_precision?
|
|
106
|
+
true
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def supports_multi_insert?
|
|
110
|
+
sqlite_version >= "3.7.11"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def active?
|
|
114
|
+
@active != false
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Disconnects from the database if already connected. Otherwise, this
|
|
118
|
+
# method does nothing.
|
|
119
|
+
def disconnect!
|
|
120
|
+
super
|
|
121
|
+
@active = false
|
|
122
|
+
@connection.close rescue nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Clears the prepared statements cache.
|
|
126
|
+
def clear_cache!
|
|
127
|
+
@statements.clear
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def supports_index_sort_order?
|
|
131
|
+
true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def valid_type?(type)
|
|
135
|
+
true
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Returns 62. SQLite supports index names up to 64
|
|
139
|
+
# characters. The rest is used by Rails internally to perform
|
|
140
|
+
# temporary rename operations
|
|
141
|
+
def allowed_index_name_length
|
|
142
|
+
index_name_length - 2
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def native_database_types #:nodoc:
|
|
146
|
+
NATIVE_DATABASE_TYPES
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Returns the current database encoding format as a string, eg: 'UTF-8'
|
|
150
|
+
def encoding
|
|
151
|
+
@connection.encoding.to_s
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def supports_explain?
|
|
155
|
+
true
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
#--
|
|
159
|
+
# DATABASE STATEMENTS ======================================
|
|
160
|
+
#++
|
|
161
|
+
|
|
162
|
+
def explain(arel, binds = [])
|
|
163
|
+
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
|
|
164
|
+
::ActiveRecord::ConnectionAdapters::SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def exec_query(sql, name = nil, binds = [], prepare: false)
|
|
168
|
+
type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
|
|
169
|
+
|
|
170
|
+
log(sql, name, binds) do
|
|
171
|
+
# Don't cache statements if they are not prepared
|
|
172
|
+
unless prepare
|
|
173
|
+
stmt = @connection.prepare(sql)
|
|
174
|
+
begin
|
|
175
|
+
cols = stmt.columns
|
|
176
|
+
unless without_prepared_statement?(binds)
|
|
177
|
+
stmt.bind_params(type_casted_binds)
|
|
178
|
+
end
|
|
179
|
+
records = stmt.to_a
|
|
180
|
+
ensure
|
|
181
|
+
stmt.close
|
|
182
|
+
end
|
|
183
|
+
stmt = records
|
|
184
|
+
else
|
|
185
|
+
cache = @statements[sql] ||= {
|
|
186
|
+
:stmt => @connection.prepare(sql)
|
|
187
|
+
}
|
|
188
|
+
stmt = cache[:stmt]
|
|
189
|
+
cols = cache[:cols] ||= stmt.columns
|
|
190
|
+
stmt.reset!
|
|
191
|
+
stmt.bind_params(type_casted_binds)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
ActiveRecord::Result.new(cols, stmt.to_a)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def exec_delete(sql, name = 'SQL', binds = [])
|
|
199
|
+
exec_query(sql, name, binds)
|
|
200
|
+
@connection.changes
|
|
201
|
+
end
|
|
202
|
+
alias :exec_update :exec_delete
|
|
203
|
+
|
|
204
|
+
def last_inserted_id(result)
|
|
205
|
+
@connection.last_insert_row_id
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def execute(sql, name = nil) #:nodoc:
|
|
209
|
+
log(sql, name) { @connection.execute(sql) }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def begin_db_transaction #:nodoc:
|
|
213
|
+
log("begin transaction",nil) { @connection.transaction }
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def commit_db_transaction #:nodoc:
|
|
217
|
+
log("commit transaction",nil) { @connection.commit }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def exec_rollback_db_transaction #:nodoc:
|
|
221
|
+
log("rollback transaction",nil) { @connection.rollback }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# SCHEMA STATEMENTS ========================================
|
|
225
|
+
|
|
226
|
+
def tables(name = nil) # :nodoc:
|
|
227
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
228
|
+
#tables currently returns both tables and views.
|
|
229
|
+
This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
|
|
230
|
+
Use #data_sources instead.
|
|
231
|
+
MSG
|
|
232
|
+
|
|
233
|
+
if name
|
|
234
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
235
|
+
Passing arguments to #tables is deprecated without replacement.
|
|
236
|
+
MSG
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
data_sources
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def data_sources
|
|
243
|
+
select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", "SCHEMA")
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def table_exists?(table_name)
|
|
247
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
248
|
+
#table_exists? currently checks both tables and views.
|
|
249
|
+
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
|
|
250
|
+
Use #data_source_exists? instead.
|
|
251
|
+
MSG
|
|
252
|
+
|
|
253
|
+
data_source_exists?(table_name)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def data_source_exists?(table_name)
|
|
257
|
+
return false unless table_name.present?
|
|
258
|
+
|
|
259
|
+
sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
|
|
260
|
+
sql << " AND name = #{quote(table_name)}"
|
|
261
|
+
|
|
262
|
+
select_values(sql, "SCHEMA").any?
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def views # :nodoc:
|
|
266
|
+
select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA")
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def view_exists?(view_name) # :nodoc:
|
|
270
|
+
return false unless view_name.present?
|
|
271
|
+
|
|
272
|
+
sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'"
|
|
273
|
+
sql << " AND name = #{quote(view_name)}"
|
|
274
|
+
|
|
275
|
+
select_values(sql, "SCHEMA").any?
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
|
279
|
+
def columns(table_name) # :nodoc:
|
|
280
|
+
table_name = table_name.to_s
|
|
281
|
+
table_structure(table_name).map do |field|
|
|
282
|
+
case field["dflt_value"]
|
|
283
|
+
when /^null$/i
|
|
284
|
+
field["dflt_value"] = nil
|
|
285
|
+
when /^'(.*)'$/m
|
|
286
|
+
field["dflt_value"] = $1.gsub("''", "'")
|
|
287
|
+
when /^"(.*)"$/m
|
|
288
|
+
field["dflt_value"] = $1.gsub('""', '"')
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
collation = field["collation"]
|
|
292
|
+
sql_type = field["type"]
|
|
293
|
+
type_metadata = fetch_type_metadata(sql_type)
|
|
294
|
+
new_column(field["name"], field["dflt_value"], type_metadata, field["notnull"].to_i == 0, table_name, nil, collation)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Returns an array of indexes for the given table.
|
|
299
|
+
def indexes(table_name, name = nil) #:nodoc:
|
|
300
|
+
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
|
|
301
|
+
sql = <<-SQL
|
|
302
|
+
SELECT sql
|
|
303
|
+
FROM sqlite_master
|
|
304
|
+
WHERE name=#{quote(row['name'])} AND type='index'
|
|
305
|
+
UNION ALL
|
|
306
|
+
SELECT sql
|
|
307
|
+
FROM sqlite_temp_master
|
|
308
|
+
WHERE name=#{quote(row['name'])} AND type='index'
|
|
309
|
+
SQL
|
|
310
|
+
index_sql = exec_query(sql).first["sql"]
|
|
311
|
+
match = /\sWHERE\s+(.+)$/i.match(index_sql)
|
|
312
|
+
where = match[1] if match
|
|
313
|
+
IndexDefinition.new(
|
|
314
|
+
table_name,
|
|
315
|
+
row["name"],
|
|
316
|
+
row["unique"] != 0,
|
|
317
|
+
exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
|
|
318
|
+
col["name"]
|
|
319
|
+
}, nil, nil, where)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def primary_keys(table_name) # :nodoc:
|
|
324
|
+
pks = table_structure(table_name).select { |f| f["pk"] > 0 }
|
|
325
|
+
pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
|
329
|
+
index_name = index_name_for_remove(table_name, options)
|
|
330
|
+
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Renames a table.
|
|
334
|
+
#
|
|
335
|
+
# Example:
|
|
336
|
+
# rename_table('octopuses', 'octopi')
|
|
337
|
+
def rename_table(table_name, new_name)
|
|
338
|
+
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
|
339
|
+
rename_table_indexes(table_name, new_name)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# See: http://www.sqlite.org/lang_altertable.html
|
|
343
|
+
# SQLite has an additional restriction on the ALTER TABLE statement
|
|
344
|
+
def valid_alter_table_type?(type)
|
|
345
|
+
type.to_sym != :primary_key
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
|
349
|
+
if valid_alter_table_type?(type)
|
|
350
|
+
super(table_name, column_name, type, options)
|
|
351
|
+
else
|
|
352
|
+
alter_table(table_name) do |definition|
|
|
353
|
+
definition.column(column_name, type, options)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
|
|
359
|
+
alter_table(table_name) do |definition|
|
|
360
|
+
definition.remove_column column_name
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
|
|
365
|
+
default = extract_new_default_value(default_or_changes)
|
|
366
|
+
|
|
367
|
+
alter_table(table_name) do |definition|
|
|
368
|
+
definition[column_name].default = default
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
|
373
|
+
unless null || default.nil?
|
|
374
|
+
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
|
375
|
+
end
|
|
376
|
+
alter_table(table_name) do |definition|
|
|
377
|
+
definition[column_name].null = null
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
|
382
|
+
alter_table(table_name) do |definition|
|
|
383
|
+
include_default = options_include_default?(options)
|
|
384
|
+
definition[column_name].instance_eval do
|
|
385
|
+
self.type = type
|
|
386
|
+
self.limit = options[:limit] if options.include?(:limit)
|
|
387
|
+
self.default = options[:default] if include_default
|
|
388
|
+
self.null = options[:null] if options.include?(:null)
|
|
389
|
+
self.precision = options[:precision] if options.include?(:precision)
|
|
390
|
+
self.scale = options[:scale] if options.include?(:scale)
|
|
391
|
+
self.collation = options[:collation] if options.include?(:collation)
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
|
397
|
+
column = column_for(table_name, column_name)
|
|
398
|
+
alter_table(table_name, rename: { column.name => new_column_name.to_s })
|
|
399
|
+
rename_column_indexes(table_name, column.name, new_column_name)
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
protected
|
|
403
|
+
|
|
404
|
+
def table_structure(table_name)
|
|
405
|
+
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
|
406
|
+
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
|
407
|
+
table_structure_with_collation(table_name, structure)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def alter_table(table_name, options = {}) #:nodoc:
|
|
411
|
+
altered_table_name = "a#{table_name}"
|
|
412
|
+
caller = lambda { |definition| yield definition if block_given? }
|
|
413
|
+
|
|
414
|
+
transaction do
|
|
415
|
+
move_table(table_name, altered_table_name,
|
|
416
|
+
options.merge(temporary: true))
|
|
417
|
+
move_table(altered_table_name, table_name, &caller)
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def move_table(from, to, options = {}, &block) #:nodoc:
|
|
422
|
+
copy_table(from, to, options, &block)
|
|
423
|
+
drop_table(from)
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def copy_table(from, to, options = {}) #:nodoc:
|
|
427
|
+
from_primary_key = primary_key(from)
|
|
428
|
+
options[:id] = false
|
|
429
|
+
create_table(to, options) do |definition|
|
|
430
|
+
@definition = definition
|
|
431
|
+
@definition.primary_key(from_primary_key) if from_primary_key.present?
|
|
432
|
+
columns(from).each do |column|
|
|
433
|
+
column_name = options[:rename] ?
|
|
434
|
+
(options[:rename][column.name] ||
|
|
435
|
+
options[:rename][column.name.to_sym] ||
|
|
436
|
+
column.name) : column.name
|
|
437
|
+
next if column_name == from_primary_key
|
|
438
|
+
|
|
439
|
+
@definition.column(column_name, column.type,
|
|
440
|
+
limit: column.limit, default: column.default,
|
|
441
|
+
precision: column.precision, scale: column.scale,
|
|
442
|
+
null: column.null, collation: column.collation)
|
|
443
|
+
end
|
|
444
|
+
yield @definition if block_given?
|
|
445
|
+
end
|
|
446
|
+
copy_table_indexes(from, to, options[:rename] || {})
|
|
447
|
+
copy_table_contents(from, to,
|
|
448
|
+
@definition.columns.map(&:name),
|
|
449
|
+
options[:rename] || {})
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def copy_table_indexes(from, to, rename = {}) #:nodoc:
|
|
453
|
+
indexes(from).each do |index|
|
|
454
|
+
name = index.name
|
|
455
|
+
if to == "a#{from}"
|
|
456
|
+
name = "t#{name}"
|
|
457
|
+
elsif from == "a#{to}"
|
|
458
|
+
name = name[1..-1]
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
to_column_names = columns(to).map(&:name)
|
|
462
|
+
columns = index.columns.map { |c| rename[c] || c }.select do |column|
|
|
463
|
+
to_column_names.include?(column)
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
unless columns.empty?
|
|
467
|
+
# index name can't be the same
|
|
468
|
+
opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
|
|
469
|
+
opts[:unique] = true if index.unique
|
|
470
|
+
add_index(to, columns, opts)
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
|
|
476
|
+
column_mappings = Hash[columns.map { |name| [name, name] }]
|
|
477
|
+
rename.each { |a| column_mappings[a.last] = a.first }
|
|
478
|
+
from_columns = columns(from).collect(&:name)
|
|
479
|
+
columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) }
|
|
480
|
+
from_columns_to_copy = columns.map { |col| column_mappings[col] }
|
|
481
|
+
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
|
|
482
|
+
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
|
|
483
|
+
|
|
484
|
+
exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
|
|
485
|
+
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
def sqlite_version
|
|
489
|
+
@sqlite_version ||= SQLite3Adapter::Version.new(select_value("select sqlite_version(*)"))
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def translate_exception(exception, message)
|
|
493
|
+
case exception.message
|
|
494
|
+
# SQLite 3.8.2 returns a newly formatted error message:
|
|
495
|
+
# UNIQUE constraint failed: *table_name*.*column_name*
|
|
496
|
+
# Older versions of SQLite return:
|
|
497
|
+
# column *column_name* is not unique
|
|
498
|
+
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
|
|
499
|
+
RecordNotUnique.new(message)
|
|
500
|
+
else
|
|
501
|
+
super
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
private
|
|
506
|
+
COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
|
|
507
|
+
|
|
508
|
+
def table_structure_with_collation(table_name, basic_structure)
|
|
509
|
+
collation_hash = {}
|
|
510
|
+
sql = "SELECT sql FROM
|
|
511
|
+
(SELECT * FROM sqlite_master UNION ALL
|
|
512
|
+
SELECT * FROM sqlite_temp_master)
|
|
513
|
+
WHERE type='table' and name='#{ table_name }' \;"
|
|
514
|
+
|
|
515
|
+
# Result will have following sample string
|
|
516
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
517
|
+
# "password_digest" varchar COLLATE "NOCASE");
|
|
518
|
+
result = exec_query(sql, 'SCHEMA').first
|
|
519
|
+
|
|
520
|
+
if result
|
|
521
|
+
# Splitting with left parentheses and picking up last will return all
|
|
522
|
+
# columns separated with comma(,).
|
|
523
|
+
columns_string = result["sql"].split('(').last
|
|
524
|
+
|
|
525
|
+
columns_string.split(',').each do |column_string|
|
|
526
|
+
# This regex will match the column name and collation type and will save
|
|
527
|
+
# the value in $1 and $2 respectively.
|
|
528
|
+
collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string)
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
basic_structure.map! do |column|
|
|
532
|
+
column_name = column['name']
|
|
533
|
+
|
|
534
|
+
if collation_hash.has_key? column_name
|
|
535
|
+
column['collation'] = collation_hash[column_name]
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
column
|
|
539
|
+
end
|
|
540
|
+
else
|
|
541
|
+
basic_structure.to_hash
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
module ActiveRecord::ConnectionAdapters
|
|
548
|
+
class SQLite3Column < JdbcColumn
|
|
549
|
+
def initialize(name, *args)
|
|
550
|
+
if Hash === name
|
|
551
|
+
super
|
|
552
|
+
else
|
|
553
|
+
super(nil, name, *args)
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
def self.string_to_binary(value)
|
|
558
|
+
value
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def self.binary_to_string(value)
|
|
562
|
+
if value.respond_to?(:encoding) && value.encoding != Encoding::ASCII_8BIT
|
|
563
|
+
value = value.force_encoding(Encoding::ASCII_8BIT)
|
|
564
|
+
end
|
|
565
|
+
value
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# @override {ActiveRecord::ConnectionAdapters::JdbcColumn#init_column}
|
|
569
|
+
def init_column(name, default, *args)
|
|
570
|
+
if default =~ /NULL/
|
|
571
|
+
@default = nil
|
|
572
|
+
else
|
|
573
|
+
super
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# @override {ActiveRecord::ConnectionAdapters::JdbcColumn#default_value}
|
|
578
|
+
def default_value(value)
|
|
579
|
+
# JDBC returns column default strings with actual single quotes :
|
|
580
|
+
return $1 if value =~ /^'(.*)'$/
|
|
581
|
+
|
|
582
|
+
value
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
# @override {ActiveRecord::ConnectionAdapters::Column#type_cast}
|
|
586
|
+
def type_cast(value)
|
|
587
|
+
return nil if value.nil?
|
|
588
|
+
case type
|
|
589
|
+
when :string then value
|
|
590
|
+
when :primary_key
|
|
591
|
+
value.respond_to?(:to_i) ? value.to_i : ( value ? 1 : 0 )
|
|
592
|
+
when :float then value.to_f
|
|
593
|
+
when :decimal then self.class.value_to_decimal(value)
|
|
594
|
+
when :boolean then self.class.value_to_boolean(value)
|
|
595
|
+
else super
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
private
|
|
600
|
+
|
|
601
|
+
# @override {ActiveRecord::ConnectionAdapters::Column#simplified_type}
|
|
602
|
+
def simplified_type(field_type)
|
|
603
|
+
case field_type
|
|
604
|
+
when /boolean/i then :boolean
|
|
605
|
+
when /text/i then :text
|
|
606
|
+
when /varchar/i then :string
|
|
607
|
+
when /int/i then :integer
|
|
608
|
+
when /float/i then :float
|
|
609
|
+
when /real|decimal/i then
|
|
610
|
+
extract_scale(field_type) == 0 ? :integer : :decimal
|
|
611
|
+
when /datetime/i then :datetime
|
|
612
|
+
when /date/i then :date
|
|
613
|
+
when /time/i then :time
|
|
614
|
+
when /blob/i then :binary
|
|
615
|
+
else super
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# @override {ActiveRecord::ConnectionAdapters::Column#extract_limit}
|
|
620
|
+
def extract_limit(sql_type)
|
|
621
|
+
return nil if sql_type =~ /^(real)\(\d+/i
|
|
622
|
+
super
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
def extract_precision(sql_type)
|
|
626
|
+
case sql_type
|
|
627
|
+
when /^(real)\((\d+)(,\d+)?\)/i then $2.to_i
|
|
628
|
+
else super
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
def extract_scale(sql_type)
|
|
633
|
+
case sql_type
|
|
634
|
+
when /^(real)\((\d+)\)/i then 0
|
|
635
|
+
when /^(real)\((\d+)(,(\d+))\)/i then $4.to_i
|
|
636
|
+
else super
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
remove_const(:SQLite3Adapter) if const_defined?(:SQLite3Adapter)
|
|
642
|
+
|
|
643
|
+
# Currently our adapter is named the same as what AR5 names its adapter. We will need to get
|
|
644
|
+
# this changed at some point so this can be a unique name and we can extend activerecord
|
|
645
|
+
# ActiveRecord::ConnectionAdapters::SQLite3Adapter. Once we can do that we can remove the
|
|
646
|
+
# module SQLite3 above and remove a majority of this file.
|
|
647
|
+
class SQLite3Adapter < AbstractAdapter
|
|
648
|
+
include ArJdbc::Abstract::Core
|
|
649
|
+
include ArJdbc::SQLite3
|
|
650
|
+
include ArJdbc::Abstract::DatabaseStatements
|
|
651
|
+
include ArJdbc::Abstract::StatementCache
|
|
652
|
+
include ArJdbc::Abstract::TransactionSupport
|
|
653
|
+
|
|
654
|
+
def begin_isolated_db_transaction(isolation)
|
|
655
|
+
raise ActiveRecord::TransactionIsolationError, 'adapter does not support setting transaction isolation'
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# SQLite driver doesn't support all types of insert statements with executeUpdate so
|
|
659
|
+
# make it act like a regular query and the ids will be returned from #last_inserted_id
|
|
660
|
+
# example: INSERT INTO "aircraft" DEFAULT VALUES
|
|
661
|
+
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
|
|
662
|
+
exec_query(sql, name, binds)
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
def jdbc_column_class
|
|
666
|
+
::ActiveRecord::ConnectionAdapters::SQLite3Column
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
def jdbc_connection_class(spec)
|
|
670
|
+
self.class.jdbc_connection_class
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# @see ActiveRecord::ConnectionAdapters::JdbcAdapter#jdbc_connection_class
|
|
674
|
+
def self.jdbc_connection_class
|
|
675
|
+
::ActiveRecord::ConnectionAdapters::SQLite3JdbcConnection
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
end
|