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
@@ -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
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class Definitions
5
+ attr_accessor :types, :types_map
6
+
7
+ def initialize(types_map)
8
+ @types_map = types_map
9
+ describe
10
+ initialize_types
11
+ end
12
+
13
+ def describe
14
+ raise Db2Query::Error, "Please describe query definitions at #{self.class.name}"
15
+ end
16
+
17
+ def queries
18
+ @queries ||= {}
19
+ end
20
+
21
+ def query_definition(query_name, &block)
22
+ definition = Query.new(query_name)
23
+ yield definition
24
+ queries[query_name] = definition
25
+ end
26
+
27
+ def lookup(query_name)
28
+ queries.fetch(query_name)
29
+ rescue
30
+ raise Db2Query::QueryDefinitionError.new(name, query_name)
31
+ end
32
+
33
+ def lookup_query(*args)
34
+ query_name, sql = query_definitions(args)
35
+ lookup(query_name).tap do |query|
36
+ query.define_sql(sql)
37
+ end
38
+ end
39
+
40
+ private
41
+ def initialize_types
42
+ queries.each do |query_name, definition|
43
+ definition.columns.each do |column, col_def|
44
+ definition.types.store(column, data_type_instance(col_def))
45
+ end
46
+ end
47
+ end
48
+
49
+ def new_data_type(klass, options)
50
+ options.nil? ? klass.new : klass.new(**options)
51
+ rescue Exception => e
52
+ raise Db2Query::Error, e.message
53
+ end
54
+
55
+ def data_type_instance(column_definition)
56
+ data_type, options = column_definition
57
+ klass = @types_map.fetch(data_type)
58
+ new_data_type(klass, options)
59
+ rescue
60
+ raise Db2Query::Error, "Not supported `#{data_type}` data type"
61
+ end
62
+
63
+ def fetch_query_name(args)
64
+ placeholder = args.pop
65
+ placeholder.fetch(:query_name)
66
+ rescue
67
+ raise Db2Query::ImplementationError.new
68
+ end
69
+
70
+ def query_definitions(args)
71
+ case args.first
72
+ when Array
73
+ query_name = fetch_query_name(args.first)
74
+ [query_name, args.last]
75
+ else args
76
+ end
77
+ end
78
+ end
79
+ end
@@ -4,13 +4,78 @@ module Db2Query
4
4
  class Error < StandardError
5
5
  end
6
6
 
7
- class StatementInvalid < ActiveRecord::ActiveRecordError
8
- def initialize(message = nil, sql: nil, binds: nil)
9
- super(message || $!.try(:message))
10
- @sql = sql
11
- @binds = binds
7
+ class ArgumentError < StandardError
8
+ def initialize(given, expected)
9
+ @given = given
10
+ @expected = expected
11
+ super(message)
12
12
  end
13
13
 
14
- attr_reader :sql, :binds
14
+ def message
15
+ "Wrong number of arguments (given #{@given}, expected #{@expected})"
16
+ end
17
+ end
18
+
19
+ class ColumnError < StandardError
20
+ def initialize(def_cols, res_cols)
21
+ @def_cols = def_cols
22
+ @res_cols = res_cols
23
+ super(message)
24
+ end
25
+
26
+ def message
27
+ "Wrong number of columns (query definitions #{@def_cols}, query result #{@res_cols})"
28
+ end
29
+ end
30
+
31
+ class ConnectionError < StandardError
32
+ def initialize(odbc_message)
33
+ @odbc_message = odbc_message
34
+ super(message)
35
+ end
36
+
37
+ def message
38
+ "Unable to activate ODBC DSN connection #{@odbc_message}"
39
+ end
40
+ end
41
+
42
+ class ExtentionError < StandardError
43
+ end
44
+
45
+ class ImplementationError < StandardError
46
+ def message
47
+ "Method `fetch`, `fetch_list`, and `exec_query` can only be implemented inside a lambda query"
48
+ end
49
+ end
50
+
51
+ class ListTypeError < StandardError
52
+ end
53
+
54
+ class MissingListError < StandardError
55
+ end
56
+
57
+ class QueryDefinitionError < StandardError
58
+ def initialize(klass, query_name = nil, column = nil)
59
+ @klass = klass
60
+ @query_name = query_name
61
+ @column = column
62
+ super(message)
63
+ end
64
+
65
+ def message
66
+ if @query_name.nil?
67
+ "Definitions::#{@klass}Definitions file not found."
68
+ elsif @column.nil?
69
+ "No query definition found for #{@klass}:#{@query_name}"
70
+ else
71
+ "Column `#{@column}` not found at `#{@klass} query:#{@query_name}` Query Definitions."
72
+ end
73
+ end
74
+ end
75
+
76
+ class QueryMethodError < StandardError
77
+ def message
78
+ "The query body needs to be callable or is a SQL statement string"
79
+ end
15
80
  end
