db2_query 0.3.1 → 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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +399 -147
  3. data/lib/db2_query.rb +36 -9
  4. data/lib/db2_query/base.rb +13 -0
  5. data/lib/db2_query/config.rb +14 -14
  6. data/lib/db2_query/core.rb +59 -118
  7. data/lib/db2_query/db_client.rb +56 -0
  8. data/lib/db2_query/db_connection.rb +67 -0
  9. data/lib/db2_query/db_statements.rb +87 -0
  10. data/lib/db2_query/definitions.rb +79 -0
  11. data/lib/db2_query/error.rb +71 -6
  12. data/lib/db2_query/field_type.rb +31 -0
  13. data/lib/db2_query/helper.rb +49 -0
  14. data/lib/db2_query/logger.rb +14 -4
  15. data/lib/db2_query/query.rb +117 -0
  16. data/lib/db2_query/quoting.rb +102 -0
  17. data/lib/db2_query/railtie.rb +5 -2
  18. data/lib/db2_query/result.rb +45 -33
  19. data/lib/db2_query/sql_statement.rb +34 -0
  20. data/lib/db2_query/tasks.rb +29 -0
  21. data/lib/db2_query/tasks/database.rake +2 -33
  22. data/lib/db2_query/tasks/init.rake +1 -1
  23. data/lib/db2_query/tasks/initializer.rake +2 -33
  24. data/lib/db2_query/tasks/templates/database.rb.tt +19 -0
  25. data/lib/db2_query/tasks/templates/initializer.rb.tt +8 -0
  26. data/lib/db2_query/type/binary.rb +19 -0
  27. data/lib/db2_query/type/boolean.rb +41 -0
  28. data/lib/db2_query/type/date.rb +34 -0
  29. data/lib/db2_query/type/decimal.rb +15 -0
  30. data/lib/db2_query/type/integer.rb +15 -0
  31. data/lib/db2_query/type/string.rb +30 -0
  32. data/lib/db2_query/type/text.rb +11 -0
  33. data/lib/db2_query/type/time.rb +30 -0
  34. data/lib/db2_query/type/timestamp.rb +30 -0
  35. data/lib/db2_query/type/value.rb +29 -0
  36. data/lib/db2_query/version.rb +1 -1
  37. data/lib/rails/generators/query/USAGE +15 -0
  38. data/lib/rails/generators/query/query_generator.rb +70 -0
  39. data/lib/rails/generators/query/templates/query.rb.tt +26 -0
  40. data/lib/rails/generators/query/templates/query_definitions.rb.tt +12 -0
  41. data/lib/rails/generators/query/templates/unit_test.rb.tt +9 -0
  42. metadata +49 -10
  43. data/lib/db2_query/connection.rb +0 -125
  44. data/lib/db2_query/formatter.rb +0 -27
data/lib/db2_query.rb CHANGED
@@ -3,20 +3,47 @@
3
3
  require "active_record"
4
4
  require "active_support"
5
5
  require "active_support/concurrency/load_interlock_aware_monitor"
6
+ require "active_model/type"
6
7
  require "connection_pool"
7
8
  require "odbc_utf8"
9
+ require "db2_query/error"
8
10
 
9
11
  module Db2Query
10
- extend ActiveSupport::Autoload
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"
11
22
 
12
- autoload :Version
13
- autoload :Config
14
- autoload :Connection
15
- autoload :Core
16
- autoload :Result
17
- autoload :Logger
18
- autoload :Error
19
- autoload :Base
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
20
47
 
21
48
  require "db2_query/railtie" if defined?(Rails)
22
49
  end
@@ -3,6 +3,19 @@
3
3
  module Db2Query
4
4
  class Base
5
5
  include Config
6
+ include Helper
7
+ include Quoting
8
+ include DbConnection
9
+ include FieldType
6
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
7
20
  end
8
21
  end
@@ -2,27 +2,27 @@
2
2
 
3
3
  module Db2Query
4
4
  module Config
5
- extend ActiveSupport::Concern
6
5
  DEFAULT_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence }
7
6
 
8
- included do
9
- @@configurations = nil
7
+ def self.included(base)
8
+ base.send(:extend, ClassMethods)
10
9
  end
11
10
 
12
- class_methods do
13
- def configurations
14
- @@configurations
15
- end
11
+ module ClassMethods
12
+ mattr_accessor :configurations
13
+ @@configurations = nil
14
+
16
15
  alias config configurations
17
16
 
17
+ def default_path
18
+ "#{Rails.root}/config/db2query.yml"
19
+ end
20
+
18
21
  def load_database_configurations(path = nil)
19
- file_path = path || "#{Rails.root}/config/db2query.yml"
20
- if File.exist?(file_path)
21
- config_file = IO.read(file_path)
22
- @@configurations = YAML.load(config_file)[DEFAULT_ENV.call].transform_keys(&:to_sym)
23
- else
24
- raise Db2Query::Error, "Could not load db2query database configuration. No such file - #{file_path}"
25
- end
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
26
  end
27
27
  end
28
28
  end
@@ -1,160 +1,101 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Db2Query
4
- class DbClient
5
- attr_reader :dsn
6
-
7
- delegate :run, :do, to: :client
8
-
9
- def initialize(config)
10
- @dsn = config[:dsn]
11
- @idle_time_limit = config[:idle] || 5
12
- @client = new_db_client
13
- @last_active = Time.now
14
- end
15
-
16
- def expire?
17
- Time.now - @last_active > 60 * @idle_time_limit
18
- end
19
-
20
- def active?
21
- @client.connected?
22
- end
23
-
24
- def connected_and_persist?
25
- active? && !expire?
26
- end
27
-
28
- def disconnect!
29
- @client.drop_all
30
- @client.disconnect if active?
31
- @client = nil
4
+ module Core
5
+ def self.included(base)
6
+ base.send(:extend, ClassMethods)
32
7
  end
