db2_query 0.1.0 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5d2946448234d56484778d59e3051573c81c2789631776f0ed543a79fdc510fa
4
- data.tar.gz: a257fabf1a7de500f7d325c70dd11f494a08163dbf78460f61b3bf51cb626b77
3
+ metadata.gz: fde277f004b8ff05460621968ce473c7f15f047a5903e77c6bf7da997640d8dc
4
+ data.tar.gz: 72fc3599f00909fabc2125964ce5a3f79de9e2343577937206c04c88d7b97cc1
5
5
  SHA512:
6
- metadata.gz: ca8c1b52138ec97d9f4e9cbbca0d8bb148e95f4ac181a929e3e5715b597ed59b70e25b779156ba45a7fe9f119203e4162a2a932c63d11c94acdc2a1eaeb56db8
7
- data.tar.gz: 67f0d7228274d4a7a6e38771cc0743b83da27621d3ce310e0d5d7d323e937316f700ec3b278734312ff27cdc88796321cb09f02d7c2bf48b8c5607a7331a7f27
6
+ metadata.gz: 477d63287ce0a745ba5dfd91c82f8984632976dec003c305f5a888ba54bccec8b5b8a065c12462bb2294dc14f74537d135ed012df9a0a541cb773109c22a6ff9
7
+ data.tar.gz: ab481810cc3cb1ed79bb97594d3af8480e518e7527d770d73606523b5ae35504a4bb6b0ef128ee32f579216604a6b1e43e6230e6e0b0e4fc222a005e086cc513
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
- # Db2Query
1
+ # DB2Query
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/db2_query.svg)](https://badge.fury.io/rb/db2_query)
4
+
2
5
  A Rails query plugin to fetch data from Db2 database by using ODBC connection.
3
6
 
4
7
  ## Installation
@@ -15,7 +18,7 @@ $ bundle
15
18
 
16
19
  Or install it yourself as:
17
20
  ```bash
18
- $ gem install db2-query
21
+ $ gem install db2_query
19
22
  ```
20
23
 
21
24
  ## Initialization
@@ -23,7 +26,7 @@ Execute init task at the app root
23
26
  ```bash
24
27
  $ rake db2query:init
25
28
  ```
26
- Db2Query will generate two required files:
29
+ DB2Query will generate two required files:
27
30
  - `config/db2query_database.yml`
28
31
  - `config/initializers/db2query`
29
32
 
@@ -35,7 +38,8 @@ At `db2query_database.yml` we can use two type of connection:
35
38
  2. Connection String config
36
39
  ```yml
37
40
  development:
38
- primary: # Connection String Example
41
+ primary: # Connection String Example
42
+ adapter: db2_query
39
43
  conn_string:
40
44
  driver: DB2
41
45
  database: SAMPLE
@@ -46,79 +50,76 @@ development:
46
50
  protocol: IPC
47
51
  uid: <%= ENV["DB2EC_UID"] %>
48
52
  pwd: <%= ENV["DB2EC_PWD"] %>
49
- secondary: # DSN Example
50
- dsn: SAMPLE
51
- uid: <%= ENV["DB2EC_UID"] %>
52
- pwd: <%= ENV["DB2EC_PWD"] %>
53
+ secondary:
54
+ adapter: db2_query # DSN Example
55
+ dsn: iseries
56
+ uid: <%= ENV["ISERIES_UID"] %>
57
+ pwd: <%= ENV["ISERIES_PWD"] %>
53
58
  ```
54
59
 
55
60
  ## Usage
56
61
  ### Basic Usage
57
- Create query class that inherit from `Db2Query::Base` in `app/queries` folder
62
+ Create query class that inherit from `DB2Query::Base` in `app/queries` folder
58
63
  ```ruby
59
- class Users < Db2Query::Base
64
+ class User < DB2Query::Base
60
65
  query :find_by, <<-SQL
61
- SELECT * FROM LIBTEST.USERS WHERE user_id = ?
66
+ SELECT * FROM LIBTEST.USERS WHERE id = ?
62
67
  SQL
63
68
  end
64
69
  ```
65
70
  Or use a normal sql method (don't forget the `_sql` suffix)
66
71
  ```ruby
67
- class Users < Db2Query::Base
72
+ class User < DB2Query::Base
68
73
  def find_by_sql
69
- "SELECT * FROM LIBTEST.USERS WHERE user_id = ?"
74
+ "SELECT * FROM LIBTEST.USERS WHERE id = ?"
70
75
  end
71
76
  end
72
77
  ```
73
78
  Check it at rails console
74
79
  ```bash
75
- Users.find_by 10000
76
- Users Load (330.28ms) SELECT * FROM LIBTEST.USERS WHERE user_id = ? [[10000]]
77
- => #<Db2Query::Result [#<Users::FindBy user_id: 10000, first_name: "Alex", last_name: "Jacobi", email: "lula_durgan@dooley.com">]>
80
+ User.find_by 10000
81
+ SQL Load (3.28ms) SELECT * FROM LIBTEST.USERS WHERE user_id = ? [[nil, 10000]]
82
+ => #<DB2Query::Result @records=[#<Record id: 10000, first_name: "Strange", last_name: "Stephen", email: "strange@marvel.universe.com">]>
78
83
  ```
84
+ Or using keywords argument
85
+ ```bash
86
+ User.by_name first_name: "Strange", last_name: "Stephen"
87
+ SQL Load (3.28ms) SELECT * FROM LIBTEST.USERS WHERE first_name = ? AND last_name = ? [["first_name", Strange], ["last_name", Stephen]]
88
+ => #<DB2Query::Result @records=[#<Record id: 10000, first_name: "Strange", last_name: "Stephen", email: "strange@marvel.universe.com">]>
89
+ ```
90
+
79
91
  ### Formatter
80
- In order to get different result column format, a query result can be reformatted by add a formatter class that inherit `Db2Query::AbstractFormatter` then register at `config\initializers\db2query.rb`
92
+ In order to get different result column format, a query result can be reformatted by add a formatter class that inherit `DB2Query::AbstractFormatter` then register at `config\initializers\db2query.rb`
81
93
  ```ruby
82
94
  require "db2_query/formatter"
83
95
 
84
96
  # create a formatter class
85
- class FirstNameFormatter < Db2Query::AbstractFormatter
97
+ class FirstNameFormatter < DB2Query::AbstractFormatter
86
98
  def format(value)
87
- "Mr/Mrs. " + value
99
+ "Dr." + value
88
100
  end
89
101
  end
90
102
 
91
103
  # register the formatter class
92
- Db2Query::Formatter.registration do |format|
104
+ DB2Query::Formatter.registration do |format|
93
105
  format.register(:first_name_formatter, FirstNameFormatter)
94
106
  end
95
107
  ```
96
108
  Use it at query class
97
109
  ```ruby
98
- class Users < Db2Query::Base
110
+ class Doctor < User
99
111
  attributes :first_name, :first_name_formatter
100
-
101
- query :find_by, <<-SQL
102
- SELECT * FROM LIBTEST.USERS WHERE user_id = ?
103
- SQL
104
112
  end
105
113
  ```
106
114
  Check it at rails console
107
115
  ```bash
108
- Users.find_by 10000
109
- Users Load (330.28ms) SELECT * FROM LIBTEST.USERS WHERE user_id = ? [[10000]]
110
- => #<Db2Query::Result [#<Users::FindBy user_id: 10000, first_name: "Mr/Mrs. Alex", last_name: "Jacobi", email: "lula_durgan@dooley.com">]>
116
+ Doctor.find_by id: 10000
117
+ SQL Load (30.28ms) SELECT * FROM LIBTEST.USERS WHERE user_id = ? [["id", 10000]]
118
+ => #<DB2Query::Result @records=[#<Record id: 10000, first_name: "Dr.Strange", last_name: "Stephen", email: "strange@marvel.universe.com">]>
111
119
  ```
112
- ### Available methods
113
- Db2Query::Result has public methods as follows:
114
- - to_a
115
- - to_hash
116
- - pluck
117
- - first
118
- - last
119
- - size
120
- - each
121
120
 
121
+ ### Available methods
122
+ `DB2Query::Result` inherit all `ActiveRecord::Result` methods with additional custom `records` method.
122
123
 
123
124
  ## License
124
125
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,29 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require "bundler/setup"
5
- rescue LoadError
6
- puts "You must `gem install bundler` and `bundle install` to run rake tasks"
7
- end
8
-
9
- require "rdoc/task"
10
-
11
- RDoc::Task.new(:rdoc) do |rdoc|
12
- rdoc.rdoc_dir = "rdoc"
13
- rdoc.title = "Db2Query"
14
- rdoc.options << "--line-numbers"
15
- rdoc.rdoc_files.include("README.md")
16
- rdoc.rdoc_files.include("lib/**/*.rb")
17
- end
18
-
19
3
  require "bundler/gem_tasks"
20
-
21
4
  require "rake/testtask"
22
5
 
23
6
  Rake::TestTask.new(:test) do |t|
24
7
  t.libs << "test"
25
- t.pattern = "test/**/*_test.rb"
26
- t.verbose = false
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
27
10
  end
28
11
 
29
12
  task default: :test
@@ -0,0 +1,203 @@
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
@@ -1,29 +1,23 @@
1
1
  # frozen_string_literal:true
2
2
 
3
- require "odbc"
4
3
  require "yaml"
5
4
  require "erb"
6
5
  require "active_record"
7
6
  require "active_support"
7
+ require "active_record/database_configurations"
8
+ require "db2_query/config"
8
9
 
9
- module Db2Query
10
+ module DB2Query
10
11
  extend ActiveSupport::Autoload
11
12
 
12
13
  autoload :Version
13
- autoload :Error
14
- autoload :Path
15
- autoload :Schema
16
- autoload :DatabaseConfigurations
17
- autoload :ODBCConnector
18
- autoload :Connection
14
+ autoload :Base
15
+ autoload :Core
19
16
  autoload :ConnectionHandling
20
17
  autoload :DatabaseStatements
21
- autoload :SQLValidator
22
- autoload :LogSubscriber
18
+ autoload :ODBCConnector
23
19
  autoload :Formatter
24
- autoload :Column
25
20
  autoload :Result
26
- autoload :Base
27
- end
28
21
 
29
- require "db2_query/railtie"
22
+ require "db2_query/railtie" if defined?(Rails)
23
+ end
@@ -1,84 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Db2Query
3
+ module DB2Query
4
4
  class Base
5
- include DatabaseConfigurations
6
- include ConnectionHandling
7
-
8
- class << self
9
- include SQLValidator
10
-
11
- def attributes(attr_name, format)
12
- attr_format.store(attr_name, format)
13
- end
14
-
15
- def query(method_name, sql_statement)
16
- unless is_query?(sql_statement)
17
- raise Error, "Query only for SQL query commands."
18
- end
19
-
20
- if self.class.respond_to?(method_name)
21
- raise Error, "Query :#{method_name} has been defined before"
22
- end
23
-
24
- self.class.define_method(method_name) do |*args|
25
- log(sql_statement, args) do
26
- columns, rows = connection.exec_query(sql_statement, *args)
27
- Result.new(self, method_name, columns, rows, attr_format)
28
- end
29
- end
30
- end
31
-
32
- private
33
- def attr_format
34
- @attr_format ||= Hash.new
35
- end
36
-
37
- def define_query_method(method_name, sql_statement)
38
- if is_query?(sql_statement)
39
- query(method_name, sql_statement)
40
- else
41
- raise NotImplementedError
42
- end
43
- end
44
-
45
- def method_missing(method_name, *args, &block)
46
- sql_methods = self.instance_methods.grep(/_sql/)
47
- sql_method = "#{method_name}_sql".to_sym
48
-
49
- if sql_methods.include?(sql_method)
50
- sql_statement = allocate.method(sql_method).call
51
-
52
- raise Error, "Query methods must return a SQL statement string!" unless sql_statement.is_a? String
53
-
54
- expected_args = sql_statement.count "?"
55
- given_args = args.size
56
-
57
- if expected_args == given_args
58
- define_query_method(method_name, sql_statement)
59
- else
60
- raise ArgumentError, "wrong number of arguments (given #{given_args}, expected #{expected_args})"
61
- end
62
-
63
- method(method_name).call(*args)
64
- else
65
- super
66
- end
67
- end
68
-
69
- def instrumenter
70
- @instrumenter ||= ActiveSupport::Notifications.instrumenter
71
- end
72
-
73
- def log(sql_statement, args)
74
- instrumenter.instrument(
75
- "sql.db2_query",
76
- sql: sql_statement,
77
- name: self,
78
- binds: args) do
79
- yield
80
- end
81
- end
82
- end
5
+ include DB2Query::Core
6
+ include ActiveRecord::Inheritance
7
+ extend ActiveSupport::Concern
8
+ extend ActiveRecord::ConnectionHandling
9
+ extend DB2Query::ConnectionHandling
83
10
  end
84
11
  end