db2_query 0.2.3 → 0.3.3

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