db2_query 0.2.3 → 0.3.3
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/MIT-LICENSE +1 -1
- data/README.md +472 -124
- data/Rakefile +3 -2
- data/lib/db2_query/base.rb +15 -5
- data/lib/db2_query/config.rb +20 -17
- data/lib/db2_query/core.rb +79 -60
- data/lib/db2_query/db_client.rb +56 -0
- data/lib/db2_query/db_connection.rb +68 -0
- data/lib/db2_query/db_statements.rb +87 -0
- data/lib/db2_query/definitions.rb +93 -0
- data/lib/db2_query/error.rb +72 -7
- data/lib/db2_query/field_type.rb +31 -0
- data/lib/db2_query/helper.rb +50 -0
- data/lib/db2_query/logger.rb +52 -0
- data/lib/db2_query/query.rb +128 -0
- data/lib/db2_query/railtie.rb +5 -10
- data/lib/db2_query/result.rb +51 -31
- data/lib/db2_query/sql_statement.rb +34 -0
- data/lib/db2_query/tasks/database.rake +2 -46
- data/lib/db2_query/tasks/init.rake +1 -1
- data/lib/db2_query/tasks/initializer.rake +2 -34
- data/lib/db2_query/tasks/templates/database.rb.tt +19 -0
- data/lib/db2_query/tasks/templates/initializer.rb.tt +8 -0
- data/lib/db2_query/tasks.rb +29 -0
- data/lib/db2_query/type/binary.rb +19 -0
- data/lib/db2_query/type/boolean.rb +41 -0
- data/lib/db2_query/type/date.rb +34 -0
- data/lib/db2_query/type/decimal.rb +15 -0
- data/lib/db2_query/type/integer.rb +15 -0
- data/lib/db2_query/type/string.rb +30 -0
- data/lib/db2_query/type/text.rb +11 -0
- data/lib/db2_query/type/time.rb +30 -0
- data/lib/db2_query/type/timestamp.rb +30 -0
- data/lib/db2_query/type/value.rb +29 -0
- data/lib/db2_query/version.rb +2 -2
- data/lib/db2_query.rb +42 -18
- data/lib/rails/generators/query/USAGE +15 -0
- data/lib/rails/generators/query/query_generator.rb +70 -0
- data/lib/rails/generators/query/templates/query.rb.tt +26 -0
- data/lib/rails/generators/query/templates/query_definitions.rb.tt +18 -0
- data/lib/rails/generators/query/templates/unit_test.rb.tt +9 -0
- metadata +74 -36
- data/lib/db2_query/bind.rb +0 -6
- data/lib/db2_query/connection.rb +0 -164
- data/lib/db2_query/connection_handling.rb +0 -112
- data/lib/db2_query/database_statements.rb +0 -89
- data/lib/db2_query/formatter.rb +0 -27
- data/lib/db2_query/odbc_connector.rb +0 -44
data/lib/db2_query/connection.rb
DELETED
@@ -1,164 +0,0 @@
|
|
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
|
-
|
10
|
-
define_callbacks :checkout, :checkin
|
11
|
-
|
12
|
-
set_callback :checkin, :after, :enable_lazy_transactions!
|
13
|
-
|
14
|
-
attr_accessor :pool
|
15
|
-
attr_reader :owner, :connector, :lock
|
16
|
-
alias :in_use? :owner
|
17
|
-
|
18
|
-
def initialize(type, config)
|
19
|
-
@connector = DB2Query::ODBCConnector.new(type, config)
|
20
|
-
@instrumenter = ActiveSupport::Notifications.instrumenter
|
21
|
-
@config = config
|
22
|
-
@pool = ActiveRecord::ConnectionAdapters::NullPool.new
|
23
|
-
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
|
24
|
-
connect
|
25
|
-
end
|
26
|
-
|
27
|
-
def adapter_name
|
28
|
-
self.class::ADAPTER_NAME
|
29
|
-
end
|
30
|
-
|
31
|
-
def current_transaction
|
32
|
-
end
|
33
|
-
|
34
|
-
def begin_transaction(options = {})
|
35
|
-
end
|
36
|
-
|
37
|
-
def transaction_open?
|
38
|
-
end
|
39
|
-
|
40
|
-
def requires_reloading?
|
41
|
-
false
|
42
|
-
end
|
43
|
-
|
44
|
-
def close
|
45
|
-
pool.checkin self
|
46
|
-
end
|
47
|
-
|
48
|
-
def connect
|
49
|
-
@connection = connector.connect
|
50
|
-
end
|
51
|
-
|
52
|
-
def active?
|
53
|
-
@connection.connected?
|
54
|
-
end
|
55
|
-
|
56
|
-
def reconnect!
|
57
|
-
disconnect!
|
58
|
-
connect
|
59
|
-
end
|
60
|
-
alias reset! reconnect!
|
61
|
-
|
62
|
-
def disconnect!
|
63
|
-
if @connection.connected?
|
64
|
-
@connection.commit
|
65
|
-
@connection.disconnect
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def check_version
|
70
|
-
end
|
71
|
-
|
72
|
-
def enable_lazy_transactions!
|
73
|
-
@lazy_transactions_enabled = true
|
74
|
-
end
|
75
|
-
|
76
|
-
def lease
|
77
|
-
if in_use?
|
78
|
-
msg = +"Cannot lease connection, "
|
79
|
-
if @owner == Thread.current
|
80
|
-
msg << "it is already leased by the current thread."
|
81
|
-
else
|
82
|
-
msg << "it is already in use by a different thread: #{@owner}. " \
|
83
|
-
"Current thread: #{Thread.current}."
|
84
|
-
end
|
85
|
-
raise DB2Query::Error, msg
|
86
|
-
end
|
87
|
-
|
88
|
-
@owner = Thread.current
|
89
|
-
end
|
90
|
-
|
91
|
-
def verify!
|
92
|
-
reconnect! unless active?
|
93
|
-
end
|
94
|
-
|
95
|
-
def translate_exception_class(e, sql, binds)
|
96
|
-
message = "#{e.class.name}: #{e.message}"
|
97
|
-
|
98
|
-
exception = translate_exception(
|
99
|
-
e, message: message, sql: sql, binds: binds
|
100
|
-
)
|
101
|
-
exception.set_backtrace e.backtrace
|
102
|
-
exception
|
103
|
-
end
|
104
|
-
|
105
|
-
def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc:
|
106
|
-
@instrumenter.instrument(
|
107
|
-
"sql.active_record",
|
108
|
-
sql: sql,
|
109
|
-
name: name,
|
110
|
-
binds: binds,
|
111
|
-
type_casted_binds: type_casted_binds,
|
112
|
-
statement_name: statement_name,
|
113
|
-
connection_id: object_id,
|
114
|
-
connection: self) do
|
115
|
-
@lock.synchronize do
|
116
|
-
yield
|
117
|
-
end
|
118
|
-
rescue => e
|
119
|
-
raise translate_exception_class(e, sql, binds)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def translate_exception(exception, message:, sql:, binds:)
|
124
|
-
case exception
|
125
|
-
when RuntimeError
|
126
|
-
exception
|
127
|
-
else
|
128
|
-
DB2Query::StatementInvalid.new(message, sql: sql, binds: binds)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def expire
|
133
|
-
if in_use?
|
134
|
-
if @owner != Thread.current
|
135
|
-
raise DB2Query::Error, "Cannot expire connection, " \
|
136
|
-
"it is owned by a different thread: #{@owner}. " \
|
137
|
-
"Current thread: #{Thread.current}."
|
138
|
-
end
|
139
|
-
|
140
|
-
@idle_since = Concurrent.monotonic_time
|
141
|
-
@owner = nil
|
142
|
-
else
|
143
|
-
raise DB2Query::Error, "Cannot expire connection, it is not currently leased."
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def steal!
|
148
|
-
if in_use?
|
149
|
-
if @owner != Thread.current
|
150
|
-
pool.send :remove_connection_from_thread_cache, self, @owner
|
151
|
-
|
152
|
-
@owner = Thread.current
|
153
|
-
end
|
154
|
-
else
|
155
|
-
raise DB2Query::Error, "Cannot steal connection, it is not currently leased."
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def seconds_idle
|
160
|
-
return 0 if in_use?
|
161
|
-
Concurrent.monotonic_time - @idle_since
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
@@ -1,112 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
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
|
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
|
-
|
69
|
-
module ConnectionHandling
|
70
|
-
RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence }
|
71
|
-
DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
|
72
|
-
|
73
|
-
def lookup_connection_handler(handler_key)
|
74
|
-
handler_key = DB2Query::Base.reading_role
|
75
|
-
connection_handlers[handler_key] ||= DB2Query::ConnectionHandler.new
|
76
|
-
end
|
77
|
-
|
78
|
-
def resolve_config_for_connection(config_or_env)
|
79
|
-
raise "Anonymous class is not allowed." unless name
|
80
|
-
|
81
|
-
config_or_env ||= DEFAULT_ENV.call.to_sym
|
82
|
-
pool_name = primary_class? ? "primary" : name
|
83
|
-
self.connection_specification_name = pool_name
|
84
|
-
resolver = DB2Query::ConnectionSpecification::Resolver.new(DB2Query::Base.configurations)
|
85
|
-
|
86
|
-
config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
|
87
|
-
config_hash[:name] = pool_name
|
88
|
-
|
89
|
-
config_hash
|
90
|
-
end
|
91
|
-
|
92
|
-
def connection_specification_name
|
93
|
-
if !defined?(@connection_specification_name) || @connection_specification_name.nil?
|
94
|
-
return self == DB2Query::Base ? "primary" : superclass.connection_specification_name
|
95
|
-
end
|
96
|
-
@connection_specification_name
|
97
|
-
end
|
98
|
-
|
99
|
-
def primary_class?
|
100
|
-
self == DB2Query::Base || defined?(Db2Record) && self == Db2Record
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
def swap_connection_handler(handler, &blk)
|
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
|
111
|
-
end
|
112
|
-
end
|
@@ -1,89 +0,0 @@
|
|
1
|
-
# frozen_String_literal: true
|
2
|
-
|
3
|
-
module DB2Query
|
4
|
-
module DatabaseStatements
|
5
|
-
def query(sql)
|
6
|
-
stmt = @connection.run(sql)
|
7
|
-
stmt.to_a
|
8
|
-
ensure
|
9
|
-
stmt.drop unless stmt.nil?
|
10
|
-
end
|
11
|
-
|
12
|
-
def query_rows(sql)
|
13
|
-
query(sql)
|
14
|
-
end
|
15
|
-
|
16
|
-
def query_value(sql)
|
17
|
-
single_value_from_rows(query(sql))
|
18
|
-
end
|
19
|
-
|
20
|
-
def query_values(sql)
|
21
|
-
query(sql).map(&:first)
|
22
|
-
end
|
23
|
-
|
24
|
-
def execute(sql, args = [])
|
25
|
-
@connection.do(sql, *args)
|
26
|
-
end
|
27
|
-
|
28
|
-
def exec_query(formatters, sql, args = [])
|
29
|
-
binds, args = extract_binds_from_sql(sql, args)
|
30
|
-
log(sql, "SQL", binds, args) do
|
31
|
-
begin
|
32
|
-
if args.empty?
|
33
|
-
stmt = @connection.run(sql)
|
34
|
-
else
|
35
|
-
stmt = @connection.run(sql, *args)
|
36
|
-
end
|
37
|
-
columns = stmt.columns.values.map { |col| col.name.downcase }
|
38
|
-
rows = stmt.to_a
|
39
|
-
ensure
|
40
|
-
stmt.drop unless stmt.nil?
|
41
|
-
end
|
42
|
-
DB2Query::Result.new(columns, rows, formatters)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
def single_value_from_rows(rows)
|
48
|
-
row = rows.first
|
49
|
-
row && row.first
|
50
|
-
end
|
51
|
-
|
52
|
-
def key_finder_regex(k)
|
53
|
-
/#{k} .\\? | #{k}.\\? | #{k}. \\? /i
|
54
|
-
end
|
55
|
-
|
56
|
-
def extract_binds_from_sql(sql, args)
|
57
|
-
question_mark_positions = sql.enum_for(:scan, /\?/i).map { Regexp.last_match.begin(0) }
|
58
|
-
args = args.first.is_a?(Hash) ? args.first : args
|
59
|
-
given, expected = args.length, question_mark_positions.length
|
60
|
-
|
61
|
-
if given != expected
|
62
|
-
raise DB2Query::Error, "wrong number of arguments (given #{given}, expected #{expected})"
|
63
|
-
end
|
64
|
-
|
65
|
-
if args.is_a?(Hash)
|
66
|
-
binds = args.map do |key, value|
|
67
|
-
position = sql.enum_for(:scan, key_finder_regex(key)).map { Regexp.last_match.begin(0) }
|
68
|
-
if position.empty?
|
69
|
-
raise DB2Query::Error, "Column name: `#{key}` not found inside sql statement."
|
70
|
-
elsif position.length > 1
|
71
|
-
raise DB2Query::Error, "Can't handle such this kind of sql. Please refactor your sql."
|
72
|
-
else
|
73
|
-
index = position[0]
|
74
|
-
end
|
75
|
-
|
76
|
-
DB2Query::Bind.new(key.to_s, value, index)
|
77
|
-
end
|
78
|
-
binds = binds.sort_by { |bind| bind.index }
|
79
|
-
[binds.map { |bind| [bind, bind.value] }, binds.map { |bind| bind.value }]
|
80
|
-
elsif question_mark_positions.length == 1 && args.length == 1
|
81
|
-
column = sql[/(.*?) . \?|(.*?) .\?|(.*?). \?|(.*?).\?/m, 1].split.last.downcase
|
82
|
-
bind = DB2Query::Bind.new(column.gsub(/[)(]/, ""), args, 0)
|
83
|
-
[[[bind, bind.value]], bind.value]
|
84
|
-
else
|
85
|
-
[args.map { |arg| [nil, arg] }, args]
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
data/lib/db2_query/formatter.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module DB2Query
|
4
|
-
module Formatter
|
5
|
-
def self.register(name, klass)
|
6
|
-
self.format_registry.store(name.to_sym, klass.new)
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.format_registry
|
10
|
-
@@format_registry ||= Hash.new
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.lookup(name)
|
14
|
-
@@format_registry.fetch(name)
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.registration(&block)
|
18
|
-
yield self if block_given?
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
class AbstractFormatter
|
23
|
-
def format(value)
|
24
|
-
raise DB2Query::Error, "Implement format method in your subclass."
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_String_literal: true
|
2
|
-
|
3
|
-
require "odbc_utf8"
|
4
|
-
|
5
|
-
module DB2Query
|
6
|
-
class ODBCConnector
|
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)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
class AbstractConnector
|
14
|
-
attr_reader :config
|
15
|
-
|
16
|
-
def initialize(config)
|
17
|
-
@config = config
|
18
|
-
end
|
19
|
-
|
20
|
-
def connect
|
21
|
-
raise "abstract method #connect must be defined"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class DsnConnector < AbstractConnector
|
26
|
-
def connect
|
27
|
-
::ODBC.connect(config[:dsn], config[:uid], config[:pwd])
|
28
|
-
rescue ::ODBC::Error => e
|
29
|
-
raise DB2Query::Error, "Unable to activate ODBC DSN connection #{e}"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class ConnStringConnector < AbstractConnector
|
34
|
-
def connect
|
35
|
-
driver = ::ODBC::Driver.new.tap do |d|
|
36
|
-
d.attrs = config[:conn_string].transform_keys(&:to_s)
|
37
|
-
d.name = "odbc"
|
38
|
-
end
|
39
|
-
::ODBC::Database.new.drvconnect(driver)
|
40
|
-
rescue ::ODBC::Error => e
|
41
|
-
raise DB2Query::Error, "Unable to activate ODBC Conn String connection #{e}"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|