db2_query 0.2.2 → 0.3.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +453 -110
  4. data/Rakefile +3 -2
  5. data/lib/db2_query.rb +41 -15
  6. data/lib/db2_query/base.rb +16 -5
  7. data/lib/db2_query/config.rb +20 -17
  8. data/lib/db2_query/core.rb +68 -55
  9. data/lib/db2_query/db_client.rb +56 -0
  10. data/lib/db2_query/db_connection.rb +67 -0
  11. data/lib/db2_query/db_statements.rb +87 -0
  12. data/lib/db2_query/definitions.rb +79 -0
  13. data/lib/db2_query/error.rb +81 -0
  14. data/lib/db2_query/field_type.rb +31 -0
  15. data/lib/db2_query/helper.rb +49 -0
  16. data/lib/db2_query/logger.rb +52 -0
  17. data/lib/db2_query/query.rb +117 -0
  18. data/lib/db2_query/quoting.rb +102 -0
  19. data/lib/db2_query/railtie.rb +5 -7
  20. data/lib/db2_query/result.rb +51 -31
  21. data/lib/db2_query/sql_statement.rb +34 -0
  22. data/lib/db2_query/tasks.rb +29 -0
  23. data/lib/db2_query/tasks/database.rake +2 -50
  24. data/lib/db2_query/tasks/init.rake +1 -1
  25. data/lib/db2_query/tasks/initializer.rake +2 -34
  26. data/lib/db2_query/tasks/templates/database.rb.tt +19 -0
  27. data/lib/db2_query/tasks/templates/initializer.rb.tt +8 -0
  28. data/lib/db2_query/type/binary.rb +19 -0
  29. data/lib/db2_query/type/boolean.rb +41 -0
  30. data/lib/db2_query/type/date.rb +34 -0
  31. data/lib/db2_query/type/decimal.rb +15 -0
  32. data/lib/db2_query/type/integer.rb +15 -0
  33. data/lib/db2_query/type/string.rb +30 -0
  34. data/lib/db2_query/type/text.rb +11 -0
  35. data/lib/db2_query/type/time.rb +30 -0
  36. data/lib/db2_query/type/timestamp.rb +30 -0
  37. data/lib/db2_query/type/value.rb +29 -0
  38. data/lib/db2_query/version.rb +2 -2
  39. data/lib/rails/generators/query/USAGE +15 -0
  40. data/lib/rails/generators/query/query_generator.rb +70 -0
  41. data/lib/rails/generators/query/templates/query.rb.tt +26 -0
  42. data/lib/rails/generators/query/templates/query_definitions.rb.tt +12 -0
  43. data/lib/rails/generators/query/templates/unit_test.rb.tt +9 -0
  44. metadata +62 -49
  45. data/lib/db2_query/connection.rb +0 -163
  46. data/lib/db2_query/connection_handling.rb +0 -112
  47. data/lib/db2_query/database_statements.rb +0 -93
  48. data/lib/db2_query/formatter.rb +0 -27
  49. data/lib/db2_query/odbc_connector.rb +0 -40
@@ -1,163 +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
- 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,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 #: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
-
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) # :nodoc:
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) # :nodoc:
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) # :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
111
- end
112
- end
@@ -1,93 +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, name = nil)
17
- single_value_from_rows(query(sql))
18
- end
19
-
20
- def query_values(sql, name = nil)
21
- query(sql).map(&:first)
22
- end
23
-
24
- def execute(sql, args = [])
25
- if args.empty?
26
- @connection.do(sql)
27
- else
28
- @connection.do(sql, *args)
29
- end
30
- end
31
-
32
- def exec_query(sql, formatter = {}, args = [], name = "SQL")
33
- binds, args = extract_binds_from_sql(sql, args)
34
- log(sql, name, binds, args) do
35
- begin
36
- if args.empty?
37
- stmt = @connection.run(sql)
38
- else
39
- stmt = @connection.run(sql, *args)
40
- end
41
- columns = stmt.columns.values.map { |col| col.name.downcase }
42
- rows = stmt.to_a
43
- ensure
44
- stmt.drop unless stmt.nil?
45
- end
46
- DB2Query::Result.new(columns, rows, formatter)
47
- end
48
- end
49
-
50
- private
51
- def single_value_from_rows(rows)
52
- row = rows.first
53
- row && row.first
54
- end
55
-
56
- def key_finder_regex(k)
57
- /#{k} =\\? | #{k}=\\? | #{k}= \\? /i
58
- end
59
-
60
- def extract_binds_from_sql(sql, args)
61
- question_mark_positions = sql.enum_for(:scan, /\?/i).map { Regexp.last_match.begin(0) }
62
- args = args.first.is_a?(Hash) ? args.first : args.is_a?(Array) ? args : [args]
63
- given, expected = args.length, question_mark_positions.length
64
-
65
- if given != expected
66
- raise ArgumentError, "wrong number of arguments (given #{given}, expected #{expected})"
67
- end
68
-
69
- if args.is_a?(Hash)
70
- binds = args.map do |key, value|
71
- position = sql.enum_for(:scan, key_finder_regex(key)).map { Regexp.last_match.begin(0) }
72
- if position.empty?
73
- raise ArgumentError, "Column name: `#{key}` not found inside sql statement."
74
- elsif position.length > 1
75
- raise ArgumentError, "Can't handle such this kind of sql. Please refactor your sql."
76
- else
77
- index = position[0]
78
- end
79
-
80
- OpenStruct.new({ name: key.to_s, value: value, index: index })
81
- end
82
- binds = binds.sort_by { |bind| bind.index }
83
- [binds.map { |bind| [bind, bind.value] }, binds.map { |bind| bind.value }]
84
- elsif question_mark_positions.length == 1 && args.length == 1
85
- column = sql[/(.*?) = \?|(.*?) =\?|(.*?)= \?|(.*?)=\?/m, 1].split.last.downcase
86
- bind = OpenStruct.new({ name: column, value: args })
87
- [[[bind, bind.value]], bind.value]
88
- else
89
- [args.map { |arg| [nil, arg] }, args]
90
- end
91
- end
92
- end
93
- 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 NotImplementedError, "Implement format method in your subclass."
25
- end
26
- end
27
- end
@@ -1,40 +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
- end
20
-
21
- class DsnConnector < AbstractConnector
22
- def connect
23
- ::ODBC.connect(config[:dsn], config[:uid], config[:pwd])
24
- rescue ::ODBC::Error => e
25
- raise ArgumentError, "Unable to activate ODBC DSN connection #{e}"
26
- end
27
- end
28
-
29
- class ConnStringConnector < AbstractConnector
30
- def connect
31
- driver = ::ODBC::Driver.new.tap do |d|
32
- d.attrs = config[:conn_string].transform_keys(&:to_s)
33
- d.name = "odbc"
34
- end
35
- ::ODBC::Database.new.drvconnect(driver)
36
- rescue ::ODBC::Error => e
37
- raise ArgumentError, "Unable to activate ODBC Conn String connection #{e}"
38
- end
39
- end
40
- end