activerecord-oracle_enhanced-adapter 7.2.1-java → 8.1.0-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/History.md +1 -7
- data/README.md +85 -1
- data/VERSION +1 -1
- data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +2 -2
- data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +17 -2
- data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +66 -36
- data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +1 -1
- data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +82 -14
- data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +12 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +8 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +1 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +5 -4
- data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +28 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +25 -4
- data/spec/active_record/connection_adapters/oracle_enhanced/compatibility_spec.rb +6 -2
- data/spec/active_record/connection_adapters/oracle_enhanced/composite_spec.rb +84 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +19 -4
- data/spec/active_record/connection_adapters/oracle_enhanced/dbconsole_spec.rb +63 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +29 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +12 -1
- data/spec/active_record/oracle_enhanced/type/integer_spec.rb +11 -9
- data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +1 -2
- data/spec/active_record/oracle_enhanced/type/text_spec.rb +51 -0
- data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +34 -4
- data/spec/spec_helper.rb +13 -2
- metadata +10 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0228707379728bbeb32011a85bd19154279fb442d234e08ef5a5dc2445b391d3'
|
|
4
|
+
data.tar.gz: 05e7d21d330955e2227120e7dc2b1c59fd0de7e8feb162fb3c6a2acc514c692f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e0531e2d250815dc629d7c78871bb30bad58687a47429e34b6ab024cd713a9a1b3f60c855a571bd2090811afea82cfd0618778c4ecba901960c842cbb676391
|
|
7
|
+
data.tar.gz: a357e405eaa4a7b226c02638b968c0cf92fbbc5404c573de144c92361eb79a38f864e7f45b1e3837c5fd9801949d3a402982e092e13b85b3f886557cc9d5f3cb
|
data/History.md
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Changes and bug fixes
|
|
4
|
-
* Accept `allow_retry` keyword argument in JDBC connection `exec`
|
|
5
|
-
* Add `reset` method to JDBC connection
|
|
6
|
-
* Skip `ObjectSpace::WeakMap` workaround on JRuby that implements `#values`
|
|
7
|
-
* Run tests on JRuby 9.4.15.0
|
|
1
|
+
This file is no longer being updated. For the latest updates and release information, please see: https://github.com/rsim/oracle-enhanced/releases
|
|
8
2
|
|
|
9
3
|
## 7.2.0 / 2025-06-23
|
|
10
4
|
|
data/README.md
CHANGED
|
@@ -6,10 +6,30 @@ Oracle enhanced adapter for ActiveRecord
|
|
|
6
6
|
DESCRIPTION
|
|
7
7
|
-----------
|
|
8
8
|
|
|
9
|
-
Oracle enhanced ActiveRecord adapter provides Oracle database access from Ruby on Rails applications. Oracle enhanced adapter can be used from Ruby on Rails versions between 2.3.x and
|
|
9
|
+
Oracle enhanced ActiveRecord adapter provides Oracle database access from Ruby on Rails applications. Oracle enhanced adapter can be used from Ruby on Rails versions between 2.3.x and 8.1 and it is working with Oracle database versions 10g and higher
|
|
10
10
|
|
|
11
11
|
INSTALLATION
|
|
12
12
|
------------
|
|
13
|
+
### Rails 8.1
|
|
14
|
+
|
|
15
|
+
Oracle enhanced adapter version 8.1 supports Rails 8.1
|
|
16
|
+
When using Ruby on Rails version 8.1 then in Gemfile include
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
# Use oracle as the database for Active Record
|
|
20
|
+
gem 'activerecord-oracle_enhanced-adapter', '~> 8.1.0'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Rails 8.0
|
|
24
|
+
|
|
25
|
+
Oracle enhanced adapter version 8.0 supports Rails 8.0
|
|
26
|
+
When using Ruby on Rails version 8.0 then in Gemfile include
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
# Use oracle as the database for Active Record
|
|
30
|
+
gem 'activerecord-oracle_enhanced-adapter', '~> 8.0.0'
|
|
31
|
+
```
|
|
32
|
+
|
|
13
33
|
### Rails 7.2
|
|
14
34
|
|
|
15
35
|
Oracle enhanced adapter version 7.2 supports Rails 7.2
|
|
@@ -823,6 +843,70 @@ complete.
|
|
|
823
843
|
|
|
824
844
|
See the **Timeouts** section above.
|
|
825
845
|
|
|
846
|
+
Development with Devcontainer
|
|
847
|
+
------------------------------
|
|
848
|
+
|
|
849
|
+
This project includes a devcontainer configuration that provides a complete development environment with Oracle Database and all necessary dependencies pre-configured. The devcontainer supports both x64 and ARM64 architectures.
|
|
850
|
+
|
|
851
|
+
### Prerequisites
|
|
852
|
+
|
|
853
|
+
- [Docker](https://www.docker.com/get-started) installed and running
|
|
854
|
+
- [VS Code](https://code.visualstudio.com/) with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
|
855
|
+
|
|
856
|
+
### Getting Started
|
|
857
|
+
|
|
858
|
+
1. Clone the repository:
|
|
859
|
+
```bash
|
|
860
|
+
git clone https://github.com/rsim/oracle-enhanced.git
|
|
861
|
+
cd oracle-enhanced
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
2. Open the project in VS Code:
|
|
865
|
+
```bash
|
|
866
|
+
code .
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
3. When prompted, click "Reopen in Container" or use the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) and select "Dev Containers: Reopen in Container"
|
|
870
|
+
|
|
871
|
+
4. VS Code will build and start the development environment automatically. This includes:
|
|
872
|
+
- Ruby 3.4
|
|
873
|
+
- Oracle Database Free
|
|
874
|
+
- Oracle Instant Client 23.8
|
|
875
|
+
- All required gems installed via `bundle install`
|
|
876
|
+
|
|
877
|
+
### What's Included
|
|
878
|
+
|
|
879
|
+
The devcontainer provides:
|
|
880
|
+
|
|
881
|
+
- **Ruby**: 3.4
|
|
882
|
+
- **Oracle Database**: Oracle Database Free (23c)
|
|
883
|
+
- **Oracle Instant Client**: Version 23.8
|
|
884
|
+
- **Database Configuration**:
|
|
885
|
+
- Database: `FREEPDB1`
|
|
886
|
+
- System password: `Oracle18`
|
|
887
|
+
- TNS configuration in `ci/network/admin`
|
|
888
|
+
|
|
889
|
+
### Database Access
|
|
890
|
+
|
|
891
|
+
The Oracle database is automatically started and configured with:
|
|
892
|
+
- Port: `1521` (forwarded from container)
|
|
893
|
+
- Service Name: `FREEPDB1`
|
|
894
|
+
- System Password: `Oracle18`
|
|
895
|
+
- Test users are created automatically from `spec/support/create_oracle_enhanced_users.sql`
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
### Running Tests
|
|
899
|
+
|
|
900
|
+
Once the devcontainer is running, you can run tests directly:
|
|
901
|
+
|
|
902
|
+
```bash
|
|
903
|
+
# Run all tests
|
|
904
|
+
bundle exec rspec
|
|
905
|
+
|
|
906
|
+
# Run specific test files
|
|
907
|
+
bundle exec rspec spec/test_file_spec.rb
|
|
908
|
+
```
|
|
909
|
+
|
|
826
910
|
RUNNING TESTS
|
|
827
911
|
-------------
|
|
828
912
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
8.1.0
|
|
@@ -6,8 +6,8 @@ module ActiveRecord
|
|
|
6
6
|
class Column < ActiveRecord::ConnectionAdapters::Column
|
|
7
7
|
delegate :virtual, to: :sql_type_metadata, allow_nil: true
|
|
8
8
|
|
|
9
|
-
def initialize(name, default, sql_type_metadata = nil, null = true, comment: nil) # :nodoc:
|
|
10
|
-
super(name, default, sql_type_metadata, null, comment: comment)
|
|
9
|
+
def initialize(name, cast_type, default, sql_type_metadata = nil, null = true, comment: nil) # :nodoc:
|
|
10
|
+
super(name, cast_type, default, sql_type_metadata, null, comment: comment)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def virtual?
|
|
@@ -330,8 +330,23 @@ module ActiveRecord
|
|
|
330
330
|
# Add context index condition.
|
|
331
331
|
def contains(column, query, options = {})
|
|
332
332
|
score_label = options[:label].to_i || 1
|
|
333
|
-
|
|
334
|
-
|
|
333
|
+
quoted_column = connection.quote_table_name(column)
|
|
334
|
+
|
|
335
|
+
# Create an Arel node for the CONTAINS function
|
|
336
|
+
contains_node = Arel::Nodes::NamedFunction.new(
|
|
337
|
+
"CONTAINS",
|
|
338
|
+
[
|
|
339
|
+
Arel::Nodes::SqlLiteral.new(quoted_column),
|
|
340
|
+
Arel::Nodes::BindParam.new(query),
|
|
341
|
+
Arel::Nodes::SqlLiteral.new(score_label.to_s)
|
|
342
|
+
]
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# Create comparison node: CONTAINS(...) > 0
|
|
346
|
+
condition = Arel::Nodes::GreaterThan.new(contains_node, Arel::Nodes::SqlLiteral.new("0"))
|
|
347
|
+
|
|
348
|
+
# Create the where clause and order by score
|
|
349
|
+
where(condition).order(Arel.sql("SCORE(#{score_label}) DESC"))
|
|
335
350
|
end
|
|
336
351
|
end
|
|
337
352
|
end
|
|
@@ -8,58 +8,79 @@ module ActiveRecord
|
|
|
8
8
|
#
|
|
9
9
|
# see: abstract/database_statements.rb
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
|
12
|
+
:close, :declare, :fetch, :move, :set, :show
|
|
13
|
+
) # :nodoc:
|
|
14
|
+
private_constant :READ_QUERY
|
|
15
|
+
|
|
16
|
+
def write_query?(sql) # :nodoc:
|
|
17
|
+
!READ_QUERY.match?(sql)
|
|
18
|
+
rescue ArgumentError # Invalid encoding
|
|
19
|
+
!READ_QUERY.match?(sql.b)
|
|
20
|
+
end
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
# Executes a SQL statement
|
|
23
|
+
def execute(...)
|
|
24
|
+
super
|
|
16
25
|
end
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
# Low level execution of a SQL statement on the connection returning adapter specific result object.
|
|
28
|
+
def raw_execute(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: false)
|
|
29
|
+
sql = preprocess_query(sql)
|
|
20
30
|
|
|
21
31
|
type_casted_binds = type_casted_binds(binds)
|
|
32
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
|
33
|
+
log(sql, name, binds, type_casted_binds, async: async) do
|
|
34
|
+
cursor = nil
|
|
35
|
+
cached = false
|
|
36
|
+
with_retry do
|
|
37
|
+
if binds.nil? || binds.empty?
|
|
38
|
+
cursor = conn.prepare(sql)
|
|
39
|
+
else
|
|
40
|
+
unless @statements.key? sql
|
|
41
|
+
@statements[sql] = conn.prepare(sql)
|
|
42
|
+
end
|
|
22
43
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
cached = false
|
|
26
|
-
with_retry do
|
|
27
|
-
if without_prepared_statement?(binds)
|
|
28
|
-
cursor = _connection.prepare(sql)
|
|
29
|
-
else
|
|
30
|
-
unless @statements.key? sql
|
|
31
|
-
@statements[sql] = _connection.prepare(sql)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
cursor = @statements[sql]
|
|
35
|
-
|
|
36
|
-
cursor.bind_params(type_casted_binds)
|
|
44
|
+
cursor = @statements[sql]
|
|
45
|
+
cursor.bind_params(type_casted_binds)
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
cached = true
|
|
48
|
+
end
|
|
49
|
+
cursor.exec
|
|
39
50
|
end
|
|
40
51
|
|
|
41
|
-
cursor.exec
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
if (name == "EXPLAIN") && sql.start_with?("EXPLAIN")
|
|
45
|
-
res = true
|
|
46
|
-
else
|
|
47
52
|
columns = cursor.get_col_names.map do |col_name|
|
|
48
53
|
oracle_downcase(col_name)
|
|
49
54
|
end
|
|
55
|
+
|
|
50
56
|
rows = []
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
if cursor.select_statement?
|
|
58
|
+
fetch_options = { get_lob_value: (name != "Writable Large Object") }
|
|
59
|
+
while row = cursor.fetch(fetch_options)
|
|
60
|
+
rows << row
|
|
61
|
+
end
|
|
54
62
|
end
|
|
55
|
-
|
|
63
|
+
|
|
64
|
+
affected_rows_count = cursor.row_count
|
|
65
|
+
|
|
66
|
+
cursor.close unless cached
|
|
67
|
+
|
|
68
|
+
{ columns: columns, rows: rows, affected_rows_count: affected_rows_count }
|
|
56
69
|
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
57
72
|
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
def cast_result(result)
|
|
74
|
+
if result.nil?
|
|
75
|
+
ActiveRecord::Result.empty
|
|
76
|
+
else
|
|
77
|
+
ActiveRecord::Result.new(result[:columns], result[:rows])
|
|
60
78
|
end
|
|
61
79
|
end
|
|
62
|
-
|
|
80
|
+
|
|
81
|
+
def affected_rows(result)
|
|
82
|
+
result[:affected_rows_count]
|
|
83
|
+
end
|
|
63
84
|
|
|
64
85
|
def supports_explain?
|
|
65
86
|
true
|
|
@@ -106,7 +127,7 @@ module ActiveRecord
|
|
|
106
127
|
cursor = nil
|
|
107
128
|
returning_id_col = returning_id_index = nil
|
|
108
129
|
with_retry do
|
|
109
|
-
if
|
|
130
|
+
if binds.nil? || binds.empty?
|
|
110
131
|
cursor = _connection.prepare(sql)
|
|
111
132
|
else
|
|
112
133
|
unless @statements.key?(sql)
|
|
@@ -146,7 +167,7 @@ module ActiveRecord
|
|
|
146
167
|
log(sql, name, binds, type_casted_binds) do
|
|
147
168
|
with_retry do
|
|
148
169
|
cached = false
|
|
149
|
-
if
|
|
170
|
+
if binds.nil? || binds.empty?
|
|
150
171
|
cursor = _connection.prepare(sql)
|
|
151
172
|
else
|
|
152
173
|
if @statements.key?(sql)
|
|
@@ -289,6 +310,15 @@ module ActiveRecord
|
|
|
289
310
|
raise
|
|
290
311
|
end
|
|
291
312
|
end
|
|
313
|
+
|
|
314
|
+
def handle_warnings(sql)
|
|
315
|
+
@notice_receiver_sql_warnings.each do |warning|
|
|
316
|
+
next if warning_ignored?(warning)
|
|
317
|
+
|
|
318
|
+
warning.sql = sql
|
|
319
|
+
ActiveRecord.db_warnings_action.call(warning)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
292
322
|
end
|
|
293
323
|
end
|
|
294
324
|
end
|
|
@@ -17,7 +17,8 @@ begin
|
|
|
17
17
|
# Oracle 12c Release 1 client provides ojdbc7.jar
|
|
18
18
|
# Oracle 12c Release 2 client provides ojdbc8.jar
|
|
19
19
|
# Oracle 21c provides ojdbc11.jar for Java 11 and above
|
|
20
|
-
|
|
20
|
+
# Oracle 23c provides ojdbc17.jar for Java 17 and above
|
|
21
|
+
ojdbc_jars = %w(ojdbc17.jar ojdbc11.jar ojdbc8.jar ojdbc7.jar ojdbc6.jar)
|
|
21
22
|
|
|
22
23
|
if !ENV_JAVA["java.class.path"]&.match?(Regexp.new(ojdbc_jars.join("|")))
|
|
23
24
|
# On Unix environment variable should be PATH, on Windows it is sometimes Path
|
|
@@ -60,6 +61,8 @@ module ActiveRecord
|
|
|
60
61
|
alias :auto_retry? :auto_retry
|
|
61
62
|
@auto_retry = false
|
|
62
63
|
|
|
64
|
+
attr_reader :session_time_zone
|
|
65
|
+
|
|
63
66
|
def initialize(config)
|
|
64
67
|
@active = true
|
|
65
68
|
@config = config
|
|
@@ -154,8 +157,10 @@ module ActiveRecord
|
|
|
154
157
|
# Set session time zone to current time zone
|
|
155
158
|
if ActiveRecord.default_timezone == :local
|
|
156
159
|
@raw_connection.setSessionTimeZone(time_zone)
|
|
160
|
+
@session_time_zone = time_zone
|
|
157
161
|
elsif ActiveRecord.default_timezone == :utc
|
|
158
162
|
@raw_connection.setSessionTimeZone("UTC")
|
|
163
|
+
@session_time_zone = "UTC"
|
|
159
164
|
end
|
|
160
165
|
|
|
161
166
|
if config[:jdbc_statement_cache_size]
|
|
@@ -236,12 +241,11 @@ module ActiveRecord
|
|
|
236
241
|
raise OracleEnhanced::ConnectionException, e.message
|
|
237
242
|
end
|
|
238
243
|
|
|
244
|
+
# Resets connection, by logging off and creating a new connection.
|
|
239
245
|
def reset
|
|
240
|
-
# tentative
|
|
241
246
|
reset!
|
|
242
247
|
end
|
|
243
248
|
|
|
244
|
-
# Resets connection, by logging off and creating a new connection.
|
|
245
249
|
def reset!
|
|
246
250
|
logoff rescue nil
|
|
247
251
|
begin
|
|
@@ -259,7 +263,7 @@ module ActiveRecord
|
|
|
259
263
|
begin
|
|
260
264
|
yield if block_given?
|
|
261
265
|
rescue Java::JavaSql::SQLException => e
|
|
262
|
-
raise unless /^(Closed Connection|Io exception:|No more data to read from socket|IO Error:)/.match?(e.message)
|
|
266
|
+
raise unless /^(Closed Connection|Io exception:|No more data to read from socket|IO Error:|ORA-03113:|ORA-03114:|ORA-17008:)/.match?(e.message)
|
|
263
267
|
@active = false
|
|
264
268
|
raise unless should_retry
|
|
265
269
|
should_retry = false
|
|
@@ -268,7 +272,11 @@ module ActiveRecord
|
|
|
268
272
|
end
|
|
269
273
|
end
|
|
270
274
|
|
|
271
|
-
def exec(sql, allow_retry: false)
|
|
275
|
+
def exec(sql, *bindvars, allow_retry: false)
|
|
276
|
+
# The signature mirrors the OCI implementation for polymorphic
|
|
277
|
+
# callers, but the JDBC path here has no bindvar handling. Fail
|
|
278
|
+
# loudly rather than silently dropping values on the floor.
|
|
279
|
+
raise ArgumentError, "JDBC exec does not support bindvars" unless bindvars.empty?
|
|
272
280
|
with_retry(allow_retry: allow_retry) do
|
|
273
281
|
exec_no_retry(sql)
|
|
274
282
|
end
|
|
@@ -298,7 +306,16 @@ module ActiveRecord
|
|
|
298
306
|
end
|
|
299
307
|
|
|
300
308
|
def prepare(sql)
|
|
301
|
-
|
|
309
|
+
# Use a plain Statement for DDL and PL/SQL blocks: PreparedStatement
|
|
310
|
+
# interprets colons as bind-parameter markers which breaks trigger
|
|
311
|
+
# definitions (:NEW/:OLD) and PL/SQL with named parameters.
|
|
312
|
+
if /\A\s*(CREATE|DROP|BEGIN|DECLARE)/i.match?(sql)
|
|
313
|
+
s = @raw_connection.createStatement()
|
|
314
|
+
s.setEscapeProcessing(false)
|
|
315
|
+
Cursor.new(self, s, sql)
|
|
316
|
+
else
|
|
317
|
+
Cursor.new(self, @raw_connection.prepareStatement(sql))
|
|
318
|
+
end
|
|
302
319
|
end
|
|
303
320
|
|
|
304
321
|
def database_version
|
|
@@ -306,9 +323,10 @@ module ActiveRecord
|
|
|
306
323
|
end
|
|
307
324
|
|
|
308
325
|
class Cursor
|
|
309
|
-
def initialize(connection, raw_statement)
|
|
326
|
+
def initialize(connection, raw_statement, exec_sql = nil)
|
|
310
327
|
@raw_connection = connection
|
|
311
328
|
@raw_statement = raw_statement
|
|
329
|
+
@exec_sql = exec_sql # non-nil for DDL/PL/SQL using createStatement
|
|
312
330
|
end
|
|
313
331
|
|
|
314
332
|
def bind_params(*bind_vars)
|
|
@@ -348,8 +366,14 @@ module ActiveRecord
|
|
|
348
366
|
when Java::JavaSql::Timestamp
|
|
349
367
|
@raw_statement.setTimestamp(position, value)
|
|
350
368
|
when Time
|
|
351
|
-
new_value =
|
|
352
|
-
|
|
369
|
+
new_value = java.sql.Timestamp.new(value.to_i * 1000)
|
|
370
|
+
new_value.setNanos(value.nsec)
|
|
371
|
+
# Oracle JDBC getTimestamp uses the session timezone, but setTimestamp
|
|
372
|
+
# (without Calendar) uses the JVM default timezone. Pass a Calendar
|
|
373
|
+
# matching the session timezone so both sides use the same reference.
|
|
374
|
+
tz_id = @raw_connection.session_time_zone || java.util.TimeZone.default.getID
|
|
375
|
+
cal = java.util.Calendar.getInstance(java.util.TimeZone.getTimeZone(tz_id))
|
|
376
|
+
@raw_statement.setTimestamp(position, new_value, cal)
|
|
353
377
|
when NilClass
|
|
354
378
|
# TODO: currently nil is always bound as NULL with VARCHAR type.
|
|
355
379
|
# When nils will actually be used by ActiveRecord as bound parameters
|
|
@@ -369,7 +393,13 @@ module ActiveRecord
|
|
|
369
393
|
end
|
|
370
394
|
|
|
371
395
|
def exec
|
|
372
|
-
|
|
396
|
+
if @exec_sql
|
|
397
|
+
# DDL / PL/SQL block executed via createStatement — never returns rows
|
|
398
|
+
@raw_statement.execute(@exec_sql)
|
|
399
|
+
elsif @raw_statement.execute
|
|
400
|
+
# execute() returns true when the result is a ResultSet (SELECT)
|
|
401
|
+
@raw_result_set = @raw_statement.getResultSet
|
|
402
|
+
end
|
|
373
403
|
true
|
|
374
404
|
end
|
|
375
405
|
|
|
@@ -378,18 +408,28 @@ module ActiveRecord
|
|
|
378
408
|
end
|
|
379
409
|
|
|
380
410
|
def metadata
|
|
381
|
-
@metadata ||= @raw_result_set
|
|
411
|
+
@metadata ||= @raw_result_set&.getMetaData
|
|
382
412
|
end
|
|
383
413
|
|
|
384
414
|
def column_types
|
|
415
|
+
return [] unless @raw_result_set
|
|
385
416
|
@column_types ||= (1..metadata.getColumnCount).map { |i| metadata.getColumnTypeName(i).to_sym }
|
|
386
417
|
end
|
|
387
418
|
|
|
388
419
|
def column_names
|
|
420
|
+
return [] unless @raw_result_set
|
|
389
421
|
@column_names ||= (1..metadata.getColumnCount).map { |i| metadata.getColumnName(i) }
|
|
390
422
|
end
|
|
391
423
|
alias :get_col_names :column_names
|
|
392
424
|
|
|
425
|
+
def select_statement?
|
|
426
|
+
!@raw_result_set.nil?
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
def row_count
|
|
430
|
+
@raw_statement.getUpdateCount
|
|
431
|
+
end
|
|
432
|
+
|
|
393
433
|
def fetch(options = {})
|
|
394
434
|
if @raw_result_set.next
|
|
395
435
|
get_lob_value = options[:get_lob_value]
|
|
@@ -419,6 +459,17 @@ module ActiveRecord
|
|
|
419
459
|
|
|
420
460
|
def close
|
|
421
461
|
@raw_statement.close
|
|
462
|
+
rescue Java::JavaSql::SQLException => e
|
|
463
|
+
# Tolerate closing a statement whose underlying connection or
|
|
464
|
+
# statement has already been torn down (typical after with_retry
|
|
465
|
+
# has reset the connection). Re-raise anything else so genuine
|
|
466
|
+
# driver/resource bugs surface.
|
|
467
|
+
#
|
|
468
|
+
# ORA-17002 "Io exception" - network/socket failure
|
|
469
|
+
# ORA-17008 "Closed Connection" - connection already closed
|
|
470
|
+
# ORA-17009 "Closed Statement" - statement already closed
|
|
471
|
+
raise unless [17_002, 17_008, 17_009].include?(e.getErrorCode)
|
|
472
|
+
nil
|
|
422
473
|
end
|
|
423
474
|
end
|
|
424
475
|
|
|
@@ -514,10 +565,27 @@ module ActiveRecord
|
|
|
514
565
|
else
|
|
515
566
|
nil
|
|
516
567
|
end
|
|
517
|
-
when :TIMESTAMP
|
|
568
|
+
when :TIMESTAMP
|
|
569
|
+
# Oracle JDBC getTimestamp for plain TIMESTAMP uses the JVM default timezone,
|
|
570
|
+
# but we store values using the session timezone. Pass a matching Calendar so
|
|
571
|
+
# the driver interprets the stored wall-clock value in the session timezone.
|
|
572
|
+
tz_id = @session_time_zone || java.util.TimeZone.default.getID
|
|
573
|
+
cal = java.util.Calendar.getInstance(java.util.TimeZone.getTimeZone(tz_id))
|
|
574
|
+
ts = rset.getTimestamp(i, cal)
|
|
575
|
+
if ts
|
|
576
|
+
instant = ts.toInstant
|
|
577
|
+
t = Time.at(instant.getEpochSecond, instant.getNano, :nanosecond)
|
|
578
|
+
ActiveRecord.default_timezone == :utc ? t.utc : t.localtime
|
|
579
|
+
end
|
|
580
|
+
when :TIMESTAMPTZ, :TIMESTAMPLTZ, :"TIMESTAMP WITH TIME ZONE", :"TIMESTAMP WITH LOCAL TIME ZONE"
|
|
581
|
+
# TIMESTAMPTZ/TIMESTAMPLTZ include timezone info so getTimestamp returns
|
|
582
|
+
# the correct UTC epoch without needing a Calendar.
|
|
518
583
|
ts = rset.getTimestamp(i)
|
|
519
|
-
|
|
520
|
-
ts.
|
|
584
|
+
if ts
|
|
585
|
+
instant = ts.toInstant
|
|
586
|
+
t = Time.at(instant.getEpochSecond, instant.getNano, :nanosecond)
|
|
587
|
+
ActiveRecord.default_timezone == :utc ? t.utc : t.localtime
|
|
588
|
+
end
|
|
521
589
|
when :CLOB
|
|
522
590
|
get_lob_value ? lob_to_ruby_value(rset.getClob(i)) : rset.getClob(i)
|
|
523
591
|
when :NCLOB
|
|
@@ -11,6 +11,8 @@ module ActiveRecord # :nodoc:
|
|
|
11
11
|
|
|
12
12
|
# After setting large objects to empty, select the OCI8::LOB
|
|
13
13
|
# and write back the data.
|
|
14
|
+
before_create :record_lobs_for_create
|
|
15
|
+
after_create :enhanced_write_lobs
|
|
14
16
|
before_update :record_changed_lobs
|
|
15
17
|
after_update :enhanced_write_lobs
|
|
16
18
|
end
|
|
@@ -25,11 +27,21 @@ module ActiveRecord # :nodoc:
|
|
|
25
27
|
|
|
26
28
|
private
|
|
27
29
|
def enhanced_write_lobs
|
|
30
|
+
# Skip if using prepared statements - LOB data is already written via temp LOB binding
|
|
31
|
+
return if self.class.connection.prepared_statements
|
|
32
|
+
|
|
28
33
|
if self.class.connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
|
|
29
34
|
!(self.class.custom_create_method || self.class.custom_update_method)
|
|
30
35
|
self.class.connection.write_lobs(self.class.table_name, self.class, attributes, @changed_lob_columns)
|
|
31
36
|
end
|
|
32
37
|
end
|
|
38
|
+
|
|
39
|
+
def record_lobs_for_create
|
|
40
|
+
@changed_lob_columns = self.class.lob_columns.select do |col|
|
|
41
|
+
!attributes[col.name].nil? && !self.class.readonly_attributes.to_a.include?(col.name)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
33
45
|
def record_changed_lobs
|
|
34
46
|
@changed_lob_columns = self.class.lob_columns.select do |col|
|
|
35
47
|
self.will_save_change_to_attribute?(col.name) && !self.class.readonly_attributes.to_a.include?(col.name)
|
|
@@ -158,6 +158,14 @@ module ActiveRecord
|
|
|
158
158
|
@raw_cursor.get_col_names
|
|
159
159
|
end
|
|
160
160
|
|
|
161
|
+
def row_count
|
|
162
|
+
@raw_cursor.row_count
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def select_statement?
|
|
166
|
+
@raw_cursor.type == :select_stmt
|
|
167
|
+
end
|
|
168
|
+
|
|
161
169
|
def fetch(options = {})
|
|
162
170
|
if row = @raw_cursor.fetch
|
|
163
171
|
get_lob_value = options[:get_lob_value]
|
|
@@ -219,9 +219,9 @@ module ActiveRecord
|
|
|
219
219
|
unless create_sequence
|
|
220
220
|
class << td
|
|
221
221
|
attr_accessor :create_sequence
|
|
222
|
-
def primary_key(
|
|
222
|
+
def primary_key(name, type = :primary_key, **options)
|
|
223
223
|
self.create_sequence = true
|
|
224
|
-
super(
|
|
224
|
+
super(name, type, **options)
|
|
225
225
|
end
|
|
226
226
|
end
|
|
227
227
|
end
|
|
@@ -284,7 +284,7 @@ module ActiveRecord
|
|
|
284
284
|
} << "SELECT * FROM DUAL\n"
|
|
285
285
|
else
|
|
286
286
|
if versions.is_a?(Array)
|
|
287
|
-
# called from ActiveRecord::Base.connection#
|
|
287
|
+
# called from ActiveRecord::Base.connection#dump_schema_versions
|
|
288
288
|
versions.map { |version|
|
|
289
289
|
"INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
|
|
290
290
|
}.join("\n\n/\n\n")
|
|
@@ -674,6 +674,7 @@ module ActiveRecord
|
|
|
674
674
|
default_value = extract_value_from_default(field["data_default"])
|
|
675
675
|
default_value = nil if is_virtual
|
|
676
676
|
OracleEnhanced::Column.new(oracle_downcase(field["name"]),
|
|
677
|
+
lookup_cast_type(field["sql_type"]),
|
|
677
678
|
default_value,
|
|
678
679
|
type_metadata,
|
|
679
680
|
field["nullable"] == "Y",
|
|
@@ -698,7 +699,7 @@ module ActiveRecord
|
|
|
698
699
|
end
|
|
699
700
|
|
|
700
701
|
def default_tablespace_for(type)
|
|
701
|
-
|
|
702
|
+
default_tablespaces[type]
|
|
702
703
|
end
|
|
703
704
|
|
|
704
705
|
def column_for(table_name, column_name)
|
|
@@ -51,6 +51,7 @@ module ActiveRecord # :nodoc:
|
|
|
51
51
|
structure << ddl
|
|
52
52
|
structure << structure_dump_indexes(table_name)
|
|
53
53
|
structure << structure_dump_unique_keys(table_name)
|
|
54
|
+
structure << structure_dump_check_constraints(table_name)
|
|
54
55
|
structure << structure_dump_table_comments(table_name)
|
|
55
56
|
structure << structure_dump_column_comments(table_name)
|
|
56
57
|
end
|
|
@@ -161,6 +162,33 @@ module ActiveRecord # :nodoc:
|
|
|
161
162
|
join_with_statement_token(fks)
|
|
162
163
|
end
|
|
163
164
|
|
|
165
|
+
# Only user-named CHECK constraints are preserved. Anonymous
|
|
166
|
+
# constraints created via `ALTER TABLE ... ADD CHECK (...)` receive
|
|
167
|
+
# Oracle-generated names (e.g. SYS_C00123) and are skipped to avoid
|
|
168
|
+
# also emitting the implicit NOT NULL check constraints that Oracle
|
|
169
|
+
# stores with constraint_type = 'C'.
|
|
170
|
+
def structure_dump_check_constraints(table_name) # :nodoc:
|
|
171
|
+
# `search_condition` is a LONG column, so it cannot appear in a
|
|
172
|
+
# WHERE clause (ORA-00997). The driver reads it as a String on
|
|
173
|
+
# SELECT. Implicit NOT NULL constraints Oracle stores with
|
|
174
|
+
# constraint_type = 'C' are excluded by `generated = 'USER NAME'`
|
|
175
|
+
# since they receive system-generated names.
|
|
176
|
+
check_constraints = select_all(<<~SQL.squish, "SCHEMA", [bind_string("table_name", table_name.upcase)])
|
|
177
|
+
SELECT constraint_name, search_condition
|
|
178
|
+
FROM all_constraints
|
|
179
|
+
WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
|
|
180
|
+
AND table_name = :table_name
|
|
181
|
+
AND constraint_type = 'C'
|
|
182
|
+
AND generated = 'USER NAME'
|
|
183
|
+
ORDER BY constraint_name
|
|
184
|
+
SQL
|
|
185
|
+
check_constraints.filter_map do |row|
|
|
186
|
+
condition = row["search_condition"]
|
|
187
|
+
next if condition.nil?
|
|
188
|
+
"ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(row["constraint_name"])} CHECK (#{condition})"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
164
192
|
def structure_dump_table_comments(table_name)
|
|
165
193
|
comments = []
|
|
166
194
|
comment = table_comment(table_name)
|