activerecord-jdbc-alt-adapter 70.2.0-java → 71.0.0.alpha2-java
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/.github/workflows/main.yml +141 -24
- data/.github/workflows/ruby.yml +12 -12
- data/.gitignore +7 -3
- data/.solargraph.yml +15 -0
- data/Gemfile +17 -4
- data/README.md +7 -3
- data/RUNNING_TESTS.md +36 -0
- data/activerecord-jdbc-adapter.gemspec +2 -2
- data/activerecord-jdbc-alt-adapter.gemspec +1 -1
- data/lib/arjdbc/abstract/connection_management.rb +26 -10
- data/lib/arjdbc/abstract/core.rb +5 -12
- data/lib/arjdbc/abstract/database_statements.rb +35 -25
- data/lib/arjdbc/abstract/statement_cache.rb +2 -7
- data/lib/arjdbc/abstract/transaction_support.rb +37 -22
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/column.rb +0 -34
- data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
- data/lib/arjdbc/mssql/adapter.rb +101 -79
- data/lib/arjdbc/mssql/column.rb +1 -0
- data/lib/arjdbc/mssql/connection_methods.rb +7 -55
- data/lib/arjdbc/mssql/database_statements.rb +182 -71
- data/lib/arjdbc/mssql/explain_support.rb +8 -5
- data/lib/arjdbc/mssql/schema_creation.rb +1 -1
- data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
- data/lib/arjdbc/mssql/schema_statements.rb +25 -14
- data/lib/arjdbc/mssql/server_version.rb +56 -0
- data/lib/arjdbc/mssql/utils.rb +23 -9
- data/lib/arjdbc/mysql/adapter.rb +104 -27
- data/lib/arjdbc/postgresql/adapter.rb +71 -44
- data/lib/arjdbc/postgresql/oid_types.rb +8 -27
- data/lib/arjdbc/postgresql/schema_statements.rb +57 -0
- data/lib/arjdbc/sqlite3/adapter.rb +205 -147
- data/lib/arjdbc/sqlite3/column.rb +103 -0
- data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
- data/lib/arjdbc/tasks/mssql_database_tasks.rb +9 -5
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/02-test.rake +1 -1
- data/rakelib/rails.rake +2 -0
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +3 -1
- data/src/java/arjdbc/mysql/MySQLRubyJdbcConnection.java +11 -0
- data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
- metadata +10 -12
- data/lib/arel/visitors/sql_server/ng42.rb +0 -294
- data/lib/arel/visitors/sql_server.rb +0 -124
- data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
- data/lib/arjdbc/mssql/lock_methods.rb +0 -77
- data/lib/arjdbc/mssql/old_adapter.rb +0 -804
- data/lib/arjdbc/mssql/old_column.rb +0 -200
@@ -1,231 +0,0 @@
|
|
1
|
-
module ArJdbc
|
2
|
-
module MSSQL
|
3
|
-
module LimitHelpers
|
4
|
-
|
5
|
-
# @private
|
6
|
-
FIND_SELECT = /\b(SELECT(\s+DISTINCT)?)\b(.*)/mi
|
7
|
-
# @private
|
8
|
-
FIND_AGGREGATE_FUNCTION = /(AVG|COUNT|COUNT_BIG|MAX|MIN|SUM|STDDEV|STDEVP|VAR|VARP)\(/i
|
9
|
-
|
10
|
-
# @private
|
11
|
-
module SqlServerReplaceLimitOffset
|
12
|
-
|
13
|
-
GROUP_BY = 'GROUP BY'
|
14
|
-
ORDER_BY = 'ORDER BY'
|
15
|
-
|
16
|
-
module_function
|
17
|
-
|
18
|
-
def replace_limit_offset!(sql, limit, offset, order)
|
19
|
-
offset ||= 0
|
20
|
-
|
21
|
-
if match = FIND_SELECT.match(sql)
|
22
|
-
select, distinct, rest_of_query = match[1], match[2], match[3]
|
23
|
-
rest_of_query.strip!
|
24
|
-
end
|
25
|
-
rest_of_query[0] = '*' if rest_of_query[0...1] == '1' && rest_of_query !~ /1 AS/i
|
26
|
-
if rest_of_query[0...1] == '*'
|
27
|
-
from_table = Utils.get_table_name(rest_of_query, true)
|
28
|
-
rest_of_query = "#{from_table}.#{rest_of_query}"
|
29
|
-
end
|
30
|
-
|
31
|
-
# Ensure correct queries if the rest_of_query contains a 'GROUP BY'. Otherwise the following error occurs:
|
32
|
-
# ActiveRecord::StatementInvalid: ActiveRecord::JDBCError: Column 'users.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
|
33
|
-
# SELECT t.* FROM ( SELECT ROW_NUMBER() OVER(ORDER BY users.id) AS _row_num, [users].[lft], COUNT([users].[lft]) FROM [users] GROUP BY [users].[lft] HAVING COUNT([users].[lft]) > 1 ) AS t WHERE t._row_num BETWEEN 1 AND 1
|
34
|
-
if i = ( rest_of_query.rindex(GROUP_BY) || rest_of_query.rindex('group by') )
|
35
|
-
# Do not catch 'GROUP BY' statements from sub-selects, indicated
|
36
|
-
# by more closing than opening brackets after the last group by.
|
37
|
-
rest_after_last_group_by = rest_of_query[i..-1]
|
38
|
-
opening_brackets_count = rest_after_last_group_by.count('(')
|
39
|
-
closing_brackets_count = rest_after_last_group_by.count(')')
|
40
|
-
|
41
|
-
if opening_brackets_count == closing_brackets_count
|
42
|
-
order_start = order.strip[0, 8]; order_start.upcase!
|
43
|
-
if order_start == ORDER_BY && order.match(FIND_AGGREGATE_FUNCTION)
|
44
|
-
# do nothing
|
45
|
-
elsif order.count(',') == 0
|
46
|
-
order.gsub!(/ORDER +BY +([^\s]+)(\s+ASC|\s+DESC)?/i, 'ORDER BY MIN(\1)\2')
|
47
|
-
else
|
48
|
-
raise("can not handle multiple order conditions (#{order.inspect}) in #{sql.inspect}")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
if distinct # select =~ /DISTINCT/i
|
54
|
-
order = order.gsub(/(\[[a-z0-9_]+\]|[a-z0-9_]+)\./, 't.')
|
55
|
-
new_sql = "SELECT t.* FROM "
|
56
|
-
new_sql << "( SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, t.* FROM (#{select} #{rest_of_query}) AS t ) AS t"
|
57
|
-
append_limit_row_num_clause(new_sql, limit, offset)
|
58
|
-
else
|
59
|
-
select_columns_before_from = rest_of_query.gsub(/FROM.*/, '').strip
|
60
|
-
only_one_column = !select_columns_before_from.include?(',')
|
61
|
-
only_one_id_column = only_one_column && (select_columns_before_from.ends_with?('.id') || select_columns_before_from.ends_with?('.[id]'))
|
62
|
-
|
63
|
-
if only_one_id_column
|
64
|
-
# If there's only one id column a subquery will be created which only contains this column
|
65
|
-
new_sql = "#{select} t.id FROM "
|
66
|
-
else
|
67
|
-
# All selected columns are used
|
68
|
-
new_sql = "#{select} t.* FROM "
|
69
|
-
end
|
70
|
-
new_sql << "( SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, #{rest_of_query} ) AS t"
|
71
|
-
append_limit_row_num_clause(new_sql, limit, offset)
|
72
|
-
end
|
73
|
-
|
74
|
-
sql.replace new_sql
|
75
|
-
end
|
76
|
-
|
77
|
-
def append_limit_row_num_clause(sql, limit, offset)
|
78
|
-
if limit
|
79
|
-
start_row = offset + 1; end_row = offset + limit.to_i
|
80
|
-
sql << " WHERE t._row_num BETWEEN #{start_row} AND #{end_row}"
|
81
|
-
else
|
82
|
-
sql << " WHERE t._row_num > #{offset}"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
# @private
|
89
|
-
module SqlServer2000ReplaceLimitOffset
|
90
|
-
|
91
|
-
module_function
|
92
|
-
|
93
|
-
def replace_limit_offset!(sql, limit, offset, order)
|
94
|
-
if limit
|
95
|
-
offset ||= 0
|
96
|
-
start_row = offset + 1
|
97
|
-
end_row = offset + limit.to_i
|
98
|
-
|
99
|
-
if match = FIND_SELECT.match(sql)
|
100
|
-
select, distinct, rest_of_query = match[1], match[2], match[3]
|
101
|
-
end
|
102
|
-
#need the table name for avoiding amiguity
|
103
|
-
table_name = Utils.get_table_name(sql, true)
|
104
|
-
primary_key = get_primary_key(order, table_name)
|
105
|
-
|
106
|
-
#I am not sure this will cover all bases. but all the tests pass
|
107
|
-
if order[/ORDER/].nil?
|
108
|
-
new_order = "ORDER BY #{order}, [#{table_name}].[#{primary_key}]" if order.index("#{table_name}.#{primary_key}").nil?
|
109
|
-
else
|
110
|
-
new_order ||= order
|
111
|
-
end
|
112
|
-
|
113
|
-
if (start_row == 1) && (end_row ==1)
|
114
|
-
new_sql = "#{select} TOP 1 #{rest_of_query} #{new_order}"
|
115
|
-
sql.replace(new_sql)
|
116
|
-
else
|
117
|
-
# We are in deep trouble here. SQL Server does not have any kind of OFFSET build in.
|
118
|
-
# Only remaining solution is adding a where condition to be sure that the ID is not in SELECT TOP OFFSET FROM SAME_QUERY.
|
119
|
-
# To do so we need to extract each part of the query to insert our additional condition in the right place.
|
120
|
-
query_without_select = rest_of_query[/FROM/i=~ rest_of_query.. -1]
|
121
|
-
additional_condition = "#{table_name}.#{primary_key} NOT IN (#{select} TOP #{offset} #{table_name}.#{primary_key} #{query_without_select} #{new_order})"
|
122
|
-
|
123
|
-
# Extract the different parts of the query
|
124
|
-
having, group_by, where, from, selection = split_sql(rest_of_query, /having/i, /group by/i, /where/i, /from/i)
|
125
|
-
|
126
|
-
# Update the where part to add our additional condition
|
127
|
-
if where.blank?
|
128
|
-
where = "WHERE #{additional_condition}"
|
129
|
-
else
|
130
|
-
where = "#{where} AND #{additional_condition}"
|
131
|
-
end
|
132
|
-
|
133
|
-
# Replace the query to be our new customized query
|
134
|
-
sql.replace("#{select} TOP #{limit} #{selection} #{from} #{where} #{group_by} #{having} #{new_order}")
|
135
|
-
end
|
136
|
-
end
|
137
|
-
sql
|
138
|
-
end
|
139
|
-
|
140
|
-
# Split the rest_of_query into chunks based on regexs (applied from end of string to the beginning)
|
141
|
-
# The result is an array of regexs.size+1 elements (the last one being the remaining once everything was chopped away)
|
142
|
-
def split_sql(rest_of_query, *regexs)
|
143
|
-
results = Array.new
|
144
|
-
|
145
|
-
regexs.each do |regex|
|
146
|
-
if position = (regex =~ rest_of_query)
|
147
|
-
# Extract the matched string and chop the rest_of_query
|
148
|
-
matched = rest_of_query[position..-1]
|
149
|
-
rest_of_query = rest_of_query[0...position]
|
150
|
-
else
|
151
|
-
matched = nil
|
152
|
-
end
|
153
|
-
|
154
|
-
results << matched
|
155
|
-
end
|
156
|
-
results << rest_of_query
|
157
|
-
|
158
|
-
results
|
159
|
-
end
|
160
|
-
|
161
|
-
def get_primary_key(order, table_name) # table_name might be quoted
|
162
|
-
if order =~ /(\w*id\w*)/i
|
163
|
-
$1
|
164
|
-
else
|
165
|
-
unquoted_name = Utils.unquote_table_name(table_name)
|
166
|
-
model = descendants.find { |m| m.table_name == table_name || m.table_name == unquoted_name }
|
167
|
-
model ? model.primary_key : 'id'
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
private
|
172
|
-
|
173
|
-
if ActiveRecord::VERSION::MAJOR >= 3
|
174
|
-
def descendants; ::ActiveRecord::Base.descendants; end
|
175
|
-
else
|
176
|
-
def descendants; ::ActiveRecord::Base.send(:subclasses) end
|
177
|
-
end
|
178
|
-
|
179
|
-
end
|
180
|
-
|
181
|
-
private
|
182
|
-
|
183
|
-
if ::ActiveRecord::VERSION::MAJOR < 3
|
184
|
-
|
185
|
-
def setup_limit_offset!(version = nil)
|
186
|
-
if version.to_s == '2000' || sqlserver_2000?
|
187
|
-
extend SqlServer2000AddLimitOffset
|
188
|
-
else
|
189
|
-
extend SqlServerAddLimitOffset
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
else
|
194
|
-
|
195
|
-
def setup_limit_offset!(version = nil); end
|
196
|
-
|
197
|
-
end
|
198
|
-
|
199
|
-
# @private
|
200
|
-
module SqlServerAddLimitOffset
|
201
|
-
|
202
|
-
# @note Only needed with (non-AREL) ActiveRecord **2.3**.
|
203
|
-
# @see Arel::Visitors::SQLServer
|
204
|
-
def add_limit_offset!(sql, options)
|
205
|
-
if options[:limit]
|
206
|
-
order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
|
207
|
-
sql.sub!(/ ORDER BY.*$/i, '')
|
208
|
-
SqlServerReplaceLimitOffset.replace_limit_offset!(sql, options[:limit], options[:offset], order)
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
end if ::ActiveRecord::VERSION::MAJOR < 3
|
213
|
-
|
214
|
-
# @private
|
215
|
-
module SqlServer2000AddLimitOffset
|
216
|
-
|
217
|
-
# @note Only needed with (non-AREL) ActiveRecord **2.3**.
|
218
|
-
# @see Arel::Visitors::SQLServer
|
219
|
-
def add_limit_offset!(sql, options)
|
220
|
-
if options[:limit]
|
221
|
-
order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
|
222
|
-
sql.sub!(/ ORDER BY.*$/i, '')
|
223
|
-
SqlServer2000ReplaceLimitOffset.replace_limit_offset!(sql, options[:limit], options[:offset], order)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
end if ::ActiveRecord::VERSION::MAJOR < 3
|
228
|
-
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
@@ -1,77 +0,0 @@
|
|
1
|
-
require 'strscan'
|
2
|
-
|
3
|
-
module ArJdbc
|
4
|
-
module MSSQL
|
5
|
-
module LockMethods
|
6
|
-
|
7
|
-
# @private
|
8
|
-
SELECT_FROM_WHERE_RE = /\A(\s*SELECT\s.*?)(\sFROM\s)(.*?)(\sWHERE\s.*|)\Z/mi
|
9
|
-
|
10
|
-
# Microsoft SQL Server uses its own syntax for SELECT .. FOR UPDATE:
|
11
|
-
# SELECT .. FROM table1 WITH(ROWLOCK,UPDLOCK), table2 WITH(ROWLOCK,UPDLOCK) WHERE ..
|
12
|
-
#
|
13
|
-
# This does in-place modification of the passed-in string.
|
14
|
-
def add_lock!(sql, options)
|
15
|
-
if (lock = options[:lock]) && sql =~ /\A\s*SELECT/mi
|
16
|
-
# Check for and extract the :limit/:offset sub-query
|
17
|
-
if sql =~ /\A(\s*SELECT t\.\* FROM \()(.*)(\) AS t WHERE t._row_num BETWEEN \d+ AND \d+\s*)\Z/m
|
18
|
-
prefix, subselect, suffix = [$1, $2, $3]
|
19
|
-
add_lock!(subselect, options)
|
20
|
-
return sql.replace(prefix + subselect + suffix)
|
21
|
-
end
|
22
|
-
unless sql =~ SELECT_FROM_WHERE_RE
|
23
|
-
# If you get this error, this driver probably needs to be fixed.
|
24
|
-
raise NotImplementedError, "Don't know how to add_lock! to SQL statement: #{sql.inspect}"
|
25
|
-
end
|
26
|
-
select_clause, from_word, from_tables, where_clause = $1, $2, $3, $4
|
27
|
-
with_clause = lock.is_a?(String) ? " #{lock} " : " WITH(ROWLOCK,UPDLOCK) "
|
28
|
-
|
29
|
-
# Split the FROM clause into its constituent tables, and add the with clause after each one.
|
30
|
-
new_from_tables = []
|
31
|
-
scanner = StringScanner.new(from_tables)
|
32
|
-
until scanner.eos?
|
33
|
-
prev_pos = scanner.pos
|
34
|
-
if scanner.scan_until(/,|(INNER\s+JOIN|CROSS\s+JOIN|(LEFT|RIGHT|FULL)(\s+OUTER)?\s+JOIN)\s+/mi)
|
35
|
-
join_operand = scanner.pre_match[prev_pos..-1]
|
36
|
-
join_operator = scanner.matched
|
37
|
-
else
|
38
|
-
join_operand = scanner.rest
|
39
|
-
join_operator = ""
|
40
|
-
scanner.terminate
|
41
|
-
end
|
42
|
-
|
43
|
-
# At this point, we have something like:
|
44
|
-
# join_operand == "appointments "
|
45
|
-
# join_operator == "INNER JOIN "
|
46
|
-
# or:
|
47
|
-
# join_operand == "appointment_details AS d1 ON appointments.[id] = d1.[appointment_id]"
|
48
|
-
# join_operator == ""
|
49
|
-
if join_operand =~ /\A(.*)(\s+ON\s+.*)\Z/mi
|
50
|
-
table_spec, on_clause = $1, $2
|
51
|
-
else
|
52
|
-
table_spec = join_operand
|
53
|
-
on_clause = ""
|
54
|
-
end
|
55
|
-
|
56
|
-
# Add the "WITH(ROWLOCK,UPDLOCK)" option to the table specification
|
57
|
-
table_spec << with_clause unless table_spec =~ /\A\(\s*SELECT\s+/mi # HACK - this parser isn't so great
|
58
|
-
join_operand = table_spec + on_clause
|
59
|
-
|
60
|
-
# So now we have something like:
|
61
|
-
# join_operand == "appointments WITH(ROWLOCK,UPDLOCK) "
|
62
|
-
# join_operator == "INNER JOIN "
|
63
|
-
# or:
|
64
|
-
# join_operand == "appointment_details AS d1 WITH(ROWLOCK,UPDLOCK) ON appointments.[id] = d1.[appointment_id]"
|
65
|
-
# join_operator == ""
|
66
|
-
|
67
|
-
new_from_tables << join_operand
|
68
|
-
new_from_tables << join_operator
|
69
|
-
end
|
70
|
-
sql.replace( select_clause.to_s << from_word.to_s << new_from_tables.join << where_clause.to_s )
|
71
|
-
end
|
72
|
-
sql
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|