db2_query 0.2.3 → 0.3.3

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 +472 -124
  4. data/Rakefile +3 -2
  5. data/lib/db2_query/base.rb +15 -5
  6. data/lib/db2_query/config.rb +20 -17
  7. data/lib/db2_query/core.rb +79 -60
  8. data/lib/db2_query/db_client.rb +56 -0
  9. data/lib/db2_query/db_connection.rb +68 -0
  10. data/lib/db2_query/db_statements.rb +87 -0
  11. data/lib/db2_query/definitions.rb +93 -0
  12. data/lib/db2_query/error.rb +72 -7
  13. data/lib/db2_query/field_type.rb +31 -0
  14. data/lib/db2_query/helper.rb +50 -0
  15. data/lib/db2_query/logger.rb +52 -0
  16. data/lib/db2_query/query.rb +128 -0
  17. data/lib/db2_query/railtie.rb +5 -10
  18. data/lib/db2_query/result.rb +51 -31
  19. data/lib/db2_query/sql_statement.rb +34 -0
  20. data/lib/db2_query/tasks/database.rake +2 -46
  21. data/lib/db2_query/tasks/init.rake +1 -1
  22. data/lib/db2_query/tasks/initializer.rake +2 -34
  23. data/lib/db2_query/tasks/templates/database.rb.tt +19 -0
  24. data/lib/db2_query/tasks/templates/initializer.rb.tt +8 -0
  25. data/lib/db2_query/tasks.rb +29 -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 +2 -2
  37. data/lib/db2_query.rb +42 -18
  38. data/lib/rails/generators/query/USAGE +15 -0
  39. data/lib/rails/generators/query/query_generator.rb +70 -0
  40. data/lib/rails/generators/query/templates/query.rb.tt +26 -0
  41. data/lib/rails/generators/query/templates/query_definitions.rb.tt +18 -0
  42. data/lib/rails/generators/query/templates/unit_test.rb.tt +9 -0
  43. metadata +74 -36
  44. data/lib/db2_query/bind.rb +0 -6
  45. data/lib/db2_query/connection.rb +0 -164
  46. data/lib/db2_query/connection_handling.rb +0 -112
  47. data/lib/db2_query/database_statements.rb +0 -89
  48. data/lib/db2_query/formatter.rb +0 -27
  49. data/lib/db2_query/odbc_connector.rb +0 -44
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module Helper
5
+ def self.included(base)
6
+ base.send(:extend, ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def sql_with_list(sql, list)
11
+ if sql.scan(/\@list+/).length == 0
12
+ raise Db2Query::MissingListError, "Missing @list pointer at SQL"
13
+ elsif !list.is_a?(Array)
14
+ raise Db2Query::ListTypeError, "The arguments should be an array of list"
15
+ end
16
+ sql.gsub("@list", "'#{list.join("', '")}'")
17
+ end
18
+
19
+ def sql_with_extension(sql, extension)
20
+ if sql.scan(/\@extension+/).length == 0
21
+ raise Db2Query::ExtensionError, "Missing @extension pointer at SQL"
22
+ end
23
+ sql.gsub("@extension", extension.strip)
24
+ end
25
+ alias sql_with_extention sql_with_extension
26
+
27
+ private
28
+ def sql_query_methods
29
+ self.instance_methods.grep(/_sql/)
30
+ end
31
+
32
+ def sql_query_symbol(method_name)
33
+ "#{method_name}_sql".to_sym
34
+ end
35
+
36
+ def sql_query_method?(method_name)
37
+ sql_query_name = sql_query_symbol(method_name)
38
+ sql_query_methods.include?(sql_query_name)
39
+ end
40
+
41
+ def validate_sql(sql)
42
+ raise Db2Query::Error, "SQL have to be in string format" unless sql.is_a?(String)
43
+ end
44
+
45
+ def fetch_error_message
46
+ "`fetch`, `fetch_list` and `fetch_extension` methods applied for SQL `select` statement only."
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module Logger
5
+ def translate_exception_class(e, sql, binds)
6
+ message = "#{e.class.name}: #{e.message}"
7
+
8
+ exception = translate_exception(
9
+ e, message: message, sql: sql, binds: binds
10
+ )
11
+ exception.set_backtrace e.backtrace
12
+ exception
13
+ end
14
+
15
+ def log(sql, binds = [], args = [], &block)
16
+ instrument(
17
+ "sql.active_record",
18
+ sql: sql,
19
+ name: "SQL",
20
+ binds: binds,
21
+ type_casted_binds: args,
22
+ statement_name: nil,
23
+ connection_id: object_id,
24
+ connection: self) do
25
+ synchronize do
26
+ yield
27
+ end
28
+ rescue => e
29
+ raise translate_exception_class(e, sql, binds)
30
+ end
31
+ end
32
+
33
+ def translate_exception(exception, message:, sql:, binds:)
34
+ case exception
35
+ when RuntimeError
36
+ exception
37
+ else
38
+ StatementInvalid.new(message, sql: sql, binds: binds)
39
+ end
40
+ end
41
+
42
+ class StatementInvalid < ActiveRecord::ActiveRecordError
43
+ def initialize(message = nil, sql: nil, binds: nil)
44
+ super(message || $!.try(:message))
45
+ @sql = sql
46
+ @binds = binds
47
+ end
48
+
49
+ attr_reader :sql, :binds
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class Query
5
+ attr_reader :query_name, :sql, :columns, :keys, :types, :argument_types
6
+
7
+ include SqlStatement
8
+
9
+ def initialize(query_name = nil)
10
+ @columns = {}
11
+ @query_name = query_name
12
+ @sql_statement = nil
13
+ @argument_types = {}
14
+ @types = {}
15
+ end
16
+
17
+ def define_sql(sql)
18
+ @keys ||= sql_arguments(sql)
19
+ @sql ||= sql.tr("$", "")
20
+ end
21
+
22
+ def map_column(name, args)
23
+ @columns[name] = args
24
+ end
25
+
26
+ def method_missing(method_name, *args, &block)
27
+ map_column(method_name, args)
28
+ end
29
+
30
+ def data_type(key)
31
+ column = column_from_key(key)
32
+ types.fetch(column.to_sym)
33
+ rescue
34
+ raise Db2Query::Error, "No column #{column} found at query: #{query_name} definitions"
35
+ end
36
+
37
+ def argument_type(key)
38
+ argument_types.fetch(key) || data_type(key)
39
+ rescue
40
+ raise Db2Query::Error, "No argument #{key} type found at query: #{query_name}"
41
+ end
42
+
43
+ def argument_keys
44
+ keys.map do |key|
45
+ arg_key = "#{key}".split(".").last
46
+ arg_key.to_sym unless arg_key.nil?
47
+ end
48
+ end
49
+
50
+ def length
51
+ columns.length
52
+ end
53
+
54
+ def raw_query_args(args)
55
+ case args
56
+ when Array, Hash
57
+ validated_args(args)
58
+ else
59
+ args
60
+ end
61
+ end
62
+
63
+ def define_args(args)
64
+ class_eval { attr_accessor "args" }
65
+ send("args=", raw_query_args(args))
66
+ end
67
+
68
+ def sorted_args(args)
69
+ argument_keys.map.with_index do |key, index|
70
+ serialized_arg(args.is_a?(Hash) ? args[key] : args[index], key)
71
+ end
72
+ end
73
+
74
+ def column_id
75
+ columns.fetch(:id, nil)
76
+ end
77
+
78
+ def validate_result_columns(result_columns)
79
+ res_cols, def_cols = [result_columns.length, length]
80
+ if res_cols != def_cols
81
+ raise Db2Query::ColumnError.new(def_cols, res_cols)
82
+ end
83
+ end
84
+
85
+ def exec_query_arguments(args)
86
+ [db2_spec_sql, binds(args), validated_args(args)]
87
+ end
88
+
89
+ def validate_select_query
90
+ if iud_sql?
91
+ raise Db2Query::Error, "Fetch queries are used for select statement query only."
92
+ end
93
+ end
94
+
95
+ class Bind < Struct.new(:name, :value)
96
+ end
97
+
98
+ private
99
+ def sql_arguments(raw_sql)
100
+ raw_sql.scan(/\$\S+/).map { |arg| arg.gsub!(/[$=,)]/, "").to_sym }
101
+ end
102
+
103
+ def serialized_arg(arg, key)
104
+ query_name.nil? ? arg : argument_type(key).serialize(arg)
105
+ end
106
+
107
+ def column_from_key(key)
108
+ "#{key}".split(".").last.downcase
109
+ end
110
+
111
+ def new_bind(key, arg)
112
+ [Bind.new(column_from_key(key), arg), arg]
113
+ end
114
+
115
+ def binds(args)
116
+ keys.map.with_index do |key, index|
117
+ new_bind(key, args.first.is_a?(Hash)? args.first[key] : args[index])
118
+ end
119
+ end
120
+
121
+ def validated_args(args)
122
+ arguments = args.first.is_a?(Hash) ? args.first : args
123
+ given, expected = [arguments.length, keys.length]
124
+ raise Db2Query::ArgumentError.new(given, expected) unless given == expected
125
+ sorted_args(arguments)
126
+ end
127
+ end
128
+ end
@@ -1,20 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "db2_query"
4
- require "rails"
5
-
6
- module DB2Query
7
- class Railtie < Rails::Railtie
3
+ module Db2Query
4
+ class Railtie < ::Rails::Railtie
8
5
  railtie_name :db2_query
