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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +472 -124
  4. data/Rakefile +3 -2
  5. data/lib/db2_query/base.rb +15 -5
  6. data/lib/db2_query/config.rb +20 -17
  7. data/lib/db2_query/core.rb +79 -60
  8. data/lib/db2_query/db_client.rb +56 -0
  9. data/lib/db2_query/db_connection.rb +68 -0
  10. data/lib/db2_query/db_statements.rb +87 -0
  11. data/lib/db2_query/definitions.rb +93 -0
  12. data/lib/db2_query/error.rb +72 -7
  13. data/lib/db2_query/field_type.rb +31 -0
  14. data/lib/db2_query/helper.rb +50 -0
  15. data/lib/db2_query/logger.rb +52 -0
  16. data/lib/db2_query/query.rb +128 -0
  17. data/lib/db2_query/railtie.rb +5 -10
  18. data/lib/db2_query/result.rb +51 -31
  19. data/lib/db2_query/sql_statement.rb +34 -0
  20. data/lib/db2_query/tasks/database.rake +2 -46
  21. data/lib/db2_query/tasks/init.rake +1 -1
  22. data/lib/db2_query/tasks/initializer.rake +2 -34
  23. data/lib/db2_query/tasks/templates/database.rb.tt +19 -0
  24. data/lib/db2_query/tasks/templates/initializer.rb.tt +8 -0
  25. data/lib/db2_query/tasks.rb +29 -0
  26. data/lib/db2_query/type/binary.rb +19 -0
  27. data/lib/db2_query/type/boolean.rb +41 -0
  28. data/lib/db2_query/type/date.rb +34 -0
  29. data/lib/db2_query/type/decimal.rb +15 -0
  30. data/lib/db2_query/type/integer.rb +15 -0
  31. data/lib/db2_query/type/string.rb +30 -0
  32. data/lib/db2_query/type/text.rb +11 -0
  33. data/lib/db2_query/type/time.rb +30 -0
  34. data/lib/db2_query/type/timestamp.rb +30 -0
  35. data/lib/db2_query/type/value.rb +29 -0
  36. data/lib/db2_query/version.rb +2 -2
  37. data/lib/db2_query.rb +42 -18
  38. data/lib/rails/generators/query/USAGE +15 -0
  39. data/lib/rails/generators/query/query_generator.rb +70 -0
  40. data/lib/rails/generators/query/templates/query.rb.tt +26 -0
  41. data/lib/rails/generators/query/templates/query_definitions.rb.tt +18 -0
  42. data/lib/rails/generators/query/templates/unit_test.rb.tt +9 -0
  43. metadata +74 -36
  44. data/lib/db2_query/bind.rb +0 -6
  45. data/lib/db2_query/connection.rb +0 -164
  46. data/lib/db2_query/connection_handling.rb +0 -112
  47. data/lib/db2_query/database_statements.rb +0 -89
  48. data/lib/db2_query/formatter.rb +0 -27
  49. data/lib/db2_query/odbc_connector.rb +0 -44
@@ -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
@@ -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