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