9
6
 
10
7
  rake_tasks do
11
- path = File.expand_path(__dir__)
12
- Dir.glob("#{path}/tasks/*.rake").each { |f| load f }
8
+ Dir.glob("#{Db2Query.root}/db2_query/tasks/*.rake").each { |f| load f }
13
9
  end
14
10
 
15
- initializer "db2_query.database_initialization" do
16
- DB2Query::Base.configurations = DB2Query.config
17
- DB2Query::Base.establish_connection :primary
11
+ config.app_generators do
12
+ require "#{Db2Query.root}/rails/generators/query/query_generator.rb"
18
13
  end
19
14
  end
20
15
  end
@@ -1,69 +1,89 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module DB2Query
3
+ module Db2Query
4
4
  class Result < ActiveRecord::Result
5
- attr_reader :formatters
5
+ attr_reader :definition
6
6
 
7
- def initialize(columns, rows, formatters = {}, column_types = {})
8
- @formatters = formatters
9
- super(columns, rows, column_types)
7
+ alias query definition
8
+
9
+ delegate :data_type, :validate_result_columns, to: :definition
10
+
11
+ def initialize(columns, rows, definition)
12
+ @definition = definition
13
+ validate_result_columns(columns)
14
+ super(columns, rows, {})
15
+ end
16
+
17
+ def record
18
+ records.first
10
19
  end