16
81
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ module FieldType
5
+ DEFAULT_FIELD_TYPES = {
6
+ binary: Db2Query::Type::Binary,
7
+ boolean: Db2Query::Type::Boolean,
8
+ string: Db2Query::Type::String,
9
+ varchar: Db2Query::Type::String,
10
+ longvarchar: Db2Query::Type::String,
11
+ decimal: Db2Query::Type::Decimal,
12
+ integer: Db2Query::Type::Integer,
13
+ date: Db2Query::Type::Date,
14
+ time: Db2Query::Type::Time,
15
+ timestamp: Db2Query::Type::Timestamp
16
+ }
17
+
18
+ def self.included(base)
19
+ base.send(:extend, ClassMethods)
20
+ end
21
+
22
+ module ClassMethods
23
+ mattr_reader :field_types_map
24
+ @@field_types_map = nil
25
+
26
+ def set_field_types(types = DEFAULT_FIELD_TYPES)
27
+ @@field_types_map = types
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,49 @@
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_extention(sql, extention)
20
+ if sql.scan(/\@extention+/).length == 0
21
+ raise Db2Query::ExtentionError, "Missing @extention pointer at SQL"
22
+ end
23
+ sql.gsub("@extention", extention.strip)
24
+ end
25
+
26
+ private
27
+ def sql_query_methods
28
+ self.instance_methods.grep(/_sql/)
29
+ end
30
+
31
+ def sql_query_symbol(method_name)
32
+ "#{method_name}_sql".to_sym
33
+ end
34
+
35
+ def sql_query_method?(method_name)
36
+ sql_query_name = sql_query_symbol(method_name)
37
+ sql_query_methods.include?(sql_query_name)
38
+ end
39
+
40
+ def validate_sql(sql)
41
+ raise Db2Query::Error, "SQL have to be in string format" unless sql.is_a?(String)
42
+ end
43
+
44
+ def fetch_error_message
45
+ "`fetch`, `fetch_list` and `fetch_extention` methods applied for SQL `select` statement only."
46
+ end
47
+ end
48
+ end
49
+ end
@@ -12,17 +12,17 @@ module Db2Query
12
12
  exception
13
13
  end
14
14
 
15
- def log(sql, name = "SQL", binds = [], args = [], &block)
16
- @instrumenter.instrument(
15
+ def log(sql, binds = [], args = [], &block)
16
+ instrument(
17
17
  "sql.active_record",
18
18
  sql: sql,
19
- name: name,
19
+ name: "SQL",
20
20
  binds: binds,
21
21
  type_casted_binds: args,
22
22
  statement_name: nil,
23
23
  connection_id: object_id,
24
24
  connection: self) do
25
- @lock.synchronize do
25
+ synchronize do
26
26
  yield
27
27
  end
28
28
  rescue => e
@@ -38,5 +38,15 @@ module Db2Query
38
38
  StatementInvalid.new(message, sql: sql, binds: binds)
39
39
  end
40
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
41
51
  end
42
52
  end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Db2Query
4
+ class Query
5
+ attr_reader :columns, :keys, :query_name, :sql, :types
6
+
7
+ include SqlStatement
8
+
9
+ def initialize(query_name = nil)
10
+ @columns = {}
11
+ @query_name = query_name
12
+ @sql_statement = nil
13
+ @types = {}
14
+ end
15
+
16
+ def define_sql(sql)
17
+ @keys ||= new_keys(sql)
18
+ @sql ||= sql.tr("$", "")
19
+ end
20
+
21
+ def map_column(name, args)
22
+ @columns[name] = args
23
+ end
24
+
25
+ def method_missing(method_name, *args, &block)
26
+ map_column(method_name, args)
27
+ end
28
+
29
+ def data_type(key)
30
+ column = column_from_key(key)
31
+ types.fetch(column.to_sym)
32
+ rescue
33
+ raise Db2Query::Error, "No column #{column} found at query: #{query_name} definitions"
34
+ end
35
+
36
+ def length
37
+ columns.length
38
+ end
39
+
40
+ def raw_query_args(args)
41
+ case args
42
+ when Array, Hash
43
+ validated_args(args)
44
+ else
45
+ args
46
+ end
47
+ end
48
+
49
+ def define_args(args)
50
+ class_eval { attr_accessor "args" }
51
+ send("args=", raw_query_args(args))
52
+ end
53
+
54
+ def sorted_args(args)
55
+ keys.map.with_index do |key, index|
56
+ serialized_arg(args.is_a?(Hash) ? args[key] : args[index], key)
57
+ end
58
+ end
59
+
60
+ def column_id
61
+ columns.fetch(:id, nil)
62
+ end
63
+
64
+ def validate_result_columns(result_columns)
65
+ res_cols, def_cols = [result_columns.length, length]
66
+ if res_cols != def_cols
67
+ raise Db2Query::ColumnError.new(def_cols, res_cols)
68
+ end
69
+ end
70
+
71
+ def exec_query_arguments(args)
72
+ [db2_spec_sql, binds(args), validated_args(args)]
73
+ end
74
+
75
+ def validate_select_query
76
+ if iud_sql?
77
+ raise Db2Query::Error, "Fetch queries are used for select statement query only."
78
+ end
79
+ end
80
+
81
+ class Bind < Struct.new(:name, :value)
82
+ end
83
+
84
+ private
85
+ def new_keys(raw_sql)
86
+ raw_sql.scan(/\$\S+/).map { |key| key.gsub!(/[$=,)]/, "").to_sym }
87
+ end
88
+
89
+ def serialized_arg(arg, key)
90
+ query_name.nil? ? arg : data_type(key).serialize(arg)
91
+ end
92
+
93
+ def column_from_key(key)
94
+ "#{key}".split(".").last.downcase
95
+ end
96
+
97
+ def new_bind(key, arg)
98
+ [Bind.new(column_from_key(key), arg), arg]
99
+ end
100
+
101
+ def binds(args)
102
+ keys.map.with_index do |key, index|
103
+ new_bind(key, args.first.is_a?(Hash)? args.first[key] : args[index])
104
+ end
105
+ end
106
+
107
+ def validate_arguments(given, expected)
108
+ raise Db2Query::ArgumentError.new(given, expected) unless given == expected
109
+ end
110
+
111
+ def validated_args(args)
112
+ args = args.first.is_a?(Hash) ? args.first : args
113
+ validate_arguments(args.length, keys.length)
114
+ sorted_args(args)
115
+ end
116
+ end
117
+ end