db2_query 0.2.1 → 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 +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
|
[](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
|