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.
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