db2_query 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5d2946448234d56484778d59e3051573c81c2789631776f0ed543a79fdc510fa
4
+ data.tar.gz: a257fabf1a7de500f7d325c70dd11f494a08163dbf78460f61b3bf51cb626b77
5
+ SHA512:
6
+ metadata.gz: ca8c1b52138ec97d9f4e9cbbca0d8bb148e95f4ac181a929e3e5715b597ed59b70e25b779156ba45a7fe9f119203e4162a2a932c63d11c94acdc2a1eaeb56db8
7
+ data.tar.gz: 67f0d7228274d4a7a6e38771cc0743b83da27621d3ce310e0d5d7d323e937316f700ec3b278734312ff27cdc88796321cb09f02d7c2bf48b8c5607a7331a7f27
@@ -0,0 +1,20 @@
1
+ Copyright 2020 yohanes_l
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,124 @@
1
+ # Db2Query
2
+ A Rails query plugin to fetch data from Db2 database by using ODBC connection.
3
+
4
+ ## Installation
5
+ Add this line to your application's Gemfile:
6
+
7
+ ```ruby
8
+ gem 'db2_query'
9
+ ```
10
+
11
+ And then execute:
12
+ ```bash
13
+ $ bundle
14
+ ```
15
+
16
+ Or install it yourself as:
17
+ ```bash
18
+ $ gem install db2-query
19
+ ```
20
+
21
+ ## Initialization
22
+ Execute init task at the app root
23
+ ```bash
24
+ $ rake db2query:init
25
+ ```
26
+ Db2Query will generate two required files:
27
+ - `config/db2query_database.yml`
28
+ - `config/initializers/db2query`
29
+
30
+ Edit these files according to the requirement.
31
+
32
+ ### Database Configuration
33
+ At `db2query_database.yml` we can use two type of connection:
34
+ 1. DSN connection config
35
+ 2. Connection String config
36
+ ```yml
37
+ development:
38
+ primary: # Connection String Example
39
+ conn_string:
40
+ driver: DB2
41
+ database: SAMPLE
42
+ dbalias: SAMPLE
43
+ hostname: LOCALHOST
44
+ currentschema: LIBTEST
45
+ port: "0"
46
+ protocol: IPC
47
+ uid: <%= ENV["DB2EC_UID"] %>
48
+ pwd: <%= ENV["DB2EC_PWD"] %>
49
+ secondary: # DSN Example
50
+ dsn: SAMPLE
51
+ uid: <%= ENV["DB2EC_UID"] %>
52
+ pwd: <%= ENV["DB2EC_PWD"] %>
53
+ ```
54
+
55
+ ## Usage
56
+ ### Basic Usage
57
+ Create query class that inherit from `Db2Query::Base` in `app/queries` folder
58
+ ```ruby
59
+ class Users < Db2Query::Base
60
+ query :find_by, <<-SQL
61
+ SELECT * FROM LIBTEST.USERS WHERE user_id = ?
62
+ SQL
63
+ end
64
+ ```
65
+ Or use a normal sql method (don't forget the `_sql` suffix)
66
+ ```ruby
67
+ class Users < Db2Query::Base
68
+ def find_by_sql
69
+ "SELECT * FROM LIBTEST.USERS WHERE user_id = ?"
70
+ end
71
+ end
72
+ ```
73
+ Check it at rails console
74
+ ```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">]>
78
+ ```
79
+ ### 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`
81
+ ```ruby
82
+ require "db2_query/formatter"
83
+
84
+ # create a formatter class
85
+ class FirstNameFormatter < Db2Query::AbstractFormatter
86
+ def format(value)
87
+ "Mr/Mrs. " + value
88
+ end
89
+ end
90
+
91
+ # register the formatter class
92
+ Db2Query::Formatter.registration do |format|
93
+ format.register(:first_name_formatter, FirstNameFormatter)
94
+ end
95
+ ```
96
+ Use it at query class
97
+ ```ruby
98
+ class Users < Db2Query::Base
99
+ attributes :first_name, :first_name_formatter
100
+
101
+ query :find_by, <<-SQL
102
+ SELECT * FROM LIBTEST.USERS WHERE user_id = ?
103
+ SQL
104
+ end
105
+ ```
106
+ Check it at rails console
107
+ ```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">]>
111
+ ```
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
+
122
+
123
+ ## License
124
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
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
+ require "bundler/gem_tasks"
20
+
21
+ require "rake/testtask"
22
+
23
+ Rake::TestTask.new(:test) do |t|
24
+ t.libs << "test"
25
+ t.pattern = "test/**/*_test.rb"
26
+ t.verbose = false
27
+ end
28
+
29
+ task default: :test
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal:true
2
+
3
+ require "odbc"
4
+ require "yaml"
5
+ require "erb"
6
+ require "active_record"
7
+ require "active_support"
8
+
9
+ module Db2Query
10
+ extend ActiveSupport::Autoload
11
+
12
+ autoload :Version
13
+ autoload :Error
14
+ autoload :Path
15
+ autoload :Schema
16
+ autoload :DatabaseConfigurations
17
+ autoload :ODBCConnector
18
+ autoload :Connection
19
+ autoload :ConnectionHandling
20
+ autoload :DatabaseStatements
21
+ autoload :SQLValidator
22
+ autoload :LogSubscriber
23
+ autoload :Formatter
24
+ autoload :Column
25
+ autoload :Result
26
+ autoload :Base
27
+ end
28
+
29
+ require "db2_query/railtie"
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
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
83
+ end
84
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class Column
5
+ attr_reader :name, :type, :formatter
6
+
7
+ FLOAT_TYPES = [ODBC::SQL_FLOAT, ODBC::SQL_DOUBLE, ODBC::SQL_DECIMAL, ODBC::SQL_REAL]
8
+ INTEGER_TYPES = [ODBC::SQL_TINYINT, ODBC::SQL_SMALLINT, ODBC::SQL_INTEGER, ODBC::SQL_BIGINT, ODBC::SQL_NUMERIC]
9
+
10
+ def initialize(name, type, format = nil)
11
+ @name = name
12
+ @type = type.to_i
13
+
14
+ @formatter = if custom_format?(format)
15
+ Formatter.lookup(format)
16
+ elsif float_type?
17
+ FloatFormatter.new
18
+ elsif integer_type?
19
+ IntegerFormatter.new
20
+ else
21
+ BareFormatter.new
22
+ end
23
+ end
24
+
25
+ def format(value)
26
+ formatter.format(value)
27
+ end
28
+
29
+ private
30
+ def custom_format?(format)
31
+ Formatter.registry.key?(format)
32
+ end
33
+
34
+ def float_type?
35
+ FLOAT_TYPES.include?(type)
36
+ end
37
+
38
+ def integer_type?
39
+ INTEGER_TYPES.include?(type)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class Connection
5
+ include DatabaseStatements
6
+
7
+ attr_reader :connector, :db_name, :odbc_conn
8
+
9
+ def initialize(connector, db_name)
10
+ @connector = connector
11
+ @db_name = db_name.to_sym
12
+ connect
13
+ end
14
+
15
+ def connect
16
+ @odbc_conn = connector.connect
17
+ @odbc_conn.use_time = true
18
+ end
19
+
20
+ def active?
21
+ @odbc_conn.connected?
22
+ end
23
+
24
+ def disconnect!
25
+ @odbc_conn.drop_all
26
+ @odbc_conn.disconnect if active?
27
+ end
28
+
29
+ def reconnect!
30
+ disconnect!
31
+ connect
32
+ end
33
+ alias reset! reconnect!
34
+
35
+ def execute(sql, *args)
36
+ reset! unless active?
37
+ @odbc_conn.do(sql, *args)
38
+ end
39
+
40
+ def exec_query(sql, *args)
41
+ reset! unless active?
42
+ statement = @odbc_conn.run(sql, *args)
43
+ columns = statement.columns.values
44
+ rows = statement.to_a
45
+ [columns, rows]
46
+ ensure
47
+ statement.drop if statement
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module ConnectionHandling
5
+ extend ActiveSupport::Concern
6
+
7
+ DEFAULT_DB = "primary"
8
+
9
+ included do |base|
10
+ def base.inherited(child)
11
+ child.connection = @connection
12
+ end
13
+
14
+ base.extend ClassMethods
15
+ base.connection = nil
16
+ end
17
+
18
+ module ClassMethods
19
+ attr_reader :connection
20
+
21
+ def connection=(connection)
22
+ @connection = connection
23
+ update_descendants_connection unless self.descendants.empty?
24
+ end
25
+
26
+ def update_descendants_connection
27
+ self.descendants.each { |child| child.connection = @connection }
28
+ end
29
+
30
+ def establish_connection(db_name = nil)
31
+ clear_connection unless self.connection.nil?
32
+ db_name = db_name.nil? ? DEFAULT_DB : db_name.to_s
33
+
34
+ self.load_database_configurations if self.configurations.nil?
35
+
36
+ if self.configurations[db_name].nil?
37
+ raise Error, "Database (:#{db_name}) not found at database configurations."
38
+ end
39
+
40
+ conn_type, conn_config = extract_configuration(db_name)
41
+
42
+ connector = ODBCConnector.new(conn_type, conn_config)
43
+ self.connection = Connection.new(connector, db_name)
44
+ end
45
+
46
+ def current_database
47
+ @connection.db_name
48
+ end
49
+
50
+ def clear_connection
51
+ @connection.disconnect!
52
+ @connection = nil
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module DatabaseConfigurations
5
+ extend ActiveSupport::Concern
6
+
7
+ DEFAULT_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence }
8
+
9
+ included do |base|
10
+ @@configurations = Hash.new
11
+
12
+ base.extend ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ def configurations
17
+ @@configurations
18
+ end
19
+
20
+ def load_database_configurations(path = nil)
21
+ file_path = path.nil? ? Path.database_config_file : path
22
+
23
+ if File.exist?(file_path)
24
+ file = File.read(file_path)
25
+ @@configurations = YAML.load(ERB.new(file).result)[DEFAULT_ENV.call]
26
+ else
27
+ raise Error, "Could not load db2query database configuration. No such file - #{file_path}"
28
+ end
29
+ end
30
+
31
+ def configurations_databases
32
+ self.load_database_configurations if self.configurations.nil?
33
+ @@configurations.keys
34
+ end
35
+
36
+ private
37
+ def extract_configuration(db_name)
38
+ configs = @@configurations[db_name.to_s]
39
+ conn_config = configs.nil? ? configs : configs.transform_keys(&:to_sym)
40
+ conn_type = (conn_config.keys & ODBCConnector::CONNECTION_TYPES).first
41
+
42
+ if conn_type.nil?
43
+ raise Error, "No data source name (:dsn) or connection string (:conn_str) provided."
44
+ end
45
+
46
+ [conn_type, conn_config]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module DatabaseStatements
5
+ def query_value(sql) # :nodoc:
6
+ single_value_from_rows(query(sql))
7
+ end
8
+
9
+ def query(sql)
10
+ exec_query(sql).last
11
+ end
12
+
13
+ def query_values(sql)
14
+ query(sql).map(&:first)
15
+ end
16
+
17
+ def current_database
18
+ db_name.to_s
19
+ end
20
+
21
+ def current_schema
22
+ query_value("select current_schema from sysibm.sysdummy1").strip
23
+ end
24
+ alias library current_schema
25
+
26
+ private
27
+ def single_value_from_rows(rows)
28
+ row = rows.first
29
+ row && row.first
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class Error < ArgumentError; end
5
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module Formatter
5
+ def self.register(name, klass)
6
+ self.registry.store(name, klass.new)
7
+ end
8
+
9
+ def self.registry
10
+ @@registry ||= Hash.new
11
+ end
12
+
13
+ def self.lookup(name)
14
+ @@registry.fetch(name.to_sym)
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
+
28
+ class BareFormatter < AbstractFormatter
29
+ def format(value)
30
+ value
31
+ end
32
+ end
33
+
34
+ class FloatFormatter < AbstractFormatter
35
+ def format(value)
36
+ value.to_f
37
+ end
38
+ end
39
+
40
+ class IntegerFormatter < AbstractFormatter
41
+ def format(value)
42
+ value.to_i
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class LogSubscriber < ActiveSupport::LogSubscriber
5
+ # Embed in a String to clear all previous ANSI sequences.
6
+ CLEAR = "\e[0m"
7
+ BOLD = "\e[1m"
8
+
9
+ # Colors
10
+ BLACK = "\e[30m"
11
+ RED = "\e[31m"
12
+ GREEN = "\e[32m"
13
+ YELLOW = "\e[33m"
14
+ BLUE = "\e[34m"
15
+ MAGENTA = "\e[35m"
16
+ CYAN = "\e[36m"
17
+ WHITE = "\e[37m"
18
+
19
+ def sql(event)
20
+ class_load_duration = color("#{event.payload[:name]} Load (#{event.duration.round(2)}ms)", :cyan, true)
21
+ sql_statement = color("#{event.payload[:sql]}", :blue, true)
22
+ message = " #{class_load_duration} #{sql_statement}"
23
+
24
+ if event.payload[:binds].size > 0
25
+ binds = color("#{event.payload[:binds]}", :white)
26
+ message = "#{message} [#{binds}]"
27
+ end
28
+
29
+ puts message
30
+ end
31
+
32
+ def schema_task(event)
33
+ puts color(" Done (#{(event.duration).round(2)}ms)", :green, true)
34
+ end
35
+
36
+ def schema_task_perform(event)
37
+ task_name = color(":#{event.payload[:task_name]}", :white, true)
38
+ schema = color("#{event.payload[:schema]}", :white, true)
39
+ puts "- Performing #{task_name} in #{schema} ..."
40
+ end
41
+
42
+ private
43
+ def color(text, color, bold = false) # :doc:
44
+ return text unless colorize_logging
45
+ color = self.class.const_get(color.upcase) if color.is_a?(Symbol)
46
+ bold = bold ? BOLD : ""
47
+ "#{bold}#{color}#{text}#{CLEAR}"
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class ODBCConnector
5
+ attr_reader :connector, :conn_type, :conn_config
6
+
7
+ CONNECTION_TYPES = %i[dsn conn_string].freeze
8
+
9
+ def initialize(type, config)
10
+ @conn_type, @conn_config = type, config.transform_keys(&:to_sym)
11
+ @connector = Db2Query.const_get("#{conn_type.to_s.camelize}Connector").new
12
+ end
13
+
14
+ def connect
15
+ connector.connect(conn_config)
16
+ end
17
+ end
18
+
19
+ class DsnConnector
20
+ def connect(config)
21
+ ::ODBC.connect(config[:dsn], config[:uid], config[:pwd])
22
+ rescue ::ODBC::Error => e
23
+ raise Error, "Unable to activate ODBC DSN connection #{e}"
24
+ end
25
+ end
26
+
27
+ class ConnStringConnector
28
+ def connect(config)
29
+ driver = ::ODBC::Driver.new.tap do |d|
30
+ d.attrs = config[:conn_string].transform_keys(&:to_s)
31
+ d.name = "odbc"
32
+ end
33
+ ::ODBC::Database.new.drvconnect(driver)
34
+ rescue ::ODBC::Error => e
35
+ raise Error, "Unable to activate ODBC Conn String connection #{e}"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module Path
5
+ def self.database_config_file
6
+ @@database_config_file ||= nil
7
+ end
8
+
9
+ def self.database_config_file=(file_path)
10
+ @@database_config_file ||= file_path
11
+ end
12
+
13
+ def self.database_config_file_exists?
14
+ File.exist?(self.database_config_file)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class Railtie < ::Rails::Railtie
5
+ railtie_name :db2_query
6
+
7
+ rake_tasks do
8
+ tasks_path = "#{File.expand_path("..", __dir__)}/tasks"
9
+ Dir.glob("#{tasks_path}/*.rake").each { |file| load file }
10
+ end
11
+
12
+ initializer "db2_query.database_initialization" do
13
+ Path.database_config_file = "#{Rails.root}/config/db2query_database.yml"
14
+ Base.load_database_configurations
15
+ Base.establish_connection
16
+ end
17
+
18
+ initializer "db2_query.attach_log_subscription" do
19
+ LogSubscriber.attach_to :db2_query
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class Result
5
+ attr_reader :core, :klass, :rows, :columns, :column_metadatas, :attr_format
6
+
7
+ def initialize(core, klass, columns, rows, attr_format = {})
8
+ @core = core
9
+ @klass = klass.to_s.camelize
10
+ @rows = rows
11
+ @attr_format = attr_format
12
+ @columns = []
13
+ @column_metadatas = extract_metadatas(columns)
14
+ end
15
+
16
+ def to_a
17
+ core.const_set(klass, row_class) unless core.const_defined?(klass)
18
+
19
+ rows.map do |row|
20
+ (core.const_get klass).new(row, column_metadatas)
21
+ end
22
+ end
23
+
24
+ def to_hash
25
+ rows.map do |row|
26
+ Hash[columns.zip(row)]
27
+ end
28
+ end
29
+
30
+ def pluck(*column_names)
31
+ records.map do |record|
32
+ column_names.map { |column_name| record.send(column_name) }
33
+ end
34
+ end
35
+
36
+ def first
37
+ records.first
38
+ end
39
+
40
+ def last
41
+ records.last
42
+ end
43
+
44
+ def size
45
+ records.size
46
+ end
47
+ alias length size
48
+
49
+ def each(&block)
50
+ records.each(&block)
51
+ end
52
+
53
+ def inspect
54
+ entries = records.take(11).map!(&:inspect)
55
+ entries[10] = "..." if entries.size == 11
56
+ "#<#{self.class} [#{entries.join(', ')}]>"
57
+ end
58
+
59
+ private
60
+ def records
61
+ @records ||= to_a
62
+ end
63
+
64
+ def extract_metadatas(columns)
65
+ columns.map do |col|
66
+ @columns << column_name = col.name.downcase
67
+ Column.new(column_name, col.type, attr_format[column_name.to_sym])
68
+ end
69
+ end
70
+
71
+ def row_class
72
+ Class.new do
73
+ def initialize(row, columns_metadata)
74
+ columns_metadata.zip(row) do |column, val|
75
+ self.class.send(:attr_accessor, column.name.to_sym)
76
+ instance_variable_set("@#{column.name}", column.format(val))
77
+ end
78
+ end
79
+
80
+ def inspect
81
+ inspection = if defined?(instance_variables) && instance_variables
82
+ instance_variables.collect do |attr|
83
+ value = instance_variable_get(attr)
84
+ "#{attr[1..-1]}: #{(value.kind_of? String) ? %Q{"#{value}"} : value}"
85
+ end.compact.join(", ")
86
+ else
87
+ "not initialized"
88
+ end
89
+
90
+ "#<#{self.class} #{inspection}>"
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class Schema
5
+ include SQLValidator
6
+
7
+ attr_accessor :config, :current_schema
8
+
9
+ def initialize
10
+ @config = Config.new
11
+ end
12
+
13
+ def connection
14
+ Base.connection
15
+ end
16
+
17
+ def schema
18
+ config.schema
19
+ end
20
+
21
+ def sql_files_dir(path)
22
+ config.sql_files_dir = path
23
+ end
24
+
25
+ def is_valid_schema?
26
+ connection.current_schema == config.schema
27
+ end
28
+
29
+ def perform_tasks(&block)
30
+ raise Error, "#{config.main_library} is not connection's current schema" unless is_valid_schema?
31
+
32
+ if Base.connection.nil? && config.init_connection
33
+ Base.establish_connection
34
+ end
35
+
36
+ puts "\n# Perform Schema Tasks: \n\n"
37
+
38
+ instance_eval(&block)
39
+
40
+ puts "\n"
41
+ end
42
+
43
+ def sql(sql_statement)
44
+ raise Error, "Task only for SQL execute commands." if query_command?(sql_statement)
45
+ sql_statement
46
+ end
47
+
48
+ def file(file_name)
49
+ sql(File.read("#{config.sql_files_dir}/#{file_name}"))
50
+ end
51
+
52
+ def task(task_name, sql = nil, *args)
53
+ Task.new(schema, task_name).perform do
54
+ if block_given?
55
+ yield(sql)
56
+ else
57
+ execute(sql, *args)
58
+ end
59
+ end
60
+ end
61
+
62
+ def execute(sql, *args)
63
+ connection.execute(sql, *args)
64
+ rescue ::ODBC::Error => e
65
+ raise Error, "Unable to execute SQL - #{e}"
66
+ end
67
+
68
+ def tables_in_schema
69
+ connection.query_values <<-SQL
70
+ SELECT table_name FROM SYSIBM.SQLTABLES
71
+ WHERE table_schem='#{schema}' AND table_type='TABLE'
72
+ SQL
73
+ end
74
+
75
+ def self.initiation(&block)
76
+ new.initiation(&block)
77
+ end
78
+
79
+ def initiation(&block)
80
+ instance_eval(&block)
81
+ end
82
+
83
+ class Task
84
+ attr_reader :instrumenter, :schema, :task_name, :start_time, :finish_time
85
+
86
+ def initialize(schema, task_name)
87
+ @schema = schema
88
+ @task_name = task_name
89
+ @instrumenter = ActiveSupport::Notifications.instrumenter
90
+ end
91
+
92
+ def perform
93
+ instrumenter.instrument("schema_task_perform.db2_query", payload)
94
+ instrumenter.start("schema_task.db2_query", payload)
95
+ yield
96
+ instrumenter.finish("schema_task.db2_query", payload)
97
+ end
98
+
99
+ private
100
+ def payload
101
+ { task_name: task_name, schema: schema }
102
+ end
103
+ end
104
+
105
+ class Config
106
+ attr_accessor :initial_tasks, :init_connection, :schema, :sql_files_dir
107
+
108
+ def initialize
109
+ @init_connection = false
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module SQLValidator
5
+ DEFAULT_TASK_COMMANDS = [:create, :drop, :delete, :insert, :set, :update]
6
+ COMMENT_REGEX = %r{/\*(?:[^\*]|\*[^/])*\*/}m
7
+
8
+ def task_command?(sql_statement)
9
+ sql_statement.match?(task_commands_regexp)
10
+ end
11
+
12
+ def query_command?(sql_statement)
13
+ sql_statement.match?(/select/i)
14
+ end
15
+
16
+ def is_query?(sql_statement)
17
+ query_command?(sql_statement) && !task_command?(sql_statement)
18
+ end
19
+
20
+ private
21
+ def task_commands_regexp
22
+ parts = DEFAULT_TASK_COMMANDS.map { |part| /#{part}/i }
23
+ /\A(?:[\(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/i
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ DB2_QUERY_DATABASE_TEMPLATE ||= <<-EOF
4
+ # frozen_string_literal: true
5
+
6
+ # Database configuration example
7
+ development:
8
+ primary:
9
+ conn_string:
10
+ driver: DB2
11
+ database: SAMPLE
12
+ dbalias: SAMPLE
13
+ hostname: LOCALHOST
14
+ currentschema: LIBTEST
15
+ port: "0"
16
+ protocol: IPC
17
+ uid: <%= ENV["DB2EC_UID"] %>
18
+ pwd: <%= ENV["DB2EC_PWD"] %>
19
+ secondary:
20
+ dsn: SAMPLE
21
+ uid: <%= ENV["DB2EC_UID"] %>
22
+ pwd: <%= ENV["DB2EC_PWD"] %>
23
+
24
+ test:
25
+ primary:
26
+ conn_string:
27
+ driver: DB2
28
+ database: SAMPLE
29
+ dbalias: SAMPLE
30
+ hostname: LOCALHOST
31
+ currentschema: LIBTEST
32
+ port: "0"
33
+ protocol: IPC
34
+ uid: <%= ENV["DB2EC_UID"] %>
35
+ pwd: <%= ENV["DB2EC_PWD"] %>
36
+ secondary:
37
+ dsn: SAMPLE
38
+ uid: <%= ENV["DB2EC_UID"] %>
39
+ pwd: <%= ENV["DB2EC_PWD"] %>
40
+ EOF
41
+
42
+ namespace :db2query do
43
+ desc "Create Database configuration file"
44
+ task :database do
45
+ database_path = "#{Rails.root}/config/db2query_database.yml"
46
+ if File.exist?(database_path)
47
+ raise ArgumentError, "File exists."
48
+ else
49
+ puts " Creating database config file ..."
50
+ File.open(database_path, "w") do |file|
51
+ file.puts DB2_QUERY_DATABASE_TEMPLATE
52
+ end
53
+ puts " File '#{database_path}' created."
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :db2query do
4
+ desc "Create Initializer and Database configuration file"
5
+ task :init do
6
+ Rake::Task["db2query:initializer"].invoke
7
+ Rake::Task["db2query:database"].invoke
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ DB2_QUERY_INITIALIZER_TEMPLATE ||= <<-EOF
4
+ # frozen_string_literal: true
5
+
6
+ # Example
7
+ require "db2_query/formatter"
8
+
9
+ class FirstNameFormatter < Db2Query::AbstractFormatter
10
+ def format(value)
11
+ "First Name: " + value
12
+ end
13
+ end
14
+
15
+ Db2Query::Formatter.registration do |format|
16
+ format.register(:first_name_formatter, FirstNameFormatter)
17
+ end
18
+ EOF
19
+
20
+ namespace :db2query do
21
+ desc "Create Initializer file"
22
+ task :initializer do
23
+ # Create initializer file
24
+ initializer_path = "#{Rails.root}/config/initializers/db2query.rb"
25
+ if File.exist?(initializer_path)
26
+ raise ArgumentError, "File exists."
27
+ else
28
+ puts " Creating initializer file ..."
29
+ File.open(initializer_path, "w") do |file|
30
+ file.puts DB2_QUERY_INITIALIZER_TEMPLATE
31
+ end
32
+ puts " File '#{initializer_path}' created."
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,199 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: db2_query
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - yohanes_l
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.3
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 6.0.3.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 6.0.3
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 6.0.3.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: ruby-odbc
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.99999'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.99999'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rubocop
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rubocop-performance
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rubocop-rails
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: faker
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: dotenv
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: byebug
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: sqlite3
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ description: A Rails query plugin to fetch data from Db2 database by using ODBC connection.
146
+ email:
147
+ - yohanes.lumentut@yahoo.com
148
+ executables: []
149
+ extensions: []
150
+ extra_rdoc_files: []
151
+ files:
152
+ - MIT-LICENSE
153
+ - README.md
154
+ - Rakefile
155
+ - lib/db2_query.rb
156
+ - lib/db2_query/base.rb
157
+ - lib/db2_query/column.rb
158
+ - lib/db2_query/connection.rb
159
+ - lib/db2_query/connection_handling.rb
160
+ - lib/db2_query/database_configurations.rb
161
+ - lib/db2_query/database_statements.rb
162
+ - lib/db2_query/error.rb
163
+ - lib/db2_query/formatter.rb
164
+ - lib/db2_query/log_subscriber.rb
165
+ - lib/db2_query/odbc_connector.rb
166
+ - lib/db2_query/path.rb
167
+ - lib/db2_query/railtie.rb
168
+ - lib/db2_query/result.rb
169
+ - lib/db2_query/schema.rb
170
+ - lib/db2_query/sql_validator.rb
171
+ - lib/db2_query/version.rb
172
+ - lib/tasks/database.rake
173
+ - lib/tasks/init.rake
174
+ - lib/tasks/initializer.rake
175
+ homepage: https://github.com/yohaneslumentut/db2_query
176
+ licenses:
177
+ - MIT
178
+ metadata:
179
+ allowed_push_host: https://rubygems.org
180
+ post_install_message:
181
+ rdoc_options: []
182
+ require_paths:
183
+ - lib
184
+ required_ruby_version: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ required_rubygems_version: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ requirements: []
195
+ rubygems_version: 3.1.3
196
+ signing_key:
197
+ specification_version: 4
198
+ summary: Db2Query
199
+ test_files: []