db2_query 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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