33
8
 
34
- def new_db_client
35
- ODBC.connect(dsn)
36
- end
9
+ module ClassMethods
10
+ attr_reader :definitions
37
11
 
38
- def client
39
- return @client if connected_and_persist?
40
- disconnect!
41
- @last_active = Time.now
42
- @client = new_db_client
43
- end
44
- end
12
+ delegate :query_rows, :query_value, :query_values, :execute, to: :connection
45
13
 
46
- module Core
47
- extend ActiveSupport::Concern
48
- included do
49
- @@connection = nil
50
- @@mutex = Mutex.new
51
- end
52
-
53
- class_methods do
54
14
  def initiation
55
15
  yield(self) if block_given?
56
16
  end
57
17
 
58
- def attributes(attr_name, format)
59
- formatters.store(attr_name, format)
18
+ def define_query_definitions
19
+ @definitions = new_definitions
60
20
  end
61
21
 
62
- def connection
63
- @@connection || create_connection
22
+ def exec_query_result(query, args)
23
+ reset_id_when_required(query)
24
+ connection.exec_query(query, args)
64
25
  end
65
26
 
66
- def create_connection
67
- @@mutex.synchronize do
68
- return @@connection if @@connection
69
- @@connection = Connection.new(config) { DbClient.new(config) }
70
- end
71
- end
72
-
73
- def establish_connection
74
- load_database_configurations
75
- create_connection
76
- end
77
-
78
- def query(name, body)
79
- if defined_method_name?(name)
80
- raise Db2Query::Error, "You tried to define a scope named \"#{name}\" " \
81
- "on the model \"#{self.name}\", but DB2Query already defined " \
82
- "a class method with the same name."
83
- end
84
-
85
- if body.respond_to?(:call)
86
- singleton_class.define_method(name) do |*args|
87
- body.call(*args)
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 })
88
32
  end
89
- elsif body.is_a?(String)
90
- sql = body.strip
91
- singleton_class.define_method(name) do |*args|
92
- connection.exec_query(formatters, sql, args)
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)
93
42
  end
94
43
  else
95
- raise Db2Query::Error, "The query body needs to be callable or is a sql string"
44
+ raise Db2Query::QueryMethodError.new
96
45
  end
97
46
  end
98
47
  alias define query
99
48
 
100
- def fetch(sql, args)
101
- validate_sql(sql)
102
- connection.exec_query({}, sql, args)
49
+ def fetch(sql, args = [])
50
+ query = definitions.lookup_query(args, sql)
51
+ query.validate_select_query
52
+ connection.exec_query(query, args)
103
53
  end
104
54
 
105
55
  def fetch_list(sql, args)
106
- validate_sql(sql)
107
- raise Db2Query::Error, "Missing @list pointer at SQL" if sql.scan(/\@list+/).length == 0
108
- raise Db2Query::Error, "The arguments should be an array of list" unless args.is_a?(Array)
109
- connection.exec_query({}, sql.gsub("@list", "'#{args.join("', '")}'"), [])
110
- end
111
-
112
- def sql_with_extention(sql, extention)
113
- validate_sql(sql)
114
- raise Db2Query::Error, "Missing @extention pointer at SQL" if sql.scan(/\@extention+/).length == 0
115
- sql.gsub("@extention", extention.strip)
56
+ list = args.first
57
+ fetch(sql_with_list(sql, list), args.drop(1))
116
58
  end
117
59
 
118
60
  private
119
- def formatters
120
- @formatters ||= Hash.new
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
121
66
  end
122
67
 
123
- def defined_method_name?(name)
124
- self.class.method_defined?(name) || self.class.private_method_defined?(name)
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
125
72
  end
126
73
 
127
- def method_missing(method_name, *args, &block)
128
- sql_methods = self.instance_methods.grep(/_sql/)
129
- sql_method = "#{method_name}_sql".to_sym
130
-
131
- if sql_methods.include?(sql_method)
132
- sql_statement = allocate.method(sql_method).call
133
-
134
- unless sql_statement.is_a? String
135
- raise Db2Query::Error, "Query methods must return a SQL statement string!"
136
- end
137
-
138
- query(method_name, sql_statement)
74
+ def raw_query(sql, args)
75
+ Query.new.tap do |query|
76
+ query.define_sql(sql)
77
+ query.define_args(args)
78
+ end
79
+ end
139
80
 
140
- if args[0].is_a?(Hash)
141
- keys = sql_statement.scan(/\$\S+/).map { |key| key.gsub!(/[$=]/, "") }
142
- rearrange_args = {}
143
- keys.each { |key| rearrange_args[key.to_sym] = args[0][key.to_sym] }
144
- args[0] = rearrange_args
145
- end
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)
85
+ end
146
86
 
87
+ def method_missing(method_name, *args, &block)
88
+ if sql_query_method?(method_name)
89
+ define_sql_query(method_name)
147
90
  method(method_name).call(*args)
148
- elsif connection.respond_to?(method_name)
149
- connection.send(method_name, *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)
150
95
  else
151
96
  super
152
97
  end
153
98
  end
154
-
155
- def validate_sql(sql)
156
- raise Db2Query::Error, "SQL have to be in string format" unless sql.is_a?(String)
157
- end
158
99
  end
159
100
  end
160
101
  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