activerecord-jdbc-adapter 1.3.13 → 1.3.14
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.
- data/.travis.yml +212 -33
- data/Appraisals +10 -12
- data/History.md +17 -0
- data/lib/arel/visitors/db2.rb +24 -1
- data/lib/arel/visitors/derby.rb +24 -0
- data/lib/arjdbc.rb +6 -0
- data/lib/arjdbc/db2/adapter.rb +61 -50
- data/lib/arjdbc/derby/adapter.rb +1 -1
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/type_cast.rb +3 -2
- data/lib/arjdbc/mysql/adapter.rb +75 -4
- data/lib/arjdbc/mysql/bulk_change_table.rb +48 -8
- data/lib/arjdbc/oracle/adapter.rb +1 -0
- data/lib/arjdbc/oracle/column.rb +1 -1
- data/lib/arjdbc/postgresql/adapter.rb +3 -0
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/02-test.rake +13 -23
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +38 -19
- metadata +242 -240
- checksums.yaml +0 -7
data/lib/arjdbc/db2/adapter.rb
CHANGED
@@ -187,28 +187,30 @@ module ArJdbc
|
|
187
187
|
select_value("SELECT NEXT VALUE FOR #{sequence_name} FROM sysibm.sysdummy1")
|
188
188
|
end
|
189
189
|
|
190
|
-
def create_table(name, options = {})
|
190
|
+
def create_table(name, options = {}, &block)
|
191
191
|
if zos?
|
192
|
-
zos_create_table(name, options)
|
192
|
+
zos_create_table(name, options, &block)
|
193
193
|
else
|
194
|
-
super
|
194
|
+
super
|
195
195
|
end
|
196
196
|
end
|
197
197
|
|
198
198
|
def zos_create_table(name, options = {})
|
199
|
-
|
200
|
-
|
199
|
+
table_definition = new_table_definition TableDefinition, name, options[:temporary], options[:options], options[:as]
|
200
|
+
|
201
201
|
unless options[:id] == false
|
202
202
|
table_definition.primary_key(options[:primary_key] || primary_key(name))
|
203
203
|
end
|
204
204
|
|
205
|
-
yield table_definition
|
205
|
+
yield table_definition if block_given?
|
206
206
|
|
207
207
|
# Clobs in DB2 Host have to be created after the Table with an auxiliary Table.
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
208
|
+
clob_columns = []
|
209
|
+
table_definition.columns.delete_if do |column|
|
210
|
+
if column.type && column.type.to_sym == :text
|
211
|
+
clob_columns << column; true
|
212
|
+
end
|
213
|
+
end
|
212
214
|
|
213
215
|
drop_table(name, options) if options[:force] && table_exists?(name)
|
214
216
|
|
@@ -217,11 +219,8 @@ module ArJdbc
|
|
217
219
|
create_sql << table_definition.to_sql
|
218
220
|
create_sql << ") #{options[:options]}"
|
219
221
|
if @config[:database] && @config[:tablespace]
|
220
|
-
|
221
|
-
else
|
222
|
-
in_db_table_space = ''
|
222
|
+
create_sql << " IN #{@config[:database]}.#{@config[:tablespace]}"
|
223
223
|
end
|
224
|
-
create_sql << in_db_table_space
|
225
224
|
|
226
225
|
execute create_sql
|
227
226
|
|
@@ -232,14 +231,12 @@ module ArJdbc
|
|
232
231
|
#primary_column = options[:id] == true ? 'id' : options[:primary_key]
|
233
232
|
#add_index(name, (primary_column || 'id').to_s, :unique => true)
|
234
233
|
|
235
|
-
|
234
|
+
clob_columns.each do |clob_column|
|
236
235
|
column_name = clob_column.name.to_s
|
237
|
-
execute "ALTER TABLE #{name
|
238
|
-
clob_table_name = name
|
236
|
+
execute "ALTER TABLE #{name} ADD COLUMN #{column_name} clob"
|
237
|
+
clob_table_name = "#{name}_#{column_name}_CD_"
|
239
238
|
if @config[:database] && @config[:lob_tablespaces]
|
240
239
|
in_lob_table_space = " IN #{@config[:database]}.#{@config[:lob_tablespaces][name.split(".")[1]]}"
|
241
|
-
else
|
242
|
-
in_lob_table_space = ''
|
243
240
|
end
|
244
241
|
execute "CREATE AUXILIARY TABLE #{clob_table_name} #{in_lob_table_space} STORES #{name} COLUMN #{column_name}"
|
245
242
|
execute "CREATE UNIQUE INDEX #{clob_table_name} ON #{clob_table_name};"
|
@@ -350,6 +347,14 @@ module ArJdbc
|
|
350
347
|
super(type, limit, precision, scale)
|
351
348
|
end
|
352
349
|
|
350
|
+
# @private
|
351
|
+
VALUES_DEFAULT = 'VALUES ( DEFAULT )' # NOTE: Arel::Visitors::DB2 uses this
|
352
|
+
|
353
|
+
# @override
|
354
|
+
def empty_insert_statement_value
|
355
|
+
VALUES_DEFAULT # won't work as DB2 needs to know the column count
|
356
|
+
end
|
357
|
+
|
353
358
|
def add_column(table_name, column_name, type, options = {})
|
354
359
|
# The keyword COLUMN allows to use reserved names for columns (ex: date)
|
355
360
|
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
@@ -393,7 +398,7 @@ module ArJdbc
|
|
393
398
|
|
394
399
|
limit = limit.to_i
|
395
400
|
if offset
|
396
|
-
replace_limit_offset_with_ordering(sql, limit, offset)
|
401
|
+
replace_limit_offset_with_ordering!(sql, limit, offset)
|
397
402
|
else
|
398
403
|
if limit == 1
|
399
404
|
sql << " FETCH FIRST ROW ONLY"
|
@@ -404,42 +409,48 @@ module ArJdbc
|
|
404
409
|
end
|
405
410
|
end
|
406
411
|
|
407
|
-
# @private
|
408
|
-
def
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
def replace_limit_offset_with_ordering( sql, limit, offset, orders=[] )
|
413
|
-
sql.sub!(/SELECT/i, "SELECT B.* FROM (SELECT A.*, row_number() over (#{build_ordering(orders)}) AS internal$rownum FROM (SELECT")
|
412
|
+
# @private used from {Arel::Visitors::DB2}
|
413
|
+
def replace_limit_offset_with_ordering!(sql, limit, offset, orders = nil)
|
414
|
+
over_order_by = nil # NOTE: orders matching got reverted as it was not complete and there were no case covering it ...
|
415
|
+
sql.sub!(/SELECT/i, "SELECT B.* FROM (SELECT A.*, row_number() OVER (#{over_order_by}) AS internal$rownum FROM (SELECT")
|
414
416
|
sql << ") A ) B WHERE B.internal$rownum > #{offset} AND B.internal$rownum <= #{limit + offset}"
|
415
417
|
sql
|
416
418
|
end
|
417
|
-
private :replace_limit_offset_with_ordering
|
418
|
-
|
419
|
-
def build_ordering( orders )
|
420
|
-
return '' unless orders.size > 0
|
421
|
-
# need to remove the library/table names from the orderings because we are not really ordering by them anymore
|
422
|
-
# we are actually ordering by the results of a query where the result set has the same column names
|
423
|
-
orders = orders.map do |o|
|
424
|
-
# need to keep in mind that the order clause could be wrapped in a function
|
425
|
-
matches = /(?:\w+\(|\s)*(\S+)(?:\)|\s)*/.match(o)
|
426
|
-
o = o.gsub( matches[1], matches[1].split('.').last ) if matches
|
427
|
-
o
|
428
|
-
end
|
429
|
-
"ORDER BY " + orders.join( ', ')
|
430
|
-
end
|
431
|
-
private :build_ordering
|
432
419
|
|
433
420
|
# @deprecated seems not sued nor tested ?!
|
434
421
|
def runstats_for_table(tablename, priority = 10)
|
435
422
|
@connection.execute_update "call sysproc.admin_cmd('RUNSTATS ON TABLE #{tablename} WITH DISTRIBUTION AND DETAILED INDEXES ALL UTIL_IMPACT_PRIORITY #{priority}')"
|
436
423
|
end
|
437
424
|
|
438
|
-
|
439
|
-
|
440
|
-
|
425
|
+
if ::ActiveRecord::VERSION::MAJOR >= 4
|
426
|
+
|
427
|
+
def select(sql, name = nil, binds = [])
|
428
|
+
exec_query(to_sql(suble_null_test(sql), binds), name, binds)
|
429
|
+
end
|
430
|
+
|
431
|
+
else
|
432
|
+
|
433
|
+
def select(sql, name = nil, binds = [])
|
434
|
+
exec_query_raw(to_sql(suble_null_test(sql), binds), name, binds)
|
435
|
+
end
|
436
|
+
|
441
437
|
end
|
442
438
|
|
439
|
+
# @private
|
440
|
+
IS_NOT_NULL = /(!=|<>)\s*NULL/i
|
441
|
+
# @private
|
442
|
+
IS_NULL = /=\s*NULL/i
|
443
|
+
|
444
|
+
def suble_null_test(sql)
|
445
|
+
return sql unless sql.is_a?(String)
|
446
|
+
# DB2 does not like "= NULL", "!= NULL", or "<> NULL" :
|
447
|
+
sql = sql.dup
|
448
|
+
sql.gsub! IS_NOT_NULL, 'IS NOT NULL'
|
449
|
+
sql.gsub! IS_NULL, 'IS NULL'
|
450
|
+
sql
|
451
|
+
end
|
452
|
+
private :suble_null_test
|
453
|
+
|
443
454
|
def add_index(table_name, column_name, options = {})
|
444
455
|
if ! zos? || ( table_name.to_s == ActiveRecord::Migrator.schema_migrations_table_name.to_s )
|
445
456
|
column_name = column_name.to_s if column_name.is_a?(Symbol)
|
@@ -654,14 +665,14 @@ module ArJdbc
|
|
654
665
|
def db2_schema
|
655
666
|
@db2_schema = false unless defined? @db2_schema
|
656
667
|
return @db2_schema if @db2_schema != false
|
668
|
+
schema = config[:schema]
|
657
669
|
@db2_schema =
|
658
|
-
if
|
659
|
-
|
660
|
-
elsif config[:jndi].present?
|
670
|
+
if schema then schema
|
671
|
+
elsif config[:jndi] || config[:data_source]
|
661
672
|
nil # let JNDI worry about schema
|
662
673
|
else
|
663
674
|
# LUW implementation uses schema name of username by default
|
664
|
-
config[:username]
|
675
|
+
config[:username] || ENV['USER']
|
665
676
|
end
|
666
677
|
end
|
667
678
|
|
@@ -681,4 +692,4 @@ module ActiveRecord::ConnectionAdapters
|
|
681
692
|
include ::ArJdbc::DB2::Column
|
682
693
|
end
|
683
694
|
|
684
|
-
end
|
695
|
+
end
|
data/lib/arjdbc/derby/adapter.rb
CHANGED
@@ -248,7 +248,7 @@ module ArJdbc
|
|
248
248
|
|
249
249
|
# @override
|
250
250
|
def empty_insert_statement_value
|
251
|
-
|
251
|
+
::Arel::Visitors::Derby::VALUES_DEFAULT # Derby needs to know the columns
|
252
252
|
end
|
253
253
|
|
254
254
|
# Set the sequence to the max value of the table's column.
|
Binary file
|
@@ -104,9 +104,10 @@ module ActiveRecord::ConnectionAdapters
|
|
104
104
|
return nil unless time
|
105
105
|
|
106
106
|
time -= offset
|
107
|
-
Base.default_timezone == :utc ? time : time.getlocal
|
107
|
+
ActiveRecord::Base.default_timezone == :utc ? time : time.getlocal
|
108
108
|
else
|
109
|
-
|
109
|
+
timezone = ActiveRecord::Base.default_timezone
|
110
|
+
Time.public_send(timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
|
110
111
|
end
|
111
112
|
end
|
112
113
|
|
data/lib/arjdbc/mysql/adapter.rb
CHANGED
@@ -390,6 +390,76 @@ module ArJdbc
|
|
390
390
|
columns
|
391
391
|
end
|
392
392
|
|
393
|
+
if defined? ::ActiveRecord::ConnectionAdapters::AbstractAdapter::SchemaCreation
|
394
|
+
|
395
|
+
class SchemaCreation < ::ActiveRecord::ConnectionAdapters::AbstractAdapter::SchemaCreation
|
396
|
+
|
397
|
+
# @private
|
398
|
+
def visit_AddColumn(o)
|
399
|
+
add_column_position!(super, column_options(o))
|
400
|
+
end
|
401
|
+
|
402
|
+
# @private re-defined since AR 4.1
|
403
|
+
def visit_ChangeColumnDefinition(o)
|
404
|
+
column = o.column
|
405
|
+
options = o.options
|
406
|
+
sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
|
407
|
+
change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
|
408
|
+
add_column_options!(change_column_sql, options.merge(:column => column))
|
409
|
+
add_column_position!(change_column_sql, options)
|
410
|
+
end
|
411
|
+
|
412
|
+
# @private since AR 4.2
|
413
|
+
def visit_DropForeignKey(name)
|
414
|
+
"DROP FOREIGN KEY #{name}"
|
415
|
+
end
|
416
|
+
|
417
|
+
# @private since AR 4.2
|
418
|
+
def visit_TableDefinition(o)
|
419
|
+
name = o.name
|
420
|
+
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
|
421
|
+
|
422
|
+
statements = o.columns.map { |c| accept c }
|
423
|
+
statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
|
424
|
+
|
425
|
+
create_sql << "(#{statements.join(', ')}) " if statements.present?
|
426
|
+
create_sql << "#{o.options}"
|
427
|
+
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
|
428
|
+
create_sql
|
429
|
+
end if AR42
|
430
|
+
|
431
|
+
private
|
432
|
+
|
433
|
+
def add_column_position!(sql, options)
|
434
|
+
if options[:first]
|
435
|
+
sql << " FIRST"
|
436
|
+
elsif options[:after]
|
437
|
+
sql << " AFTER #{quote_column_name(options[:after])}"
|
438
|
+
end
|
439
|
+
sql
|
440
|
+
end
|
441
|
+
|
442
|
+
def column_options(o)
|
443
|
+
column_options = {}
|
444
|
+
column_options[:null] = o.null unless o.null.nil?
|
445
|
+
column_options[:default] = o.default unless o.default.nil?
|
446
|
+
column_options[:column] = o
|
447
|
+
column_options[:first] = o.first
|
448
|
+
column_options[:after] = o.after
|
449
|
+
column_options
|
450
|
+
end
|
451
|
+
|
452
|
+
def index_in_create(table_name, column_name, options)
|
453
|
+
index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
|
454
|
+
"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
|
455
|
+
end
|
456
|
+
|
457
|
+
end
|
458
|
+
|
459
|
+
def schema_creation; SchemaCreation.new self end
|
460
|
+
|
461
|
+
end
|
462
|
+
|
393
463
|
# @private
|
394
464
|
def recreate_database(name, options = {})
|
395
465
|
drop_database(name)
|
@@ -417,7 +487,7 @@ module ArJdbc
|
|
417
487
|
|
418
488
|
# @override
|
419
489
|
def create_table(name, options = {})
|
420
|
-
super(name, {:options => "ENGINE=InnoDB
|
490
|
+
super(name, { :options => "ENGINE=InnoDB" }.merge(options))
|
421
491
|
end
|
422
492
|
|
423
493
|
def drop_table(table_name, options = {})
|
@@ -572,7 +642,7 @@ module ArJdbc
|
|
572
642
|
when 0..0xfff; "varbinary(#{limit})"
|
573
643
|
when nil; "blob"
|
574
644
|
when 0x1000..0xffffffff; "blob(#{limit})"
|
575
|
-
else raise
|
645
|
+
else raise ActiveRecord::ActiveRecordError, "No binary type has character length #{limit}"
|
576
646
|
end
|
577
647
|
when 'integer'
|
578
648
|
case limit
|
@@ -581,7 +651,7 @@ module ArJdbc
|
|
581
651
|
when 3; 'mediumint'
|
582
652
|
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
583
653
|
when 5..8; 'bigint'
|
584
|
-
else raise
|
654
|
+
else raise ActiveRecord::ActiveRecordError, "No integer type has byte size #{limit}"
|
585
655
|
end
|
586
656
|
when 'text'
|
587
657
|
case limit
|
@@ -589,7 +659,7 @@ module ArJdbc
|
|
589
659
|
when nil, 0x100..0xffff; 'text'
|
590
660
|
when 0x10000..0xffffff; 'mediumtext'
|
591
661
|
when 0x1000000..0xffffffff; 'longtext'
|
592
|
-
else raise
|
662
|
+
else raise ActiveRecord::ActiveRecordError, "No text type has character length #{limit}"
|
593
663
|
end
|
594
664
|
else
|
595
665
|
super
|
@@ -602,6 +672,7 @@ module ArJdbc
|
|
602
672
|
end
|
603
673
|
|
604
674
|
protected
|
675
|
+
|
605
676
|
def quoted_columns_for_index(column_names, options = {})
|
606
677
|
length = options[:length] if options.is_a?(Hash)
|
607
678
|
|
@@ -2,10 +2,14 @@ module ArJdbc
|
|
2
2
|
module MySQL
|
3
3
|
module BulkChangeTable
|
4
4
|
|
5
|
+
# @private
|
6
|
+
AR41 = ActiveRecord::VERSION::STRING >= '4.1'
|
7
|
+
|
8
|
+
# @private
|
9
|
+
ChangeColumnDefinition = ActiveRecord::ConnectionAdapters::ChangeColumnDefinition if AR41
|
10
|
+
|
5
11
|
# @override
|
6
|
-
def supports_bulk_alter
|
7
|
-
true
|
8
|
-
end
|
12
|
+
def supports_bulk_alter?; true end
|
9
13
|
|
10
14
|
def bulk_change_table(table_name, operations)
|
11
15
|
sqls = operations.map do |command, args|
|
@@ -30,7 +34,13 @@ module ArJdbc
|
|
30
34
|
add_column_options!(add_column_sql, options)
|
31
35
|
add_column_position!(add_column_sql, options)
|
32
36
|
add_column_sql
|
33
|
-
end
|
37
|
+
end unless AR41
|
38
|
+
|
39
|
+
def add_column_sql(table_name, column_name, type, options = {})
|
40
|
+
td = create_table_definition table_name, options[:temporary], options[:options]
|
41
|
+
cd = td.new_column_definition(column_name, type, options)
|
42
|
+
schema_creation.visit_AddColumn cd
|
43
|
+
end if AR41
|
34
44
|
|
35
45
|
def change_column_sql(table_name, column_name, type, options = {})
|
36
46
|
column = column_for(table_name, column_name)
|
@@ -47,7 +57,22 @@ module ArJdbc
|
|
47
57
|
add_column_options!(change_column_sql, options)
|
48
58
|
add_column_position!(change_column_sql, options)
|
49
59
|
change_column_sql
|
50
|
-
end
|
60
|
+
end unless AR41
|
61
|
+
|
62
|
+
def change_column_sql(table_name, column_name, type, options = {})
|
63
|
+
column = column_for(table_name, column_name)
|
64
|
+
|
65
|
+
unless options_include_default?(options)
|
66
|
+
options[:default] = column.default
|
67
|
+
end
|
68
|
+
|
69
|
+
unless options.has_key?(:null)
|
70
|
+
options[:null] = column.null
|
71
|
+
end
|
72
|
+
|
73
|
+
options[:name] = column.name
|
74
|
+
schema_creation.accept ChangeColumnDefinition.new column, type, options
|
75
|
+
end if AR41
|
51
76
|
|
52
77
|
def rename_column_sql(table_name, column_name, new_column_name)
|
53
78
|
options = {}
|
@@ -64,7 +89,22 @@ module ArJdbc
|
|
64
89
|
rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
|
65
90
|
add_column_options!(rename_column_sql, options)
|
66
91
|
rename_column_sql
|
67
|
-
end
|
92
|
+
end unless AR41
|
93
|
+
|
94
|
+
def rename_column_sql(table_name, column_name, new_column_name)
|
95
|
+
options = { :name => new_column_name }
|
96
|
+
|
97
|
+
if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
98
|
+
options[:default] = column.default
|
99
|
+
options[:null] = column.null
|
100
|
+
options[:auto_increment] = (column.extra == "auto_increment")
|
101
|
+
else
|
102
|
+
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
103
|
+
end
|
104
|
+
|
105
|
+
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
|
106
|
+
schema_creation.accept ChangeColumnDefinition.new column, current_type, options
|
107
|
+
end if AR41
|
68
108
|
|
69
109
|
def remove_column_sql(table_name, column_name, type = nil, options = {})
|
70
110
|
"DROP #{quote_column_name(column_name)}"
|
@@ -84,8 +124,8 @@ module ArJdbc
|
|
84
124
|
"DROP INDEX #{index_name}"
|
85
125
|
end
|
86
126
|
|
87
|
-
def add_timestamps_sql(table_name)
|
88
|
-
[add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
|
127
|
+
def add_timestamps_sql(table_name, options = {})
|
128
|
+
[add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
|
89
129
|
end
|
90
130
|
|
91
131
|
def remove_timestamps_sql(table_name)
|
@@ -140,6 +140,7 @@ module ArJdbc
|
|
140
140
|
def table_name_length; IDENTIFIER_LENGTH; end
|
141
141
|
def index_name_length; IDENTIFIER_LENGTH; end
|
142
142
|
def column_name_length; IDENTIFIER_LENGTH; end
|
143
|
+
def sequence_name_length; IDENTIFIER_LENGTH end
|
143
144
|
|
144
145
|
def default_sequence_name(table_name, column = nil)
|
145
146
|
# TODO: remove schema prefix if present (before truncating)
|
data/lib/arjdbc/oracle/column.rb
CHANGED
@@ -988,7 +988,10 @@ module ArJdbc
|
|
988
988
|
pk, seq = pk_and_sequence_for(new_name)
|
989
989
|
if seq == "#{table_name}_#{pk}_seq"
|
990
990
|
new_seq = "#{new_name}_#{pk}_seq"
|
991
|
+
idx = "#{table_name}_pkey"
|
992
|
+
new_idx = "#{new_name}_pkey"
|
991
993
|
execute "ALTER TABLE #{quote_table_name(seq)} RENAME TO #{quote_table_name(new_seq)}"
|
994
|
+
execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}"
|
992
995
|
end
|
993
996
|
rename_table_indexes(table_name, new_name) if respond_to?(:rename_table_indexes) # AR-4.0 SchemaStatements
|
994
997
|
end
|