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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +453 -110
- data/Rakefile +3 -2
- data/lib/db2_query.rb +41 -15
- data/lib/db2_query/base.rb +16 -5
- data/lib/db2_query/config.rb +20 -17
- data/lib/db2_query/core.rb +68 -55
- data/lib/db2_query/db_client.rb +56 -0
- data/lib/db2_query/db_connection.rb +67 -0
- data/lib/db2_query/db_statements.rb +87 -0
- data/lib/db2_query/definitions.rb +79 -0
- data/lib/db2_query/error.rb +81 -0
- data/lib/db2_query/field_type.rb +31 -0
- data/lib/db2_query/helper.rb +49 -0
- data/lib/db2_query/logger.rb +52 -0
- data/lib/db2_query/query.rb +117 -0
- data/lib/db2_query/quoting.rb +102 -0
- data/lib/db2_query/railtie.rb +5 -7
- data/lib/db2_query/result.rb +51 -31
- data/lib/db2_query/sql_statement.rb +34 -0
- data/lib/db2_query/tasks.rb +29 -0
- data/lib/db2_query/tasks/database.rake +2 -50
- data/lib/db2_query/tasks/init.rake +1 -1
- data/lib/db2_query/tasks/initializer.rake +2 -34
- data/lib/db2_query/tasks/templates/database.rb.tt +19 -0
- data/lib/db2_query/tasks/templates/initializer.rb.tt +8 -0
- data/lib/db2_query/type/binary.rb +19 -0
- data/lib/db2_query/type/boolean.rb +41 -0
- data/lib/db2_query/type/date.rb +34 -0
- data/lib/db2_query/type/decimal.rb +15 -0
- data/lib/db2_query/type/integer.rb +15 -0
- data/lib/db2_query/type/string.rb +30 -0
- data/lib/db2_query/type/text.rb +11 -0
- data/lib/db2_query/type/time.rb +30 -0
- data/lib/db2_query/type/timestamp.rb +30 -0
- data/lib/db2_query/type/value.rb +29 -0
- data/lib/db2_query/version.rb +2 -2
- data/lib/rails/generators/query/USAGE +15 -0
- data/lib/rails/generators/query/query_generator.rb +70 -0
- data/lib/rails/generators/query/templates/query.rb.tt +26 -0
- data/lib/rails/generators/query/templates/query_definitions.rb.tt +12 -0
- data/lib/rails/generators/query/templates/unit_test.rb.tt +9 -0
- metadata +62 -49
- data/lib/db2_query/connection.rb +0 -163
- data/lib/db2_query/connection_handling.rb +0 -112
- data/lib/db2_query/database_statements.rb +0 -93
- data/lib/db2_query/formatter.rb +0 -27
- data/lib/db2_query/odbc_connector.rb +0 -40
data/Rakefile
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "bundler/setup"
|
3
4
|
require "bundler/gem_tasks"
|
4
5
|
require "rake/testtask"
|
5
6
|
|
6
7
|
Rake::TestTask.new(:test) do |t|
|
7
8
|
t.libs << "test"
|
8
|
-
t.
|
9
|
-
t.
|
9
|
+
t.pattern = "test/**/*_test.rb"
|
10
|
+
t.verbose = false
|
10
11
|
end
|
11
12
|
|
12
13
|
task default: :test
|
data/lib/db2_query.rb
CHANGED
@@ -1,23 +1,49 @@
|
|
1
|
-
# frozen_string_literal:true
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "yaml"
|
4
|
-
require "erb"
|
5
3
|
require "active_record"
|
6
4
|
require "active_support"
|
7
|
-
require "
|
8
|
-
require "
|
5
|
+
require "active_support/concurrency/load_interlock_aware_monitor"
|
6
|
+
require "active_model/type"
|
7
|
+
require "connection_pool"
|
8
|
+
require "odbc_utf8"
|
9
|
+
require "db2_query/error"
|
9
10
|
|
10
|
-
module
|
11
|
-
|
11
|
+
module Db2Query
|
12
|
+
autoload :Version, "db2_query/version"
|
13
|
+
autoload :Error, "db2_query/error"
|
14
|
+
autoload :Config, "db2_query/config"
|
15
|
+
autoload :Logger, "db2_query/logger"
|
16
|
+
autoload :DbClient, "db2_query/db_client"
|
17
|
+
autoload :DbStatements, "db2_query/db_statements"
|
18
|
+
autoload :Validations, "db2_query/validations"
|
19
|
+
autoload :Helper, "db2_query/helper"
|
20
|
+
autoload :Quoting, "db2_query/quoting"
|
21
|
+
autoload :FieldType, "db2_query/field_type"
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
23
|
+
module Type
|
24
|
+
autoload :Value, "db2_query/type/value"
|
25
|
+
autoload :Binary, "db2_query/type/binary"
|
26
|
+
autoload :Boolean, "db2_query/type/boolean"
|
27
|
+
autoload :Decimal, "db2_query/type/decimal"
|
28
|
+
autoload :String, "db2_query/type/string"
|
29
|
+
autoload :Text, "db2_query/type/text"
|
30
|
+
autoload :Integer, "db2_query/type/integer"
|
31
|
+
autoload :Time, "db2_query/type/time"
|
32
|
+
autoload :Timestamp, "db2_query/type/timestamp"
|
33
|
+
autoload :Date, "db2_query/type/date"
|
34
|
+
end
|
35
|
+
|
36
|
+
autoload :SqlStatement, "db2_query/sql_statement"
|
37
|
+
autoload :Query, "db2_query/query"
|
38
|
+
autoload :Definitions, "db2_query/definitions"
|
39
|
+
autoload :DbConnection, "db2_query/db_connection"
|
40
|
+
autoload :Result, "db2_query/result"
|
41
|
+
autoload :Core, "db2_query/core"
|
42
|
+
autoload :Base, "db2_query/base"
|
43
|
+
|
44
|
+
def self.root
|
45
|
+
__dir__
|
46
|
+
end
|
21
47
|
|
22
48
|
require "db2_query/railtie" if defined?(Rails)
|
23
49
|
end
|
data/lib/db2_query/base.rb
CHANGED
@@ -1,10 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
3
|
+
module Db2Query
|
4
4
|
class Base
|
5
|
-
include
|
6
|
-
include
|
7
|
-
|
8
|
-
|
5
|
+
include Config
|
6
|
+
include Helper
|
7
|
+
include Quoting
|
8
|
+
include DbConnection
|
9
|
+
include FieldType
|
10
|
+
include Core
|
11
|
+
|
12
|
+
def self.inherited(subclass)
|
13
|
+
subclass.define_query_definitions
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.establish_connection
|
17
|
+
load_database_configurations
|
18
|
+
new_database_connection
|
19
|
+
end
|
9
20
|
end
|
10
21
|
end
|
data/lib/db2_query/config.rb
CHANGED
@@ -1,26 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
@config ||= read_config
|
7
|
-
end
|
3
|
+
module Db2Query
|
4
|
+
module Config
|
5
|
+
DEFAULT_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence }
|
8
6
|
|
9
|
-
def
|
10
|
-
|
11
|
-
"#{Rails.root}/config/db2query_database.yml"
|
12
|
-
else
|
13
|
-
ENV["DQ_CONFIG_PATH"]
|
14
|
-
end
|
7
|
+
def self.included(base)
|
8
|
+
base.send(:extend, ClassMethods)
|
15
9
|
end
|
16
10
|
|
17
|
-
|
18
|
-
|
19
|
-
|
11
|
+
module ClassMethods
|
12
|
+
mattr_accessor :configurations
|
13
|
+
@@configurations = nil
|
14
|
+
|
15
|
+
alias config configurations
|
20
16
|
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
def default_path
|
18
|
+
"#{Rails.root}/config/db2query.yml"
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_database_configurations(path = nil)
|
22
|
+
config_file = IO.read(path || default_path)
|
23
|
+
@@configurations = YAML.load(config_file)[DEFAULT_ENV.call].transform_keys(&:to_sym)
|
24
|
+
rescue Exception => e
|
25
|
+
raise Db2Query::Error, e.message
|
26
|
+
end
|
24
27
|
end
|
25
28
|
end
|
26
29
|
end
|
data/lib/db2_query/core.rb
CHANGED
@@ -1,84 +1,97 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
module DB2Query
|
3
|
+
module Db2Query
|
6
4
|
module Core
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def self.configurations=(config)
|
11
|
-
@@configurations = ActiveRecord::DatabaseConfigurations.new(config)
|
12
|
-
end
|
13
|
-
self.configurations = {}
|
14
|
-
|
15
|
-
def self.configurations
|
16
|
-
@@configurations
|
17
|
-
end
|
5
|
+
def self.included(base)
|
6
|
+
base.send(:extend, ClassMethods)
|
7
|
+
end
|
18
8
|
|
19
|
-
|
9
|
+
module ClassMethods
|
10
|
+
attr_reader :definitions
|
20
11
|
|
21
|
-
|
12
|
+
delegate :query_rows, :query_value, :query_values, :execute, to: :connection
|
22
13
|
|
23
|
-
def
|
24
|
-
|
14
|
+
def initiation
|
15
|
+
yield(self) if block_given?
|
25
16
|
end
|
26
17
|
|
27
|
-
def
|
28
|
-
|
18
|
+
def define_query_definitions
|
19
|
+
@definitions = new_definitions
|
29
20
|
end
|
30
21
|
|
31
|
-
|
32
|
-
|
22
|
+
def exec_query_result(query, args)
|
23
|
+
reset_id_when_required(query)
|
24
|
+
connection.exec_query(query, args)
|
25
|
+
end
|
33
26
|
|
34
|
-
|
35
|
-
|
36
|
-
|
27
|
+
def query(*query_args)
|
28
|
+
if query_args[1].respond_to?(:call)
|
29
|
+
query_name, body = query_args
|
30
|
+
singleton_class.define_method(query_name) do |*args|
|
31
|
+
body.call(args << { query_name: query_name })
|
32
|
+
end
|
33
|
+
elsif query_args[0].is_a?(String)
|
34
|
+
sql, query_args = [query_args.first.strip, query_args.drop(1)]
|
35
|
+
query = raw_query(sql, query_args)
|
36
|
+
connection.raw_query(query.db2_spec_sql, query.args)
|
37
|
+
elsif query_args[1].is_a?(String) && query_args[1].strip.length > 0
|
38
|
+
query_name, sql = query_args
|
39
|
+
query = definitions.lookup_query(query_name, sql.strip)
|
40
|
+
singleton_class.define_method(query_name) do |*args|
|
41
|
+
exec_query_result(query, args)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
raise Db2Query::QueryMethodError.new
|
45
|
+
end
|
37
46
|
end
|
47
|
+
alias define query
|
38
48
|
|
39
|
-
def
|
40
|
-
|
49
|
+
def fetch(sql, args = [])
|
50
|
+
query = definitions.lookup_query(args, sql)
|
51
|
+
query.validate_select_query
|
52
|
+
connection.exec_query(query, args)
|
41
53
|
end
|
42
54
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
"a class method with the same name."
|
48
|
-
end
|
55
|
+
def fetch_list(sql, args)
|
56
|
+
list = args.first
|
57
|
+
fetch(sql_with_list(sql, list), args.drop(1))
|
58
|
+
end
|
49
59
|
|
50
|
-
|
51
|
-
|
60
|
+
private
|
61
|
+
def new_definitions
|
62
|
+
definition_class = "Definitions::#{name}Definitions"
|
63
|
+
Object.const_get(definition_class).new(field_types_map)
|
64
|
+
rescue Exception => e
|
65
|
+
raise Db2Query::Error, e.message
|
52
66
|
end
|
53
67
|
|
54
|
-
|
55
|
-
|
68
|
+
def reset_id_when_required(query)
|
69
|
+
if query.insert_sql? && !query.column_id.nil?
|
70
|
+
connection.reset_id_sequence!(query.table_name)
|
71
|
+
end
|
56
72
|
end
|
57
|
-
end
|
58
73
|
|
59
|
-
|
60
|
-
|
61
|
-
|
74
|
+
def raw_query(sql, args)
|
75
|
+
Query.new.tap do |query|
|
76
|
+
query.define_sql(sql)
|
77
|
+
query.define_args(args)
|
78
|
+
end
|
62
79
|
end
|
63
80
|
|
64
|
-
def
|
65
|
-
|
81
|
+
def define_sql_query(method_name)
|
82
|
+
sql_query_name = sql_query_symbol(method_name)
|
83
|
+
sql_statement = allocate.method(sql_query_name).call
|
84
|
+
define(method_name, sql_statement)
|
66
85
|
end
|
67
86
|
|
68
87
|
def method_missing(method_name, *args, &block)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
if sql_methods.include?(sql_method)
|
73
|
-
sql_statement = allocate.method(sql_method).call
|
74
|
-
|
75
|
-
unless sql_statement.is_a? String
|
76
|
-
raise ArgumentError, "Query methods must return a SQL statement string!"
|
77
|
-
end
|
78
|
-
|
79
|
-
query(method_name, sql_statement)
|
80
|
-
|
88
|
+
if sql_query_method?(method_name)
|
89
|
+
define_sql_query(method_name)
|
81
90
|
method(method_name).call(*args)
|
91
|
+
elsif method_name == :exec_query
|
92
|
+
sql, args = [args.shift, args.first]
|
93
|
+
query = definitions.lookup_query(args, sql)
|
94
|
+
exec_query_result(query, args)
|
82
95
|
else
|
83
96
|
super
|
84
97
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Db2Query
|
4
|
+
class DbClient
|
5
|
+
attr_reader :dsn
|
6
|
+
|
7
|
+
include ActiveModel::Type::Helpers::Timezone
|
8
|
+
|
9
|
+
delegate :run, :do, to: :client
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
@dsn = config[:dsn]
|
13
|
+
@idle_time_limit = config[:idle] || 5
|
14
|
+
@client = new_client
|
15
|
+
@last_transaction = Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
def expire?
|
19
|
+
Time.now - @last_transaction > 60 * @idle_time_limit
|
20
|
+
end
|
21
|
+
|
22
|
+
def active?
|
23
|
+
@client.connected?
|
24
|
+
end
|
25
|
+
|
26
|
+
def connected_and_persist?
|
27
|
+
active? && !expire?
|
28
|
+
end
|
29
|
+
|
30
|
+
def disconnect!
|
31
|
+
@client.drop_all
|
32
|
+
@client.disconnect if active?
|
33
|
+
@client = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def new_client
|
37
|
+
ODBC.connect(dsn).tap do |odbc_conn|
|
38
|
+
odbc_conn.use_time = true
|
39
|
+
odbc_conn.use_utc = is_utc?
|
40
|
+
end
|
41
|
+
rescue ::ODBC::Error => e
|
42
|
+
raise Db2Query::ConnectionError.new(e.message)
|
43
|
+
end
|
44
|
+
|
45
|
+
def reconnect!
|
46
|
+
disconnect!
|
47
|
+
@client = new_client
|
48
|
+
end
|
49
|
+
|
50
|
+
def client
|
51
|
+
reconnect! unless connected_and_persist?
|
52
|
+
@last_transaction = Time.now
|
53
|
+
@client
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Db2Query
|
4
|
+
module DbConnection
|
5
|
+
def self.included(base)
|
6
|
+
base.send(:extend, ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
mattr_reader :connection
|
11
|
+
@@connection = nil
|
12
|
+
|
13
|
+
def new_database_connection
|
14
|
+
@@connection = Connection.new(config)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Connection
|
20
|
+
class Pool < ConnectionPool
|
21
|
+
def initialize(config, &block)
|
22
|
+
super(config, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def current_state
|
26
|
+
{ size: self.size, available: self.available }
|
27
|
+
end
|
28
|
+
|
29
|
+
def disconnect!
|
30
|
+
shutdown { |client| client.disconnect! }
|
31
|
+
end
|
32
|
+
|
33
|
+
def reload
|
34
|
+
super { |client| client.disconnect! }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :config, :connection_pool, :instrumenter, :lock
|
39
|
+
|
40
|
+
delegate :with, :current_state, :disconnect!, :reload, to: :connection_pool
|
41
|
+
delegate :instrument, to: :instrumenter
|
42
|
+
delegate :synchronize, to: :lock
|
43
|
+
|
44
|
+
include Logger
|
45
|
+
include DbStatements
|
46
|
+
|
47
|
+
def initialize(config)
|
48
|
+
@config = config
|
49
|
+
@instrumenter = ActiveSupport::Notifications.instrumenter
|
50
|
+
@lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
|
51
|
+
create_connection_pool
|
52
|
+
end
|
53
|
+
|
54
|
+
alias pool with
|
55
|
+
|
56
|
+
def pool_config
|
57
|
+
{ size: config[:pool], timeout: config[:timeout] }
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_connection_pool
|
61
|
+
synchronize do
|
62
|
+
return @connection_pool if @connection_pool
|
63
|
+
@connection_pool = Pool.new(pool_config) { DbClient.new(config) }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Db2Query
|
4
|
+
module DbStatements
|
5
|
+
def query(sql)
|
6
|
+
pool do |client|
|
7
|
+
stmt = client.run(sql)
|
8
|
+
stmt.to_a
|
9
|
+
ensure
|
10
|
+
stmt.drop unless stmt.nil?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def query_rows(sql)
|
15
|
+
query(sql)
|
16
|
+
end
|
17
|
+
|
18
|
+
def query_value(sql)
|
19
|
+
rows = query(sql)
|
20
|
+
row = rows.first
|
21
|
+
row && row.first
|
22
|
+
end
|
23
|
+
|
24
|
+
def query_values(sql)
|
25
|
+
query(sql).map(&:first)
|
26
|
+
end
|
27
|
+
|
28
|
+
def execute(sql, args = [])
|
29
|
+
pool do |client|
|
30
|
+
client.do(sql, *args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def raw_query(sql, args = [])
|
35
|
+
pool do |client|
|
36
|
+
stmt = client.run(sql, *args)
|
37
|
+
raw_result(stmt)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def exec_query(query, args = [])
|
42
|
+
sql, binds, args = query.exec_query_arguments(args)
|
43
|
+
log(sql, binds, args) do
|
44
|
+
pool do |client|
|
45
|
+
stmt = client.run(sql, *args)
|
46
|
+
columns = stmt.columns.values.map { |col| col.name.downcase }
|
47
|
+
rows = stmt.to_a
|
48
|
+
Db2Query::Result.new(columns, rows, query)
|
49
|
+
ensure
|
50
|
+
stmt.drop unless stmt.nil?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def reset_id_sequence!(table_name)
|
56
|
+
next_val = max_id(table_name) + 1
|
57
|
+
execute <<-SQL
|
58
|
+
ALTER TABLE #{table_name}
|
59
|
+
ALTER COLUMN ID
|
60
|
+
RESTART WITH #{next_val}
|
61
|
+
SET INCREMENT BY 1
|
62
|
+
SET NO CYCLE
|
63
|
+
SET CACHE 500
|
64
|
+
SET NO ORDER;
|
65
|
+
SQL
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def max_id(table_name)
|
70
|
+
query_value("SELECT COALESCE(MAX (ID),0) FROM #{table_name}")
|
71
|
+
end
|
72
|
+
|
73
|
+
def raw_result(stmt)
|
74
|
+
columns = stmt.columns.values.map { |col| col.name.downcase }
|
75
|
+
stmt.to_a.map do |row|
|
76
|
+
index, hash = [0, {}]
|
77
|
+
while index < columns.length
|
78
|
+
hash[columns[index].to_sym] = row[index]
|
79
|
+
index += 1
|
80
|
+
end
|
81
|
+
hash
|
82
|
+
end
|
83
|
+
ensure
|
84
|
+
stmt.drop unless stmt.nil?
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|