11
20
 
12
21
  def records
13
- @records ||= rows.map do |row|
14
- Record.new(row, columns, formatters)
15
- end
22
+ @records ||= rows.map { |row| new_record(row) }
16
23
  end
17
24
 
18
25
  def to_h
19
26
  rows.map do |row|
20
- columns.zip(row).each_with_object({}) { |cr, h| h[cr[0].to_sym] = cr[1] }
27
+ index, hash = [0, {}]
28
+ while index < columns.length
29
+ attr_name = columns[index].to_sym
30
+ hash[attr_name] = data_type(attr_name).deserialize(row[index])
31
+ index += 1
32
+ end
33
+ hash
21
34
  end
22
35
  end
23
36
 
24
37
  def inspect
25
38
  entries = records.take(11).map!(&:inspect)
26
-
27
39
  entries[10] = "..." if entries.size == 11
28
-
29
- "#<#{self.class.name} @records=[#{entries.join(', ')}]>"
40
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
30
41
  end
31
42
 
32
43
  class Record
33
- attr_reader :formatters
44
+ attr_reader :definition
34
45
 
35
- def initialize(row, columns, formatters)
36
- @formatters = formatters
37
- columns.zip(row) do |col, val|
38
- column, value = format(col, val)
39
- singleton_class.class_eval { attr_accessor "#{column}" }
40
- send("#{column}=", value)
41
- end
46
+ delegate :data_type, to: :definition
47
+
48
+ def initialize(row, columns, definition)
49
+ @definition = definition
50
+ add_attributes(columns, row)
42
51
  end
43
52
 
44
53
  def inspect
45
54
  inspection = if defined?(instance_variables) && instance_variables
46
- instance_variables.reject { |var| var == :@formatters }.map do |attr|
47
- value = instance_variable_get(attr)
48
- "#{attr[1..-1]}: #{(value.kind_of? String) ? %Q{"#{value}"} : value}"
55
+ instance_variables.reject { |var| var == :@definition }.map do |attribute|
56
+ "#{attribute[1..-1]}: #{instance_variable_get(attribute)}"
49
57
  end.compact.join(", ")
50
58
  else
51
59
  "not initialized"
52
60
  end
53
-
54
61
  "#<Record #{inspection}>"
55
62
  end
56
63
 
57
64
  private
