activerecord-jdbc-alt-adapter 70.2.0-java → 71.0.0.alpha2-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|