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.
- checksums.yaml +4 -4
- data/README.md +399 -147
- data/lib/db2_query.rb +36 -9
- data/lib/db2_query/base.rb +13 -0
- data/lib/db2_query/config.rb +14 -14
- data/lib/db2_query/core.rb +59 -118
- data/lib/db2_query/db_client.rb +56 -0
- data/lib/db2_query/db_connection.rb +67 -0
- data/lib/db2_query/db_statements.rb +87 -0
- data/lib/db2_query/definitions.rb +79 -0
- data/lib/db2_query/error.rb +71 -6
- data/lib/db2_query/field_type.rb +31 -0
- data/lib/db2_query/helper.rb +49 -0
- data/lib/db2_query/logger.rb +14 -4
- data/lib/db2_query/query.rb +117 -0
- data/lib/db2_query/quoting.rb +102 -0
- data/lib/db2_query/railtie.rb +5 -2
- data/lib/db2_query/result.rb +45 -33
- data/lib/db2_query/sql_statement.rb +34 -0
- data/lib/db2_query/tasks.rb +29 -0
- data/lib/db2_query/tasks/database.rake +2 -33
- data/lib/db2_query/tasks/init.rake +1 -1
- data/lib/db2_query/tasks/initializer.rake +2 -33
- data/lib/db2_query/tasks/templates/database.rb.tt +19 -0
- data/lib/db2_query/tasks/templates/initializer.rb.tt +8 -0
- data/lib/db2_query/type/binary.rb +19 -0
- data/lib/db2_query/type/boolean.rb +41 -0
- data/lib/db2_query/type/date.rb +34 -0
- data/lib/db2_query/type/decimal.rb +15 -0
- data/lib/db2_query/type/integer.rb +15 -0
- data/lib/db2_query/type/string.rb +30 -0
- data/lib/db2_query/type/text.rb +11 -0
- data/lib/db2_query/type/time.rb +30 -0
- data/lib/db2_query/type/timestamp.rb +30 -0
- data/lib/db2_query/type/value.rb +29 -0
- data/lib/db2_query/version.rb +1 -1
- data/lib/rails/generators/query/USAGE +15 -0
- data/lib/rails/generators/query/query_generator.rb +70 -0
- data/lib/rails/generators/query/templates/query.rb.tt +26 -0
- data/lib/rails/generators/query/templates/query_definitions.rb.tt +12 -0
- data/lib/rails/generators/query/templates/unit_test.rb.tt +9 -0
- metadata +49 -10
- data/lib/db2_query/connection.rb +0 -125
- 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
|
data/lib/db2_query/error.rb
CHANGED
@@ -4,13 +4,78 @@ module Db2Query
|
|
4
4
|
class Error < StandardError
|
5
5
|
end
|
6
6
|
|
7
|
-
class
|
8
|
-
def initialize(
|
9
|
-
|
10
|
-
@
|
11
|
-
|
7
|
+
class ArgumentError < StandardError
|
8
|
+
def initialize(given, expected)
|
9
|
+
@given = given
|
10
|
+
@expected = expected
|
11
|
+
super(message)
|
12
12
|
end
|
13
13
|
|
14
|
-
|
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
|
data/lib/db2_query/logger.rb
CHANGED
@@ -12,17 +12,17 @@ module Db2Query
|
|
12
12
|
exception
|
13
13
|
end
|
14
14
|
|
15
|
-
def log(sql,
|
16
|
-
|
15
|
+
def log(sql, binds = [], args = [], &block)
|
16
|
+
instrument(
|
17
17
|
"sql.active_record",
|
18
18
|
sql: sql,
|
19
|
-
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
|
-
|
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
|