58
- def format(col, val)
59
- column = col.downcase
60
- format_name = formatters[column.to_sym]
61
- unless format_name.nil?
62
- formatter = DB2Query::Formatter.lookup(format_name)
63
- val = formatter.format(val)
65
+ def add_attributes(columns, row)
66
+ index = 0
67
+ while index < columns.length
68
+ column, value = [columns[index], row[index]]
69
+ class_eval { attr_accessor "#{column}" }
70
+ send("#{column}=", data_type(column).deserialize(value))
71
+ index += 1
64
72
  end
65
- [column, val]
66
73
  end
67
74
  end
75
+
76
+ private
77
+ def new_record(row)
78
+ Record.new(row, columns, definition)
79
+ end
80
+
81
+ def method_missing(method_name, *args, &block)
82
+ if record.respond_to?(method_name)
83
+ record.send(method_name)
84
+ else
85
+ super
86
+ end
87
+ end
68
88
  end
69
89
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module SqlStatement
5
+ def delete_sql?
6
+ sql.match?(/delete/i)
7
+ end
8
+
9
+ def insert_sql?
10
+ sql.match?(/insert/i)
11
+ end
12
+
13
+ def iud_sql?
14
+ sql.match?(/insert into|update|delete/i)
15
+ end
16
+
17
+ def db2_spec_sql
18
+ iud_sql? ? iud_spec_sql : sql
19
+ end
20
+
21
+ def table_name
22
+ insert_sql? ? sql.split("INTO ").last.split(" ").first : nil
23
+ end
24
+
25
+ private
26
+ def new_keys(raw_sql)
27
+ raw_sql.scan(/\$\S+/).map { |key| key.gsub!(/[$=,)]/, "").to_sym }
28
+ end
29
+
30
+ def iud_spec_sql
31
+ "SELECT * FROM #{delete_sql? ? "OLD" : "NEW"} TABLE (#{sql})"
32
+ end
33
+ end
34
+ end
@@ -1,54 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- DB2_QUERY_DATABASE_TEMPLATE ||= <<-EOF
4
- # frozen_string_literal: true
5
- # Database configuration example
6
- development:
7
- primary:
8
- dsn: iseries
9
- uid: <%= ENV["ISERIES_UID"] %>
10
- pwd: <%= ENV["ISERIES_PWD"] %>
11
- secondary:
12
- conn_string:
13
- driver: DB2
14
- database: ARUNIT2
15
- dbalias: ARUNIT2
16
- hostname: LOCALHOST
17
- currentschema: LIBTEST
18
- port: "0"
19
- protocol: IPC
20
- uid: <%= ENV["DB2EC_UID"] %>
21
- pwd: <%= ENV["DB2EC_PWD"] %>
22
- test:
23
- primary:
24
- dsn: iseries
25
- uid: <%= ENV["ISERIES_UID"] %>
26
- pwd: <%= ENV["ISERIES_PWD"] %>
27
- secondary:
28
- conn_string:
29
- driver: DB2
30
- database: ARUNIT2
31
- dbalias: ARUNIT2
32
- hostname: LOCALHOST
33
- currentschema: LIBTEST
34
- port: "0"
35
- protocol: IPC
36
- uid: <%= ENV["DB2EC_UID"] %>
37
- pwd: <%= ENV["DB2EC_PWD"] %>
38
- EOF
3
+ require "db2_query/tasks"
39
4
 
40
5
  namespace :db2query do
41
6
  desc "Create Database configuration file"
42
7
  task :database do
43
- database_path = "#{Rails.root}/config/db2query_database.yml"
44
- if File.exist?(database_path)
45
- raise ArgumentError, "File exists."
46
- else
47
- puts " Creating database config file ..."
48
- File.open(database_path, "w") do |file|
49
- file.puts DB2_QUERY_DATABASE_TEMPLATE
50
- end
51
- puts " File '#{database_path}' created."
52
- end
8
+ Db2Query::DatabaseTask.generate_file
53
9
  end
54
10
  end
@@ -3,7 +3,7 @@
3
3
  namespace :db2query do
4
4
  desc "Create Initializer and Database configuration file"
5
5
  task :init do
6
- Rake::Task["db2query:initializer"].invoke
7
6
  Rake::Task["db2query:database"].invoke
7
+ Rake::Task["db2query:initializer"].invoke
8
8
  end
9
9
  end
