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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +453 -110
  4. data/Rakefile +3 -2
  5. data/lib/db2_query.rb +41 -15
  6. data/lib/db2_query/base.rb +16 -5
  7. data/lib/db2_query/config.rb +20 -17
  8. data/lib/db2_query/core.rb +68 -55
  9. data/lib/db2_query/db_client.rb +56 -0
  10. data/lib/db2_query/db_connection.rb +67 -0
  11. data/lib/db2_query/db_statements.rb +87 -0
  12. data/lib/db2_query/definitions.rb +79 -0
  13. data/lib/db2_query/error.rb +81 -0
  14. data/lib/db2_query/field_type.rb +31 -0
  15. data/lib/db2_query/helper.rb +49 -0
  16. data/lib/db2_query/logger.rb +52 -0
  17. data/lib/db2_query/query.rb +117 -0
  18. data/lib/db2_query/quoting.rb +102 -0
  19. data/lib/db2_query/railtie.rb +5 -7
  20. data/lib/db2_query/result.rb +51 -31
  21. data/lib/db2_query/sql_statement.rb +34 -0
  22. data/lib/db2_query/tasks.rb +29 -0
  23. data/lib/db2_query/tasks/database.rake +2 -50
  24. data/lib/db2_query/tasks/init.rake +1 -1
  25. data/lib/db2_query/tasks/initializer.rake +2 -34
  26. data/lib/db2_query/tasks/templates/database.rb.tt +19 -0
  27. data/lib/db2_query/tasks/templates/initializer.rb.tt +8 -0
  28. data/lib/db2_query/type/binary.rb +19 -0
  29. data/lib/db2_query/type/boolean.rb +41 -0
  30. data/lib/db2_query/type/date.rb +34 -0
  31. data/lib/db2_query/type/decimal.rb +15 -0
  32. data/lib/db2_query/type/integer.rb +15 -0
  33. data/lib/db2_query/type/string.rb +30 -0
  34. data/lib/db2_query/type/text.rb +11 -0
  35. data/lib/db2_query/type/time.rb +30 -0
  36. data/lib/db2_query/type/timestamp.rb +30 -0
  37. data/lib/db2_query/type/value.rb +29 -0
  38. data/lib/db2_query/version.rb +2 -2
  39. data/lib/rails/generators/query/USAGE +15 -0
  40. data/lib/rails/generators/query/query_generator.rb +70 -0
  41. data/lib/rails/generators/query/templates/query.rb.tt +26 -0
  42. data/lib/rails/generators/query/templates/query_definitions.rb.tt +12 -0
  43. data/lib/rails/generators/query/templates/unit_test.rb.tt +9 -0
  44. metadata +62 -49
  45. data/lib/db2_query/connection.rb +0 -163
  46. data/lib/db2_query/connection_handling.rb +0 -112
  47. data/lib/db2_query/database_statements.rb +0 -93
  48. data/lib/db2_query/formatter.rb +0 -27
  49. 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.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
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 "db2_query/config"
8
- require "db2_query/connection_handling"
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 DB2Query
11
- extend ActiveSupport::Autoload
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
- autoload :Version
14
- autoload :Base
15
- autoload :Core
16
- autoload :DatabaseStatements
17
- autoload :Connection
18
- autoload :ODBCConnector
19
- autoload :Formatter
20
- autoload :Result
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
@@ -1,10 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module DB2Query
3
+ module Db2Query
4
4
  class Base
5
- include ActiveRecord::Inheritance
6
- include DB2Query::Core
7
- extend ActiveRecord::ConnectionHandling
8
- extend DB2Query::ConnectionHandling
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
@@ -1,26 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module DB2Query
4
- class << self
5
- def config
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 config_path
10
- if defined?(Rails)
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
- def config_file
18
- Pathname.new(config_path)
19
- end
11
+ module ClassMethods
12
+ mattr_accessor :configurations
13
+ @@configurations = nil
14
+
15
+ alias config configurations
20
16
 
21
- def read_config
22
- erb = ERB.new(config_file.read)
23
- YAML.parse(erb.result(binding)).transform.transform_keys(&:to_sym)
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
@@ -1,84 +1,97 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record/database_configurations"
4
-
5
- module DB2Query
3
+ module Db2Query
6
4
  module Core
7
- extend ActiveSupport::Concern
8
-
9
- included do
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
- mattr_accessor :connection_handlers, instance_accessor: false, default: {}
9
+ module ClassMethods
10
+ attr_reader :definitions
20
11
 
21
- class_attribute :default_connection_handler
12
+ delegate :query_rows, :query_value, :query_values, :execute, to: :connection
22
13
 
23
- def self.connection_handler
24
- Thread.current.thread_variable_get("db2q_connection_handler") || default_connection_handler
14
+ def initiation
15
+ yield(self) if block_given?
25
16
  end
26
17
 
27
- def self.connection_handler=(handler)
28
- Thread.current.thread_variable_set("db2q_connection_handler", handler)
18
+ def define_query_definitions
19
+ @definitions = new_definitions
29
20
  end
30
21
 
31
- self.default_connection_handler = DB2Query::ConnectionHandler.new
32
- end
22
+ def exec_query_result(query, args)
23
+ reset_id_when_required(query)
24
+ connection.exec_query(query, args)
25
+ end
33
26
 
34
- module ClassMethods
35
- def initiation
36
- yield(self) if block_given?
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 attributes(attr_name, format)
40
- formatters.store(attr_name, format)
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 query(name, sql_statement)
44
- if defined_method_name?(name)
45
- raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
46
- "on the model \"#{self.name}\", but DB2Query already defined " \
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
- unless sql_statement.strip.match?(/^select/i)
51
- raise NotImplementedError
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
- self.class.define_method(name) do |*args|
55
- connection.exec_query(sql_statement, formatters, args)
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
- private
60
- def formatters
61
- @formatters ||= Hash.new
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 defined_method_name?(name)
65
- self.class.method_defined?(name) || self.class.private_method_defined?(name)
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
- sql_methods = self.instance_methods.grep(/_sql/)
70
- sql_method = "#{method_name}_sql".to_sym
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