db2_query 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +77 -7
- data/lib/db2_query.rb +2 -2
- data/lib/db2_query/base.rb +1 -2
- data/lib/db2_query/config.rb +1 -5
- data/lib/db2_query/connection.rb +163 -0
- data/lib/db2_query/connection_handling.rb +82 -3
- data/lib/db2_query/core.rb +11 -11
- data/lib/db2_query/database_statements.rb +5 -0
- data/lib/db2_query/odbc_connector.rb +15 -13
- data/lib/db2_query/railtie.rb +2 -2
- data/lib/db2_query/result.rb +7 -1
- data/lib/db2_query/tasks/init.rake +1 -1
- data/lib/db2_query/tasks/initializer.rake +6 -1
- data/lib/db2_query/version.rb +1 -1
- metadata +4 -16
- data/lib/active_record/connection_adapters/db2_query_adapter.rb +0 -203
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a54ab3ca53e8078aee466400a89ec9b18c5c5ed65450d6d35cdf7a8552e0e21c
|
4
|
+
data.tar.gz: 72ad54eb98b13a471ba8e022e57cb686a31cf7a2e1d833a4d54d833f3d900105
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a037c568900f33c3eefdc2692fd0d8d470dc4167ff87f3b1c0fa11902204fb38523b4bc257cf6cac069590a9706f1c94f506fc13fd35aa7388495fc9e28ea84a
|
7
|
+
data.tar.gz: 9f1815d05567d1f972ff1f7d0ff5324a2d11781ae0000b13b552c1c21b4da260de7e7e0d8a633fb48a5ce5636ceea7f90e1fe0a509352a7ff99779788ef0b7d7
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/db2_query.svg)](https://badge.fury.io/rb/db2_query)
|
4
4
|
|
5
|
-
A Rails query plugin to fetch data from Db2 database by using ODBC connection
|
5
|
+
A Rails 6+ query plugin to fetch data from Db2 database by using ODBC connection
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
Add this line to your application's Gemfile:
|
@@ -20,8 +20,6 @@ Or install it yourself as:
|
|
20
20
|
```bash
|
21
21
|
$ gem install db2_query
|
22
22
|
```
|
23
|
-
## Note
|
24
|
-
Start v.0.2.0, this module use `DB2Query` instead of `Db2Query`
|
25
23
|
|
26
24
|
## Initialization
|
27
25
|
Execute init task at the app root
|
@@ -30,10 +28,16 @@ $ rake db2query:init
|
|
30
28
|
```
|
31
29
|
DB2Query will generate two required files:
|
32
30
|
- `config/db2query_database.yml`
|
33
|
-
- `config/initializers/db2query`
|
31
|
+
- `config/initializers/db2query.rb`
|
34
32
|
|
35
33
|
Edit these files according to the requirement.
|
36
34
|
|
35
|
+
Note:
|
36
|
+
|
37
|
+
To upgrade from the previous version to v.0.2.1, please delete all the files generated by init task and do init task again (don't forget to backup your db2query_database.yml).
|
38
|
+
|
39
|
+
In v.0.2.0, we have to `require 'db2_query'` at initializer manually.
|
40
|
+
|
37
41
|
### Database Configuration
|
38
42
|
At `db2query_database.yml` we can use two type of connection:
|
39
43
|
1. DSN connection config
|
@@ -59,7 +63,27 @@ development:
|
|
59
63
|
pwd: <%= ENV["ISERIES_PWD"] %>
|
60
64
|
```
|
61
65
|
|
66
|
+
Ensure that `unixodbc` have been installed and test your connection first by using `isql` commands.
|
67
|
+
|
68
|
+
Example:
|
69
|
+
|
70
|
+
Secondary database connection test
|
71
|
+
```bash
|
72
|
+
$ isql -v iseries
|
73
|
+
+---------------------------------------+
|
74
|
+
| Connected! |
|
75
|
+
| |
|
76
|
+
| sql-statement |
|
77
|
+
| help [tablename] |
|
78
|
+
| quit |
|
79
|
+
| |
|
80
|
+
+---------------------------------------+
|
81
|
+
SQL>
|
82
|
+
```
|
83
|
+
|
62
84
|
## Usage
|
85
|
+
Note: Version 0.1.0 use `Db2Query` namespace. Please use `DB2Query` in versions greater than it.
|
86
|
+
|
63
87
|
### Basic Usage
|
64
88
|
Create query class that inherit from `DB2Query::Base` in `app/queries` folder
|
65
89
|
```ruby
|
@@ -83,7 +107,7 @@ User.find_by 10000
|
|
83
107
|
SQL Load (3.28ms) SELECT * FROM LIBTEST.USERS WHERE id = ? [[nil, 10000]]
|
84
108
|
=> #<DB2Query::Result @records=[#<Record id: 10000, first_name: "Strange", last_name: "Stephen", email: "strange@marvel.universe.com">]>
|
85
109
|
```
|
86
|
-
Or using keywords argument
|
110
|
+
Or using keywords argument if the sql use `=` operator, e.g `first_name = ?`
|
87
111
|
```bash
|
88
112
|
User.by_name first_name: "Strange", last_name: "Stephen"
|
89
113
|
SQL Load (3.28ms) SELECT * FROM LIBTEST.USERS WHERE first_name = ? AND last_name = ? [["first_name", Strange], ["last_name", Stephen]]
|
@@ -116,12 +140,58 @@ end
|
|
116
140
|
Check it at rails console
|
117
141
|
```bash
|
118
142
|
Doctor.find_by id: 10000
|
119
|
-
SQL Load (
|
143
|
+
SQL Load (3.28ms) SELECT * FROM LIBTEST.USERS WHERE id = ? [["id", 10000]]
|
120
144
|
=> #<DB2Query::Result @records=[#<Record id: 10000, first_name: "Dr.Strange", last_name: "Stephen", email: "strange@marvel.universe.com">]>
|
121
145
|
```
|
122
146
|
|
123
147
|
### Available methods
|
124
|
-
`DB2Query::Result` inherit all `ActiveRecord::Result` methods with additional custom
|
148
|
+
`DB2Query::Result` inherit all `ActiveRecord::Result` methods with additional custom methods:
|
149
|
+
1. `records` to convert query result into array of Record objects.
|
150
|
+
2. `to_h` to convert query result into hash with symbolized keys.
|
151
|
+
|
152
|
+
### ActiveRecord Combination
|
153
|
+
|
154
|
+
Create an abstract class that inherit from `ActiveRecord::Base`
|
155
|
+
```ruby
|
156
|
+
class Db2Record < ActiveRecord::Base
|
157
|
+
self.abstract_class = true
|
158
|
+
|
159
|
+
def self.query(sql, formatter = {}, args = [])
|
160
|
+
DB2Query::Base.connection.exec_query(sql, formatter, args).to_a.map(&:deep_symbolize_keys)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
Utilize the goodness of rails model `scope`
|
166
|
+
```ruby
|
167
|
+
class User < Db2Record
|
168
|
+
scope :by_name, -> *args {
|
169
|
+
query(
|
170
|
+
"SELECT * FROM LIBTEST.USERS WHERE first_name = ? AND last_name = ?", {}, args
|
171
|
+
)
|
172
|
+
}
|
173
|
+
end
|
174
|
+
```
|
175
|
+
```bash
|
176
|
+
User.by_name first_name: "Strange", last_name: "Stephen"
|
177
|
+
SQL Load (3.28ms) SELECT * FROM LIBTEST.USERS WHERE first_name = ? AND last_name = ? [["first_name", Strange], ["last_name", Stephen]]
|
178
|
+
=> [{:id=> 10000, :first_name=> "Strange", :last_name=> "Stephen", :email=> "strange@marvel.universe.com"}]
|
179
|
+
```
|
180
|
+
|
181
|
+
Another example:
|
182
|
+
```ruby
|
183
|
+
class User < Db2Record
|
184
|
+
scope :age_gt, -> age {
|
185
|
+
query("SELECT * FROM LIBTEST.USERS WHERE age > #{age}")
|
186
|
+
}
|
187
|
+
end
|
188
|
+
```
|
189
|
+
|
190
|
+
```bash
|
191
|
+
User.age_gt 500
|
192
|
+
SQL Load (3.28ms) SELECT * FROM LIBTEST.USERS WHERE age > 500
|
193
|
+
=> [{:id=> 99999, :first_name=> "Ancient", :last_name=> "One", :email=> "ancientone@marvel.universe.com"}]
|
194
|
+
```
|
125
195
|
|
126
196
|
## License
|
127
197
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/db2_query.rb
CHANGED
@@ -4,8 +4,8 @@ require "yaml"
|
|
4
4
|
require "erb"
|
5
5
|
require "active_record"
|
6
6
|
require "active_support"
|
7
|
-
require "active_record/database_configurations"
|
8
7
|
require "db2_query/config"
|
8
|
+
require "db2_query/connection_handling"
|
9
9
|
|
10
10
|
module DB2Query
|
11
11
|
extend ActiveSupport::Autoload
|
@@ -13,8 +13,8 @@ module DB2Query
|
|
13
13
|
autoload :Version
|
14
14
|
autoload :Base
|
15
15
|
autoload :Core
|
16
|
-
autoload :ConnectionHandling
|
17
16
|
autoload :DatabaseStatements
|
17
|
+
autoload :Connection
|
18
18
|
autoload :ODBCConnector
|
19
19
|
autoload :Formatter
|
20
20
|
autoload :Result
|
data/lib/db2_query/base.rb
CHANGED
data/lib/db2_query/config.rb
CHANGED
@@ -20,11 +20,7 @@ module DB2Query
|
|
20
20
|
|
21
21
|
def read_config
|
22
22
|
erb = ERB.new(config_file.read)
|
23
|
-
YAML.parse(erb.result(binding)).transform
|
24
|
-
end
|
25
|
-
|
26
|
-
def connection_env
|
27
|
-
ENV["RAILS_ENV"].to_sym
|
23
|
+
YAML.parse(erb.result(binding)).transform.transform_keys(&:to_sym)
|
28
24
|
end
|
29
25
|
end
|
30
26
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_String_literal: true
|
2
|
+
|
3
|
+
module DB2Query
|
4
|
+
class Connection
|
5
|
+
ADAPTER_NAME = "DB2Query"
|
6
|
+
|
7
|
+
include DB2Query::DatabaseStatements
|
8
|
+
include ActiveSupport::Callbacks
|
9
|
+
define_callbacks :checkout, :checkin
|
10
|
+
|
11
|
+
set_callback :checkin, :after, :enable_lazy_transactions!
|
12
|
+
|
13
|
+
attr_accessor :pool
|
14
|
+
attr_reader :owner, :connector, :lock
|
15
|
+
alias :in_use? :owner
|
16
|
+
|
17
|
+
def initialize(type, config)
|
18
|
+
@connector = DB2Query::ODBCConnector.new(type, config)
|
19
|
+
@instrumenter = ActiveSupport::Notifications.instrumenter
|
20
|
+
@config = config
|
21
|
+
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
|
22
|
+
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
|
23
|
+
connect
|
24
|
+
end
|
25
|
+
|
26
|
+
def adapter_name
|
27
|
+
self.class::ADAPTER_NAME
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_transaction
|
31
|
+
end
|
32
|
+
|
33
|
+
def begin_transaction(options = {})
|
34
|
+
end
|
35
|
+
|
36
|
+
def transaction_open?
|
37
|
+
end
|
38
|
+
|
39
|
+
def requires_reloading?
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
pool.checkin self
|
45
|
+
end
|
46
|
+
|
47
|
+
def connect
|
48
|
+
@connection = connector.connect
|
49
|
+
end
|
50
|
+
|
51
|
+
def active?
|
52
|
+
@connection.connected?
|
53
|
+
end
|
54
|
+
|
55
|
+
def reconnect!
|
56
|
+
disconnect!
|
57
|
+
connect
|
58
|
+
end
|
59
|
+
alias reset! reconnect!
|
60
|
+
|
61
|
+
def disconnect!
|
62
|
+
if @connection.connected?
|
63
|
+
@connection.commit
|
64
|
+
@connection.disconnect
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_version
|
69
|
+
end
|
70
|
+
|
71
|
+
def enable_lazy_transactions!
|
72
|
+
@lazy_transactions_enabled = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def lease
|
76
|
+
if in_use?
|
77
|
+
msg = +"Cannot lease connection, "
|
78
|
+
if @owner == Thread.current
|
79
|
+
msg << "it is already leased by the current thread."
|
80
|
+
else
|
81
|
+
msg << "it is already in use by a different thread: #{@owner}. " \
|
82
|
+
"Current thread: #{Thread.current}."
|
83
|
+
end
|
84
|
+
raise ActiveRecordError, msg
|
85
|
+
end
|
86
|
+
|
87
|
+
@owner = Thread.current
|
88
|
+
end
|
89
|
+
|
90
|
+
def verify!
|
91
|
+
reconnect! unless active?
|
92
|
+
end
|
93
|
+
|
94
|
+
def translate_exception_class(e, sql, binds)
|
95
|
+
message = "#{e.class.name}: #{e.message}"
|
96
|
+
|
97
|
+
exception = translate_exception(
|
98
|
+
e, message: message, sql: sql, binds: binds
|
99
|
+
)
|
100
|
+
exception.set_backtrace e.backtrace
|
101
|
+
exception
|
102
|
+
end
|
103
|
+
|
104
|
+
def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc:
|
105
|
+
@instrumenter.instrument(
|
106
|
+
"sql.active_record",
|
107
|
+
sql: sql,
|
108
|
+
name: name,
|
109
|
+
binds: binds,
|
110
|
+
type_casted_binds: type_casted_binds,
|
111
|
+
statement_name: statement_name,
|
112
|
+
connection_id: object_id,
|
113
|
+
connection: self) do
|
114
|
+
@lock.synchronize do
|
115
|
+
yield
|
116
|
+
end
|
117
|
+
rescue => e
|
118
|
+
raise translate_exception_class(e, sql, binds)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def translate_exception(exception, message:, sql:, binds:)
|
123
|
+
case exception
|
124
|
+
when RuntimeError
|
125
|
+
exception
|
126
|
+
else
|
127
|
+
ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def expire
|
132
|
+
if in_use?
|
133
|
+
if @owner != Thread.current
|
134
|
+
raise ActiveRecordError, "Cannot expire connection, " \
|
135
|
+
"it is owned by a different thread: #{@owner}. " \
|
136
|
+
"Current thread: #{Thread.current}."
|
137
|
+
end
|
138
|
+
|
139
|
+
@idle_since = Concurrent.monotonic_time
|
140
|
+
@owner = nil
|
141
|
+
else
|
142
|
+
raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def steal!
|
147
|
+
if in_use?
|
148
|
+
if @owner != Thread.current
|
149
|
+
pool.send :remove_connection_from_thread_cache, self, @owner
|
150
|
+
|
151
|
+
@owner = Thread.current
|
152
|
+
end
|
153
|
+
else
|
154
|
+
raise ActiveRecordError, "Cannot steal connection, it is not currently leased."
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def seconds_idle # :nodoc:
|
159
|
+
return 0 if in_use?
|
160
|
+
Concurrent.monotonic_time - @idle_since
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -1,17 +1,87 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DB2Query
|
4
|
+
CONNECTION_TYPES = %i[dsn conn_string].freeze
|
5
|
+
|
6
|
+
class ConnectionPool < ActiveRecord::ConnectionAdapters::ConnectionPool
|
7
|
+
attr_reader :conn_type
|
8
|
+
|
9
|
+
def initialize(spec)
|
10
|
+
@conn_type = (spec.config.keys & DB2Query::CONNECTION_TYPES).first
|
11
|
+
super(spec)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def new_connection
|
16
|
+
DB2Query::Connection.new(conn_type, spec.config)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ConnectionSpecification #:nodoc:
|
21
|
+
attr_reader :name, :config
|
22
|
+
|
23
|
+
def initialize(name, config)
|
24
|
+
@name, @config = name, config
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize_dup(original)
|
28
|
+
@config = original.config.dup
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
@config.merge(name: @name)
|
33
|
+
end
|
34
|
+
|
35
|
+
class Resolver < ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver
|
36
|
+
def spec(config)
|
37
|
+
pool_name = config if config.is_a?(Symbol)
|
38
|
+
spec = resolve(config, pool_name).symbolize_keys
|
39
|
+
ConnectionSpecification.new(spec.delete(:name) || "primary", spec)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class ConnectionHandler < ActiveRecord::ConnectionAdapters::ConnectionHandler
|
45
|
+
def establish_connection(config)
|
46
|
+
resolver = ConnectionSpecification::Resolver.new(DB2Query::Base.configurations)
|
47
|
+
|
48
|
+
spec = resolver.spec(config)
|
49
|
+
|
50
|
+
remove_connection(spec.name)
|
51
|
+
|
52
|
+
message_bus = ActiveSupport::Notifications.instrumenter
|
53
|
+
payload = {
|
54
|
+
connection_id: object_id
|
55
|
+
}
|
56
|
+
if spec
|
57
|
+
payload[:spec_name] = spec.name
|
58
|
+
payload[:config] = spec.config
|
59
|
+
end
|
60
|
+
|
61
|
+
message_bus.instrument("!connection.active_record", payload) do
|
62
|
+
owner_to_pool[spec.name] = DB2Query::ConnectionPool.new(spec)
|
63
|
+
end
|
64
|
+
|
65
|
+
owner_to_pool[spec.name]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
4
69
|
module ConnectionHandling
|
5
70
|
RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence }
|
6
71
|
DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
|
7
72
|
|
73
|
+
def lookup_connection_handler(handler_key) # :nodoc:
|
74
|
+
handler_key = DB2Query::Base.reading_role
|
75
|
+
connection_handlers[handler_key] ||= DB2Query::ConnectionHandler.new
|
76
|
+
end
|
77
|
+
|
8
78
|
def resolve_config_for_connection(config_or_env) # :nodoc:
|
9
79
|
raise "Anonymous class is not allowed." unless name
|
10
80
|
|
11
81
|
config_or_env ||= DEFAULT_ENV.call.to_sym
|
12
82
|
pool_name = primary_class? ? "primary" : name
|
13
83
|
self.connection_specification_name = pool_name
|
14
|
-
resolver =
|
84
|
+
resolver = DB2Query::ConnectionSpecification::Resolver.new(DB2Query::Base.configurations)
|
15
85
|
|
16
86
|
config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
|
17
87
|
config_hash[:name] = pool_name
|
@@ -21,13 +91,22 @@ module DB2Query
|
|
21
91
|
|
22
92
|
def connection_specification_name
|
23
93
|
if !defined?(@connection_specification_name) || @connection_specification_name.nil?
|
24
|
-
return self == Base ? "primary" : superclass.connection_specification_name
|
94
|
+
return self == DB2Query::Base ? "primary" : superclass.connection_specification_name
|
25
95
|
end
|
26
96
|
@connection_specification_name
|
27
97
|
end
|
28
98
|
|
29
99
|
def primary_class?
|
30
|
-
self == Base || defined?(
|
100
|
+
self == DB2Query::Base || defined?(Db2Record) && self == Db2Record
|
31
101
|
end
|
102
|
+
|
103
|
+
private
|
104
|
+
def swap_connection_handler(handler, &blk) # :nodoc:
|
105
|
+
old_handler, DB2Query::Base.connection_handler = DB2Query::Base.connection_handler, handler
|
106
|
+
return_value = yield
|
107
|
+
return_value
|
108
|
+
ensure
|
109
|
+
DB2Query::Base.connection_handler = old_handler
|
110
|
+
end
|
32
111
|
end
|
33
112
|
end
|
data/lib/db2_query/core.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record/database_configurations"
|
4
|
+
|
3
5
|
module DB2Query
|
4
6
|
module Core
|
5
7
|
extend ActiveSupport::Concern
|
6
8
|
|
7
|
-
included do
|
9
|
+
included do
|
8
10
|
def self.configurations=(config)
|
9
11
|
@@configurations = ActiveRecord::DatabaseConfigurations.new(config)
|
10
12
|
end
|
@@ -14,28 +16,26 @@ module DB2Query
|
|
14
16
|
@@configurations
|
15
17
|
end
|
16
18
|
|
17
|
-
class_attribute :default_connection_handler
|
18
|
-
|
19
19
|
mattr_accessor :connection_handlers, instance_accessor: false, default: {}
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
mattr_accessor :reading_role, instance_accessor: false, default: :reading
|
21
|
+
class_attribute :default_connection_handler
|
24
22
|
|
25
23
|
def self.connection_handler
|
26
|
-
Thread.current.thread_variable_get("
|
24
|
+
Thread.current.thread_variable_get("db2q_connection_handler") || default_connection_handler
|
27
25
|
end
|
28
26
|
|
29
27
|
def self.connection_handler=(handler)
|
30
|
-
Thread.current.thread_variable_set("
|
28
|
+
Thread.current.thread_variable_set("db2q_connection_handler", handler)
|
31
29
|
end
|
32
30
|
|
33
|
-
self.default_connection_handler =
|
34
|
-
|
35
|
-
base.extend ClassMethods
|
31
|
+
self.default_connection_handler = DB2Query::ConnectionHandler.new
|
36
32
|
end
|
37
33
|
|
38
34
|
module ClassMethods
|
35
|
+
def initiation
|
36
|
+
yield(self) if block_given?
|
37
|
+
end
|
38
|
+
|
39
39
|
def attributes(attr_name, format)
|
40
40
|
formatters.store(attr_name, format)
|
41
41
|
end
|
@@ -1,31 +1,33 @@
|
|
1
1
|
# frozen_String_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
CONNECTION_TYPES = %i[dsn conn_string].freeze
|
3
|
+
require "odbc_utf8"
|
5
4
|
|
5
|
+
module DB2Query
|
6
6
|
class ODBCConnector
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@conn_type, @conn_config = type, config.transform_keys(&:to_sym)
|
11
|
-
@connector = DB2Query.const_get("#{conn_type.to_s.camelize}Connector").new
|
7
|
+
def self.new(type, config)
|
8
|
+
conn_type, conn_config = type, config.transform_keys(&:to_sym)
|
9
|
+
DB2Query.const_get("#{conn_type.to_s.camelize}Connector").new(conn_config)
|
12
10
|
end
|
11
|
+
end
|
13
12
|
|
14
|
-
|
15
|
-
|
13
|
+
class AbstractConnector
|
14
|
+
attr_reader :config
|
15
|
+
|
16
|
+
def initialize(config)
|
17
|
+
@config = config
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
19
|
-
class DsnConnector
|
20
|
-
def connect
|
21
|
+
class DsnConnector < AbstractConnector
|
22
|
+
def connect
|
21
23
|
::ODBC.connect(config[:dsn], config[:uid], config[:pwd])
|
22
24
|
rescue ::ODBC::Error => e
|
23
25
|
raise ArgumentError, "Unable to activate ODBC DSN connection #{e}"
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
27
|
-
class ConnStringConnector
|
28
|
-
def connect
|
29
|
+
class ConnStringConnector < AbstractConnector
|
30
|
+
def connect
|
29
31
|
driver = ::ODBC::Driver.new.tap do |d|
|
30
32
|
d.attrs = config[:conn_string].transform_keys(&:to_s)
|
31
33
|
d.name = "odbc"
|
data/lib/db2_query/railtie.rb
CHANGED
data/lib/db2_query/result.rb
CHANGED
@@ -15,6 +15,12 @@ module DB2Query
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
def to_h
|
19
|
+
rows.map do |row|
|
20
|
+
columns.zip(row).each_with_object({}) { |cr, h| h[cr[0].to_sym] = cr[1] }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
18
24
|
def inspect
|
19
25
|
entries = records.take(11).map!(&:inspect)
|
20
26
|
|
@@ -25,7 +31,7 @@ module DB2Query
|
|
25
31
|
|
26
32
|
class Record
|
27
33
|
attr_reader :formatters
|
28
|
-
|
34
|
+
|
29
35
|
def initialize(row, columns, formatters)
|
30
36
|
@formatters = formatters
|
31
37
|
columns.zip(row) do |col, val|
|
@@ -6,6 +6,11 @@ DB2_QUERY_INITIALIZER_TEMPLATE ||= <<-EOF
|
|
6
6
|
require "db2_query"
|
7
7
|
require "db2_query/formatter"
|
8
8
|
|
9
|
+
DB2Query::Base.initiation do |base|
|
10
|
+
base.configurations = base.parent.config
|
11
|
+
base.establish_connection ENV['RAILS_ENV'].to_sym
|
12
|
+
end
|
13
|
+
|
9
14
|
# Example
|
10
15
|
|
11
16
|
class FirstNameFormatter < DB2Query::AbstractFormatter
|
@@ -34,4 +39,4 @@ namespace :db2query do
|
|
34
39
|
puts " File '#{initializer_path}' created."
|
35
40
|
end
|
36
41
|
end
|
37
|
-
end
|
42
|
+
end
|
data/lib/db2_query/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: db2_query
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yohanes_l
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-odbc
|
@@ -31,9 +31,6 @@ dependencies:
|
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 6.0.3
|
34
|
-
- - ">="
|
35
|
-
- !ruby/object:Gem::Version
|
36
|
-
version: 6.0.3.1
|
37
34
|
type: :runtime
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -41,9 +38,6 @@ dependencies:
|
|
41
38
|
- - "~>"
|
42
39
|
- !ruby/object:Gem::Version
|
43
40
|
version: 6.0.3
|
44
|
-
- - ">="
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: 6.0.3.1
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
42
|
name: activerecord
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -51,9 +45,6 @@ dependencies:
|
|
51
45
|
- - "~>"
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: 6.0.3
|
54
|
-
- - ">="
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: 6.0.3.1
|
57
48
|
type: :runtime
|
58
49
|
prerelease: false
|
59
50
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -61,9 +52,6 @@ dependencies:
|
|
61
52
|
- - "~>"
|
62
53
|
- !ruby/object:Gem::Version
|
63
54
|
version: 6.0.3
|
64
|
-
- - ">="
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: 6.0.3.1
|
67
55
|
- !ruby/object:Gem::Dependency
|
68
56
|
name: rubocop
|
69
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,10 +160,10 @@ files:
|
|
172
160
|
- MIT-LICENSE
|
173
161
|
- README.md
|
174
162
|
- Rakefile
|
175
|
-
- lib/active_record/connection_adapters/db2_query_adapter.rb
|
176
163
|
- lib/db2_query.rb
|
177
164
|
- lib/db2_query/base.rb
|
178
165
|
- lib/db2_query/config.rb
|
166
|
+
- lib/db2_query/connection.rb
|
179
167
|
- lib/db2_query/connection_handling.rb
|
180
168
|
- lib/db2_query/core.rb
|
181
169
|
- lib/db2_query/database_statements.rb
|
@@ -207,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
207
195
|
- !ruby/object:Gem::Version
|
208
196
|
version: '0'
|
209
197
|
requirements: []
|
210
|
-
rubygems_version: 3.
|
198
|
+
rubygems_version: 3.0.3
|
211
199
|
signing_key:
|
212
200
|
specification_version: 4
|
213
201
|
summary: DB2Query
|
@@ -1,203 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "odbc_utf8"
|
4
|
-
require "db2_query/odbc_connector"
|
5
|
-
require "db2_query/database_statements"
|
6
|
-
|
7
|
-
module ActiveRecord
|
8
|
-
module ConnectionHandling
|
9
|
-
def db2_query_connection(config)
|
10
|
-
conn_type = (config.keys & DB2Query::CONNECTION_TYPES).first
|
11
|
-
if conn_type.nil?
|
12
|
-
raise ArgumentError, "No data source name (:dsn) or connection string (:conn_str) provided."
|
13
|
-
end
|
14
|
-
connector = DB2Query::ODBCConnector.new(conn_type, config)
|
15
|
-
ConnectionAdapters::DB2QueryConnection.new(connector, config)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
module ConnectionAdapters
|
20
|
-
class DB2QueryConnection
|
21
|
-
include DB2Query::DatabaseStatements
|
22
|
-
include ActiveSupport::Callbacks
|
23
|
-
define_callbacks :checkout, :checkin
|
24
|
-
|
25
|
-
set_callback :checkin, :after, :enable_lazy_transactions!
|
26
|
-
|
27
|
-
attr_accessor :pool
|
28
|
-
attr_reader :owner, :connector, :lock
|
29
|
-
alias :in_use? :owner
|
30
|
-
|
31
|
-
def initialize(connector, config)
|
32
|
-
@connector = connector
|
33
|
-
@instrumenter = ActiveSupport::Notifications.instrumenter
|
34
|
-
@config = config
|
35
|
-
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
|
36
|
-
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
|
37
|
-
connect
|
38
|
-
end
|
39
|
-
|
40
|
-
def connect
|
41
|
-
@connection = connector.connect
|
42
|
-
@connection.use_time = true
|
43
|
-
end
|
44
|
-
|
45
|
-
def active?
|
46
|
-
@connection.connected?
|
47
|
-
end
|
48
|
-
|
49
|
-
def reconnect!
|
50
|
-
disconnect!
|
51
|
-
connect
|
52
|
-
end
|
53
|
-
alias reset! reconnect!
|
54
|
-
|
55
|
-
def disconnect!
|
56
|
-
if @connection.connected?
|
57
|
-
@connection.commit
|
58
|
-
@connection.disconnect
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def check_version
|
63
|
-
end
|
64
|
-
|
65
|
-
def enable_lazy_transactions!
|
66
|
-
@lazy_transactions_enabled = true
|
67
|
-
end
|
68
|
-
|
69
|
-
def lease
|
70
|
-
if in_use?
|
71
|
-
msg = +"Cannot lease connection, "
|
72
|
-
if @owner == Thread.current
|
73
|
-
msg << "it is already leased by the current thread."
|
74
|
-
else
|
75
|
-
msg << "it is already in use by a different thread: #{@owner}. " \
|
76
|
-
"Current thread: #{Thread.current}."
|
77
|
-
end
|
78
|
-
raise ActiveRecordError, msg
|
79
|
-
end
|
80
|
-
|
81
|
-
@owner = Thread.current
|
82
|
-
end
|
83
|
-
|
84
|
-
def verify!
|
85
|
-
reconnect! unless active?
|
86
|
-
end
|
87
|
-
|
88
|
-
def translate_exception_class(e, sql, binds)
|
89
|
-
message = "#{e.class.name}: #{e.message}"
|
90
|
-
|
91
|
-
exception = translate_exception(
|
92
|
-
e, message: message, sql: sql, binds: binds
|
93
|
-
)
|
94
|
-
exception.set_backtrace e.backtrace
|
95
|
-
exception
|
96
|
-
end
|
97
|
-
|
98
|
-
def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc:
|
99
|
-
@instrumenter.instrument(
|
100
|
-
"sql.active_record",
|
101
|
-
sql: sql,
|
102
|
-
name: name,
|
103
|
-
binds: binds,
|
104
|
-
type_casted_binds: type_casted_binds,
|
105
|
-
statement_name: statement_name,
|
106
|
-
connection_id: object_id,
|
107
|
-
connection: self) do
|
108
|
-
@lock.synchronize do
|
109
|
-
yield
|
110
|
-
end
|
111
|
-
rescue => e
|
112
|
-
raise translate_exception_class(e, sql, binds)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def translate_exception(exception, message:, sql:, binds:)
|
117
|
-
case exception
|
118
|
-
when RuntimeError
|
119
|
-
exception
|
120
|
-
else
|
121
|
-
ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def expire
|
126
|
-
if in_use?
|
127
|
-
if @owner != Thread.current
|
128
|
-
raise ActiveRecordError, "Cannot expire connection, " \
|
129
|
-
"it is owned by a different thread: #{@owner}. " \
|
130
|
-
"Current thread: #{Thread.current}."
|
131
|
-
end
|
132
|
-
|
133
|
-
@idle_since = Concurrent.monotonic_time
|
134
|
-
@owner = nil
|
135
|
-
else
|
136
|
-
raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def steal!
|
141
|
-
if in_use?
|
142
|
-
if @owner != Thread.current
|
143
|
-
pool.send :remove_connection_from_thread_cache, self, @owner
|
144
|
-
|
145
|
-
@owner = Thread.current
|
146
|
-
end
|
147
|
-
else
|
148
|
-
raise ActiveRecordError, "Cannot steal connection, it is not currently leased."
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def seconds_idle # :nodoc:
|
153
|
-
return 0 if in_use?
|
154
|
-
Concurrent.monotonic_time - @idle_since
|
155
|
-
end
|
156
|
-
|
157
|
-
private
|
158
|
-
def type_map
|
159
|
-
@type_map ||= Type::TypeMap.new.tap do |mapping|
|
160
|
-
initialize_type_map(mapping)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def alias_type(map, new_type, old_type)
|
165
|
-
map.register_type(new_type) do |_, *args|
|
166
|
-
map.lookup(old_type, *args)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def initialize_type_map(map)
|
171
|
-
map.register_type "boolean", Type::Boolean.new
|
172
|
-
map.register_type ODBC::SQL_CHAR, Type::String.new
|
173
|
-
map.register_type ODBC::SQL_LONGVARCHAR, Type::Text.new
|
174
|
-
map.register_type ODBC::SQL_TINYINT, Type::Integer.new(limit: 4)
|
175
|
-
map.register_type ODBC::SQL_SMALLINT, Type::Integer.new(limit: 8)
|
176
|
-
map.register_type ODBC::SQL_INTEGER, Type::Integer.new(limit: 16)
|
177
|
-
map.register_type ODBC::SQL_BIGINT, Type::BigInteger.new(limit: 32)
|
178
|
-
map.register_type ODBC::SQL_REAL, Type::Float.new(limit: 24)
|
179
|
-
map.register_type ODBC::SQL_FLOAT, Type::Float.new
|
180
|
-
map.register_type ODBC::SQL_DOUBLE, Type::Float.new(limit: 53)
|
181
|
-
map.register_type ODBC::SQL_DECIMAL, Type::Float.new
|
182
|
-
map.register_type ODBC::SQL_NUMERIC, Type::Integer.new
|
183
|
-
map.register_type ODBC::SQL_BINARY, Type::Binary.new
|
184
|
-
map.register_type ODBC::SQL_DATE, Type::Date.new
|
185
|
-
map.register_type ODBC::SQL_DATETIME, Type::DateTime.new
|
186
|
-
map.register_type ODBC::SQL_TIME, Type::Time.new
|
187
|
-
map.register_type ODBC::SQL_TIMESTAMP, Type::DateTime.new
|
188
|
-
map.register_type ODBC::SQL_GUID, Type::String.new
|
189
|
-
|
190
|
-
alias_type map, ODBC::SQL_BIT, "boolean"
|
191
|
-
alias_type map, ODBC::SQL_VARCHAR, ODBC::SQL_CHAR
|
192
|
-
alias_type map, ODBC::SQL_WCHAR, ODBC::SQL_CHAR
|
193
|
-
alias_type map, ODBC::SQL_WVARCHAR, ODBC::SQL_CHAR
|
194
|
-
alias_type map, ODBC::SQL_WLONGVARCHAR, ODBC::SQL_LONGVARCHAR
|
195
|
-
alias_type map, ODBC::SQL_VARBINARY, ODBC::SQL_BINARY
|
196
|
-
alias_type map, ODBC::SQL_LONGVARBINARY, ODBC::SQL_BINARY
|
197
|
-
alias_type map, ODBC::SQL_TYPE_DATE, ODBC::SQL_DATE
|
198
|
-
alias_type map, ODBC::SQL_TYPE_TIME, ODBC::SQL_TIME
|
199
|
-
alias_type map, ODBC::SQL_TYPE_TIMESTAMP, ODBC::SQL_TIMESTAMP
|
200
|
-
end
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|