@@ -1,42 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- DB2_QUERY_INITIALIZER_TEMPLATE ||= <<-EOF
4
- # frozen_string_literal: true
5
-
6
- require "db2_query"
7
- require "db2_query/formatter"
8
-
9
- DB2Query::Base.initiation do |base|
10
- base.configurations = base.parent.config
11
- base.establish_connection ENV['RAILS_ENV'].to_sym
12
- end
13
-
14
- # Example
15
-
16
- class FirstNameFormatter < DB2Query::AbstractFormatter
17
- def format(value)
18
- "Dr." + value
19
- end
20
- end
21
-
22
- DB2Query::Formatter.registration do |format|
23
- format.register(:first_name_formatter, FirstNameFormatter)
24
- end
25
- EOF
3
+ require "db2_query/tasks"
26
4
 
27
5
  namespace :db2query do
28
6
  desc "Create Initializer file"
29
7
  task :initializer do
30
- # Create initializer file
31
- initializer_path = "#{Rails.root}/config/initializers/db2query.rb"
32
- if File.exist?(initializer_path)
33
- raise ArgumentError, "File exists."
34
- else
35
- puts " Creating initializer file ..."
36
- File.open(initializer_path, "w") do |file|
37
- file.puts DB2_QUERY_INITIALIZER_TEMPLATE
38
- end
39
- puts " File '#{initializer_path}' created."
40
- end
8
+ Db2Query::InitializerTask.generate_file
41
9
  end
42
10
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ development:
4
+ dsn: TODO
5
+ idle: 5
6
+ pool: 5
7
+ timeout: 5
8
+
9
+ test:
10
+ dsn: TODO
11
+ idle: 5
12
+ pool: 5
13
+ timeout: 5
14
+
15
+ production:
16
+ dsn: TODO
17
+ idle: 5
18
+ pool: 5
19
+ timeout: 5
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "db2_query"
4
+
5
+ Db2Query::Base.initiation do |base|
6
+ base.set_field_types
7
+ base.establish_connection
8
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Db2Query
6
+ class Tasks < Thor::Group
7
+ include Thor::Actions
8
+
9
+ class << self
10
+ alias generate_file start
11
+ end
12
+ end
13
+
14
+ class DatabaseTask < Tasks
15
+ source_root File.expand_path("../tasks/templates", __FILE__)
16
+
17
+ def create_database_config_file
18
+ template "database.rb", File.join("config/db2query.yml")
19
+ end
20
+ end
21
+
22
+ class InitializerTask < Tasks
23
+ source_root File.expand_path("../tasks/templates", __FILE__)
24
+
25
+ def create_initializer_file
26
+ template "initializer.rb", File.join("config/initializers/db2query.rb")
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module Type
5
+ class Binary
6
+ def type
7
+ :binary
8
+ end
9
+
10
+ def serialize(value)
11
+ value.unpack1("H*")
12
+ end
13
+
14
+ def deserialize(value)
15
+ [value].pack("H*")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module Type
5
+ class Boolean < Value
6
+ TRUE_VALUES = [
7
+ true, 1, "1", "t", "T",
8
+ "true", "TRUE", "on", "ON",
9
+ :"1", :t, :T, :true, :TRUE, :on, :ON
10
+ ].freeze
11
+
12
+ DEFAULT = { true: true, false: false }
13
+
14
+ def initialize(options = DEFAULT)
15
+ super(options)
16
+ end
17
+
18
+ def name
19
+ :boolean
20
+ end
21
+
22
+ def serialize(value)
23
+ case value
24
+ when *TRUE_VALUES
25
+ 1
26
+ else
27
+ 0
28
+ end
29
+ end
30
+
31
+ def deserialize(value)
32
+ case value
33
+ when 1
34
+ options[:true]
35
+ else
36
+ options[:false]
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module Type
5
+ class Date < Value
6
+ def type
7
+ :string
8
+ end
9
+
10
+ YMD_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
11
+ DMY_DATE = /\A(\d\d)-(\d\d)-(\d{4})\z/
12
+
13
+ def serialize(value)
14
+ if value.is_a?(::String)
15
+ value = value.tr("/", "-")
16
+ case value
17
+ when YMD_DATE, DMY_DATE
18
+ quote(::Date.parse(value))
19
+ else
20
+ nil
21
+ end
22
+ elsif value.is_a?(::Date)
23
+ quote(value.strftime("%F"))
24
+ else
25
+ nil
26
+ end
27
+ end
28
+
29
+ def deserialize(value)
30
+ ::Date.parse(value.to_s)
31
+ end
32
+ end
33
+ end
34
+ end