activerecord-jdbc-alt-adapter 70.2.0-java → 71.0.0.alpha1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +135 -21
  3. data/.github/workflows/ruby.yml +10 -10
  4. data/.gitignore +1 -0
  5. data/.solargraph.yml +15 -0
  6. data/Gemfile +17 -4
  7. data/README.md +7 -3
  8. data/RUNNING_TESTS.md +36 -0
  9. data/activerecord-jdbc-adapter.gemspec +2 -2
  10. data/activerecord-jdbc-alt-adapter.gemspec +1 -1
  11. data/lib/arjdbc/abstract/connection_management.rb +23 -10
  12. data/lib/arjdbc/abstract/core.rb +5 -6
  13. data/lib/arjdbc/abstract/database_statements.rb +35 -25
  14. data/lib/arjdbc/abstract/statement_cache.rb +1 -6
  15. data/lib/arjdbc/abstract/transaction_support.rb +37 -9
  16. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  17. data/lib/arjdbc/jdbc/column.rb +0 -34
  18. data/lib/arjdbc/jdbc/connection_methods.rb +1 -1
  19. data/lib/arjdbc/mssql/adapter.rb +93 -80
  20. data/lib/arjdbc/mssql/column.rb +1 -0
  21. data/lib/arjdbc/mssql/connection_methods.rb +7 -55
  22. data/lib/arjdbc/mssql/database_statements.rb +182 -71
  23. data/lib/arjdbc/mssql/explain_support.rb +8 -5
  24. data/lib/arjdbc/mssql/schema_creation.rb +1 -1
  25. data/lib/arjdbc/mssql/schema_definitions.rb +10 -0
  26. data/lib/arjdbc/mssql/schema_statements.rb +19 -11
  27. data/lib/arjdbc/mssql/server_version.rb +56 -0
  28. data/lib/arjdbc/mssql/utils.rb +23 -9
  29. data/lib/arjdbc/mysql/adapter.rb +64 -22
  30. data/lib/arjdbc/sqlite3/adapter.rb +218 -135
  31. data/lib/arjdbc/sqlite3/column.rb +103 -0
  32. data/lib/arjdbc/sqlite3/connection_methods.rb +7 -2
  33. data/lib/arjdbc/tasks/mssql_database_tasks.rb +9 -5
  34. data/lib/arjdbc/version.rb +1 -1
  35. data/rakelib/02-test.rake +1 -1
  36. data/rakelib/rails.rake +2 -0
  37. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +4 -2
  38. data/src/java/arjdbc/sqlite3/SQLite3RubyJdbcConnection.java +2 -1
  39. metadata +9 -12
  40. data/lib/arel/visitors/sql_server/ng42.rb +0 -294
  41. data/lib/arel/visitors/sql_server.rb +0 -124
  42. data/lib/arjdbc/mssql/limit_helpers.rb +0 -231
  43. data/lib/arjdbc/mssql/lock_methods.rb +0 -77
  44. data/lib/arjdbc/mssql/old_adapter.rb +0 -804
  45. 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