db2_query 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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