peasys-ruby 1.0.2 → 2.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2bfe718a25db8bdd2f49fa52d98d431b72bcc7615a8228b39e57d8e92b3d587
4
- data.tar.gz: 896d7f841636adec7f73a3b3f32beb5ede86267c27622b825d9c4acf9464d3e0
3
+ metadata.gz: 419d2e97864d0ff2a64d3680138f4d44159bff0645a14e985b52a368e0e031bf
4
+ data.tar.gz: 7c3e3fbf3805ae4b39979560886759198d56b2ce36b2536d69130db0002f41d4
5
5
  SHA512:
6
- metadata.gz: 0fb9b7343f3fdf459aff902176db299311b6d88a23457ac0fc1c88821cf3a716364d3eaf0fb82da1066b53972679450d053ecc32f3969d0bc3ec77a2dafcd9c1
7
- data.tar.gz: 2a8c6efd843b885428da0fa402f4d0b8bded3234a19700b26ea132429cef0169ac21f1f7fb0e830300bd8c8c8614b4c7e4f0ad0beebfd812c33bb3680fc9fab4
6
+ metadata.gz: 10f2c9b8313fbad9130bbd2b075642046ee42aec1c4b3f169037def314a76db368bd769bf6bc50087fd48a790aea02e3eb0477ca484fc49a65c90e4e4e6ec0cd
7
+ data.tar.gz: 0a37b235628fbc62a89e6362c4a49b73fd21d93bb2c9b86684f4e32c27ec14434f3821b9c94a6a608143f0f1adc06351575a2a68ff4c62d51ea4ff61e484e874
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Peasys
6
+ class Column < ConnectionAdapters::Column
7
+ attr_reader :auto_increment
8
+
9
+ def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, comment: nil, auto_increment: false, **)
10
+ super(name, default, sql_type_metadata, null, default_function, comment: comment)
11
+ @auto_increment = auto_increment
12
+ end
13
+
14
+ def auto_increment?
15
+ @auto_increment
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Peasys
6
+ module DatabaseStatements
7
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
8
+ :begin, :commit, :rollback, :savepoint, :release, :set
9
+ ) # :nodoc:
10
+ private_constant :READ_QUERY
11
+
12
+ def write_query?(sql)
13
+ !READ_QUERY.match?(sql)
14
+ rescue ArgumentError
15
+ !READ_QUERY.match?(sql.b)
16
+ end
17
+
18
+ def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false)
19
+ sql = transform_query(sql)
20
+ check_if_write_query(sql)
21
+ mark_transaction_written_if_write(sql)
22
+
23
+ type_casted_binds = type_casted_binds(binds)
24
+ bound_sql = bind_params_to_sql(sql, type_casted_binds)
25
+
26
+ log(bound_sql, name, binds, type_casted_binds, async: async) do |notification_payload|
27
+ with_raw_connection do |conn|
28
+ columns, rows = execute_and_extract(conn, bound_sql)
29
+ verified!
30
+ result = build_result(columns: columns, rows: rows)
31
+ notification_payload[:row_count] = result.length if notification_payload
32
+ result
33
+ end
34
+ end
35
+ end
36
+
37
+ def execute(sql, name = nil, allow_retry: false)
38
+ sql = transform_query(sql)
39
+ check_if_write_query(sql)
40
+ mark_transaction_written_if_write(sql)
41
+
42
+ log(sql, name) do
43
+ with_raw_connection do |conn|
44
+ execute_and_extract(conn, sql)
45
+ verified!
46
+ end
47
+ end
48
+ end
49
+
50
+ def exec_delete(sql, name = nil, binds = [])
51
+ sql = transform_query(sql)
52
+ type_casted_binds = type_casted_binds(binds)
53
+ bound_sql = bind_params_to_sql(sql, type_casted_binds)
54
+
55
+ log(bound_sql, name, binds, type_casted_binds) do
56
+ with_raw_connection do |conn|
57
+ keyword = bound_sql.strip.split(/\s+/, 2).first.upcase
58
+ case keyword
59
+ when "DELETE"
60
+ response = conn.execute_delete(bound_sql)
61
+ raise_if_failed(response, bound_sql)
62
+ verified!
63
+ response.row_count
64
+ when "UPDATE"
65
+ response = conn.execute_update(bound_sql)
66
+ raise_if_failed(response, bound_sql)
67
+ verified!
68
+ response.row_count
69
+ else
70
+ execute_and_extract(conn, bound_sql)
71
+ verified!
72
+ 0
73
+ end
74
+ end
75
+ end
76
+ end
77
+ alias exec_update exec_delete
78
+
79
+ def begin_db_transaction
80
+ # DB2 on IBM i uses implicit transactions.
81
+ # We send a SET TRANSACTION ISOLATION LEVEL to mark the start.
82
+ # Alternatively, we just track state -- COMMIT/ROLLBACK are explicit.
83
+ end
84
+
85
+ def commit_db_transaction
86
+ with_raw_connection do |conn|
87
+ conn.execute_sql("COMMIT")
88
+ end
89
+ end
90
+
91
+ def exec_rollback_db_transaction
92
+ with_raw_connection do |conn|
93
+ conn.execute_sql("ROLLBACK")
94
+ end
95
+ end
96
+
97
+ def default_sequence_name(table_name, _column = nil)
98
+ nil
99
+ end
100
+
101
+ private
102
+
103
+ # Transposes PeaSelectResponse columnar format to row-based format.
104
+ #
105
+ # PeaClient returns: { "COL1" => ["a","b"], "COL2" => [1, 2] }
106
+ # AR expects: columns=["col1","col2"], rows=[["a",1],["b",2]]
107
+ def transpose_columnar_result(response)
108
+ columns = response.columns_name.map { |c| c.to_s.downcase }
109
+ row_count = response.row_count.to_i
110
+ return [columns, []] if row_count == 0
111
+
112
+ rows = Array.new(row_count) { Array.new(columns.size) }
113
+
114
+ response.columns_name.each_with_index do |original_name, col_idx|
115
+ values = response.result[original_name] || []
116
+ values.each_with_index do |val, row_idx|
117
+ rows[row_idx][col_idx] = val
118
+ end
119
+ end
120
+
121
+ [columns, rows]
122
+ end
123
+
124
+ # Routes SQL to the appropriate PeaClient method based on the statement keyword.
125
+ def execute_and_extract(conn, sql)
126
+ keyword = sql.strip.split(/\s+/, 2).first.upcase
127
+
128
+ case keyword
129
+ when "SELECT", "WITH"
130
+ response = conn.execute_select(sql)
131
+ raise_if_failed(response, sql)
132
+ transpose_columnar_result(response)
133
+ when "INSERT"
134
+ response = conn.execute_insert(sql)
135
+ raise_if_failed(response, sql)
136
+ [[], []]
137
+ when "UPDATE"
138
+ response = conn.execute_update(sql)
139
+ raise_if_failed(response, sql)
140
+ [[], []]
141
+ when "DELETE"
142
+ response = conn.execute_delete(sql)
143
+ raise_if_failed(response, sql)
144
+ [[], []]
145
+ when "CREATE"
146
+ response = conn.execute_create(sql)
147
+ raise_if_failed(response, sql)
148
+ [[], []]
149
+ when "ALTER"
150
+ response = conn.execute_alter(sql, false)
151
+ raise_if_failed(response, sql)
152
+ [[], []]
153
+ when "DROP"
154
+ response = conn.execute_drop(sql)
155
+ raise_if_failed(response, sql)
156
+ [[], []]
157
+ else
158
+ # SET, COMMIT, ROLLBACK, GRANT, REVOKE, etc.
159
+ response = conn.execute_sql(sql)
160
+ raise_if_failed(response, sql)
161
+ [[], []]
162
+ end
163
+ end
164
+
165
+ def raise_if_failed(response, sql)
166
+ return if response.has_succeeded
167
+
168
+ raise ActiveRecord::StatementInvalid.new(
169
+ "DB2 Error [#{response.sql_state}]: #{response.sql_message}",
170
+ sql: sql,
171
+ connection_pool: @pool
172
+ )
173
+ end
174
+
175
+ # PeaClient has no prepared statement support.
176
+ # Substitutes bind parameters (?) directly into SQL with proper quoting.
177
+ def bind_params_to_sql(sql, type_casted_binds)
178
+ return sql if type_casted_binds.empty?
179
+
180
+ binds = type_casted_binds.dup
181
+ sql.gsub("?") do
182
+ val = binds.shift
183
+ if val.nil?
184
+ "NULL"
185
+ else
186
+ quote(val)
187
+ end
188
+ end
189
+ end
190
+
191
+ def last_inserted_id(result)
192
+ with_raw_connection do |conn|
193
+ response = conn.execute_select(
194
+ "SELECT IDENTITY_VAL_LOCAL() AS LAST_ID FROM SYSIBM.SYSDUMMY1"
195
+ )
196
+ if response.has_succeeded && response.row_count.to_i > 0
197
+ val = response.result["LAST_ID"]&.first
198
+ val&.to_i
199
+ end
200
+ end
201
+ end
202
+
203
+ def build_truncate_statement(table_name)
204
+ "DELETE FROM #{quote_table_name(table_name)}"
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Peasys
6
+ module Quoting
7
+ extend ActiveSupport::Concern
8
+
9
+ QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
10
+ QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
11
+
12
+ module ClassMethods # :nodoc:
13
+ def column_name_matcher
14
+ /
15
+ \A
16
+ (
17
+ (?:
18
+ # "TABLE"."COLUMN" | function(one or two args)
19
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
20
+ )
21
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
22
+ )
23
+ (?:\s*,\s*\g<1>)*
24
+ \z
25
+ /ix
26
+ end
27
+
28
+ def column_name_with_order_matcher
29
+ /
30
+ \A
31
+ (
32
+ (?:
33
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
34
+ )
35
+ (?:\s+ASC|\s+DESC)?
36
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
37
+ )
38
+ (?:\s*,\s*\g<1>)*
39
+ \z
40
+ /ix
41
+ end
42
+
43
+ def quote_column_name(name)
44
+ QUOTED_COLUMN_NAMES[name] ||= %("#{name.to_s.gsub('"', '""').upcase}").freeze
45
+ end
46
+
47
+ def quote_table_name(name)
48
+ QUOTED_TABLE_NAMES[name] ||= begin
49
+ parts = name.to_s.split(".")
50
+ parts.map { |p| %("#{p.gsub('"', '""').upcase}") }.join(".").freeze
51
+ end
52
+ end
53
+ end
54
+
55
+ def quote_string(s)
56
+ s.gsub("'", "''")
57
+ end
58
+
59
+ def quoted_true
60
+ "1"
61
+ end
62
+
63
+ def unquoted_true
64
+ 1
65
+ end
66
+
67
+ def quoted_false
68
+ "0"
69
+ end
70
+
71
+ def unquoted_false
72
+ 0
73
+ end
74
+
75
+ def quoted_date(value)
76
+ if value.acts_like?(:time)
77
+ value.strftime("%Y-%m-%d %H:%M:%S.%6N")
78
+ else
79
+ value.to_s
80
+ end
81
+ end
82
+
83
+ def quote_table_name_for_assignment(_table, attr)
84
+ quote_column_name(attr)
85
+ end
86
+
87
+ private
88
+
89
+ def type_cast(value)
90
+ case value
91
+ when true then 1
92
+ when false then 0
93
+ else super
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Peasys
6
+ class SchemaCreation < ConnectionAdapters::SchemaCreation
7
+ private
8
+
9
+ def add_column_options!(sql, options)
10
+ if options[:auto_increment]
11
+ sql << " GENERATED BY DEFAULT AS IDENTITY"
12
+ end
13
+
14
+ if options[:null] == false
15
+ sql << " NOT NULL"
16
+ end
17
+
18
+ if options.key?(:default) && !options[:auto_increment]
19
+ sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}"
20
+ end
21
+
22
+ sql
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Peasys
6
+ class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Peasys
6
+ class SchemaDumper < ConnectionAdapters::SchemaDumper
7
+ private
8
+
9
+ def header(stream)
10
+ stream.puts <<~HEADER
11
+ # This file is auto-generated from the current state of the database. Instead
12
+ # of editing this file, please use the migrations feature of Active Record to
13
+ # incrementally modify your database, and then regenerate this schema definition.
14
+ #
15
+ # This file is the source Rails uses to define your schema when running `bin/rails
16
+ # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
17
+ # be faster and is potentially less error prone than running all of your
18
+ # migrations from scratch. Old migrations may fail to apply correctly if those
19
+ # migrations use external dependencies or application code.
20
+ #
21
+ # It's strongly recommended that you check this file into your version control system.
22
+
23
+ ActiveRecord::Schema[#{ActiveRecord::Migration.current_version}].define(version: #{define_params}) do
24
+ HEADER
25
+ end
26
+
27
+ # DB2 identity columns should be dumped as `bigint` or `integer` primary keys
28
+ # with no explicit default (Rails infers the auto-increment behavior).
29
+ def column_spec(column)
30
+ spec = super
31
+ spec[:auto_increment] = "true" if column.respond_to?(:auto_increment?) && column.auto_increment?
32
+ spec
33
+ end
34
+
35
+ # Override to use DB2-specific type names in the dump
36
+ def schema_type(column)
37
+ case column.sql_type
38
+ when /\ACLOB\z/i
39
+ :text
40
+ when /\ABLOB\z/i
41
+ :binary
42
+ when /\ASMALLINT\z/i
43
+ :boolean
44
+ when /\ATIMESTAMP\z/i
45
+ :datetime
46
+ when /\ADOUBLE\z/i
47
+ :float
48
+ when /\ABIGINT\z/i
49
+ :bigint
50
+ else
51
+ super
52
+ end
53
+ end
54
+
55
+ # Override to handle DB2's specific schema limit/precision/scale
56
+ def schema_limit(column)
57
+ case column.sql_type
58
+ when /\ASMALLINT\z/i, /\AINTEGER\z/i, /\ABIGINT\z/i,
59
+ /\ACLOB\z/i, /\ABLOB\z/i, /\ADOUBLE\z/i, /\ATIMESTAMP\z/i
60
+ nil
61
+ else
62
+ super
63
+ end
64
+ end
65
+
66
+ def schema_precision(column)
67
+ case column.sql_type
68
+ when /\ATIMESTAMP\z/i
69
+ nil
70
+ else
71
+ super
72
+ end
73
+ end
74
+
75
+ # DB2 defaults that should not be dumped
76
+ def schema_default(column)
77
+ return nil if column.respond_to?(:auto_increment?) && column.auto_increment?
78
+
79
+ super
80
+ end
81
+
82
+ def explicit_primary_key_default?(column)
83
+ column.respond_to?(:auto_increment?) && column.auto_increment?
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end