rubyfb 0.5.9 → 0.6
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/CHANGELOG +36 -0
- data/Manifest +1 -0
- data/README +75 -175
- data/Rakefile +1 -1
- data/ext/Blob.c +1 -1
- data/ext/Connection.c +40 -126
- data/ext/FireRubyException.c +27 -41
- data/ext/Generator.c +61 -339
- data/ext/Generator.h +0 -5
- data/ext/ResultSet.c +201 -381
- data/ext/ResultSet.h +8 -9
- data/ext/Statement.c +495 -426
- data/ext/Statement.h +8 -11
- data/ext/Transaction.c +3 -2
- data/ext/TypeMap.c +44 -43
- data/ext/TypeMap.h +1 -1
- data/lib/active_record/connection_adapters/rubyfb_adapter.rb +370 -120
- data/lib/arel/visitors/rubyfb.rb +7 -3
- data/lib/arel/visitors/rubyfb_15compat.rb +8 -4
- data/lib/rubyfb_lib.so +0 -0
- data/lib/src.rb +76 -39
- data/rubyfb.gemspec +3 -3
- data/test/AddRemoveUserTest.rb +7 -0
- data/test/BackupRestoreTest.rb +1 -1
- data/test/BlobTest.rb +5 -5
- data/test/GeneratorTest.rb +8 -8
- data/test/KeyTest.rb +1 -2
- data/test/ResultSetTest.rb +14 -16
- data/test/RoleTest.rb +2 -2
- data/test/RowCountTest.rb +19 -19
- data/test/RowTest.rb +7 -8
- data/test/SQLTest.rb +7 -8
- data/test/StatementTest.rb +18 -24
- data/test/StoredProcedureTest.rb +80 -0
- data/test/TransactionTest.rb +1 -1
- data/test/TypeTest.rb +4 -5
- metadata +5 -5
@@ -1,6 +1,8 @@
|
|
1
1
|
# Author: Ken Kunz <kennethkunz@gmail.com>
|
2
|
+
require 'digest/sha1'
|
2
3
|
require 'active_record/connection_adapters/abstract_adapter'
|
3
4
|
require 'active_support/core_ext/kernel/requires'
|
5
|
+
require 'rubyfb_options'
|
4
6
|
|
5
7
|
if defined?(Arel) then
|
6
8
|
if Rubyfb::Options.fb15_compat
|
@@ -28,15 +30,15 @@ module Rubyfb # :nodoc: all
|
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
31
|
-
class
|
32
|
-
|
33
|
-
|
34
|
-
sanitize_sql_array(sql_array)
|
35
|
-
end
|
33
|
+
class SQLBinder < ActiveRecord::Base
|
34
|
+
def self.bind(sql, binds)
|
35
|
+
sanitize_sql_array([sql] + binds)
|
36
36
|
end
|
37
|
-
|
37
|
+
end
|
38
|
+
|
39
|
+
class ProcedureCall
|
38
40
|
def sql_value_list(values)
|
39
|
-
|
41
|
+
Rubyfb::SQLBinder.bind(param_names.collect{|p| '?'}.join(','), param_names.collect{|p| values[p]})
|
40
42
|
end
|
41
43
|
end
|
42
44
|
end
|
@@ -44,7 +46,7 @@ end
|
|
44
46
|
module ActiveRecord
|
45
47
|
class Base
|
46
48
|
def self.rubyfb_connection(config) # :nodoc:
|
47
|
-
|
49
|
+
require 'rubyfb'
|
48
50
|
config.symbolize_keys!
|
49
51
|
db = Rubyfb::Database.new_from_config(config)
|
50
52
|
connection_params = config.values_at(:username, :password)
|
@@ -52,17 +54,42 @@ module ActiveRecord
|
|
52
54
|
connection_params << {Rubyfb::Connection::SQL_ROLE_NAME=>config[:sql_role_name]}
|
53
55
|
end
|
54
56
|
connection = db.connect(*connection_params)
|
55
|
-
|
57
|
+
|
58
|
+
if ActiveRecord::VERSION::MAJOR >= 3 && ActiveRecord::VERSION::MINOR >= 1
|
59
|
+
ConnectionAdapters::RubyfbAR31Adapter.new(connection, logger, connection_params)
|
60
|
+
else
|
61
|
+
ConnectionAdapters::RubyfbAdapter.new(connection, logger, connection_params)
|
62
|
+
end
|
56
63
|
end
|
57
64
|
|
58
|
-
after_save :
|
59
|
-
|
60
|
-
|
61
|
-
|
65
|
+
after_save :rubyfb_write_blobs
|
66
|
+
|
67
|
+
def rubyfb_write_blobs #:nodoc:
|
68
|
+
if connection.is_a?(ConnectionAdapters::RubyfbAdapter)
|
69
|
+
connection.write_blobs(self.class.table_name, self.class, attributes, true)
|
62
70
|
end
|
63
71
|
end
|
72
|
+
private :rubyfb_write_blobs
|
73
|
+
end
|
74
|
+
|
75
|
+
#FIXME ugly - but ... https://github.com/rails/rails/issues/1623
|
76
|
+
module FinderMethods
|
77
|
+
def exists?(id = nil)
|
78
|
+
id = id.id if ActiveRecord::Base === id
|
79
|
+
|
80
|
+
join_dependency = construct_join_dependency_for_association_find
|
81
|
+
relation = construct_relation_for_association_find(join_dependency)
|
82
|
+
relation = relation.except(:select).select("1 as o").limit(1)
|
83
|
+
|
84
|
+
case id
|
85
|
+
when Array, Hash
|
86
|
+
relation = relation.where(id)
|
87
|
+
else
|
88
|
+
relation = relation.where(table[primary_key].eq(id)) if id
|
89
|
+
end
|
64
90
|
|
65
|
-
|
91
|
+
connection.select_value(relation.to_sql) ? true : false
|
92
|
+
end
|
66
93
|
end
|
67
94
|
|
68
95
|
module ConnectionAdapters
|
@@ -71,18 +98,19 @@ module ActiveRecord
|
|
71
98
|
|
72
99
|
def initialize(connection, name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
|
73
100
|
@firebird_type = Rubyfb::SQLType.to_base_type(type, sub_type).to_s
|
101
|
+
@domain, @sub_type = domain, sub_type
|
74
102
|
|
75
103
|
super(name.downcase, nil, @firebird_type, !null_flag)
|
76
|
-
|
104
|
+
|
105
|
+
@precision, @scale = precision, (scale.nil? ? 0 : scale.abs)
|
77
106
|
@limit = decide_limit(length)
|
78
|
-
@domain, @sub_type, @precision, @scale = domain, sub_type, precision, (scale.nil? ? 0 : scale.abs)
|
79
107
|
@type = simplified_type(@firebird_type)
|
80
108
|
@default = parse_default(default_source) if default_source
|
81
109
|
@default = type_cast(decide_default(connection)) if @default
|
82
110
|
end
|
83
111
|
|
84
112
|
def self.value_to_boolean(value)
|
85
|
-
|
113
|
+
(TRUE_VALUES + [RubyfbAdapter.boolean_domain[:true]]).include?(value) || super
|
86
114
|
end
|
87
115
|
|
88
116
|
private
|
@@ -131,7 +159,7 @@ module ActiveRecord
|
|
131
159
|
when /decimal|numeric|number/i
|
132
160
|
@scale == 0 ? :integer : :decimal
|
133
161
|
when /blob/i
|
134
|
-
@
|
162
|
+
@sub_type == 1 ? :text : :binary
|
135
163
|
else
|
136
164
|
if @domain =~ RubyfbAdapter.boolean_domain[:domain_pattern] || name =~ RubyfbAdapter.boolean_domain[:name_pattern]
|
137
165
|
:boolean
|
@@ -253,7 +281,7 @@ module ActiveRecord
|
|
253
281
|
# or using a view instead.)
|
254
282
|
#
|
255
283
|
# == Connection Options
|
256
|
-
# The following options are supported by the
|
284
|
+
# The following options are supported by the Rubyfb adapter. None of the
|
257
285
|
# options have default values.
|
258
286
|
#
|
259
287
|
# <tt>:database</tt>::
|
@@ -297,16 +325,56 @@ module ActiveRecord
|
|
297
325
|
def initialize(connection, logger, connection_params = nil)
|
298
326
|
super(connection, logger)
|
299
327
|
@connection_params = connection_params
|
328
|
+
@transaction = nil
|
329
|
+
@blobs_disabled = 0
|
330
|
+
@statements = {}
|
331
|
+
end
|
332
|
+
|
333
|
+
ADAPTER_NAME = 'Rubyfb'.freeze
|
334
|
+
|
335
|
+
def adapter_name #:nodoc:
|
336
|
+
ADAPTER_NAME
|
337
|
+
end
|
338
|
+
|
339
|
+
def supports_migrations? #:nodoc:
|
340
|
+
true
|
300
341
|
end
|
301
342
|
|
302
|
-
def
|
303
|
-
|
343
|
+
def supports_statement_cache?
|
344
|
+
true
|
304
345
|
end
|
305
346
|
|
306
|
-
def
|
347
|
+
def supports_ddl_transactions?
|
307
348
|
true
|
308
349
|
end
|
309
350
|
|
351
|
+
# maximum length of identifiers
|
352
|
+
IDENTIFIER_MAX_LENGTH = 30
|
353
|
+
|
354
|
+
def table_alias_length #:nodoc:
|
355
|
+
IDENTIFIER_MAX_LENGTH
|
356
|
+
end
|
357
|
+
|
358
|
+
# the maximum length of a table name
|
359
|
+
def table_name_length
|
360
|
+
IDENTIFIER_MAX_LENGTH
|
361
|
+
end
|
362
|
+
|
363
|
+
# the maximum length of a column name
|
364
|
+
def column_name_length
|
365
|
+
IDENTIFIER_MAX_LENGTH
|
366
|
+
end
|
367
|
+
|
368
|
+
# the maximum length of an index name
|
369
|
+
def index_name_length
|
370
|
+
IDENTIFIER_MAX_LENGTH
|
371
|
+
end
|
372
|
+
|
373
|
+
def in_clause_length
|
374
|
+
1000
|
375
|
+
end
|
376
|
+
alias ids_in_list_limit in_clause_length
|
377
|
+
|
310
378
|
def native_database_types # :nodoc:
|
311
379
|
{
|
312
380
|
:primary_key => "BIGINT NOT NULL PRIMARY KEY",
|
@@ -332,7 +400,7 @@ module ActiveRecord
|
|
332
400
|
end
|
333
401
|
|
334
402
|
def default_sequence_name(table_name, primary_key = nil) # :nodoc:
|
335
|
-
"#{table_name}_seq"
|
403
|
+
"#{table_name}_seq".upcase
|
336
404
|
end
|
337
405
|
|
338
406
|
|
@@ -340,14 +408,11 @@ module ActiveRecord
|
|
340
408
|
|
341
409
|
# We use quoting in order to implement BLOB handling. In order to
|
342
410
|
# do this we quote a BLOB to an empty string which will force Firebird
|
343
|
-
# to create an empty BLOB in the db for us.
|
344
|
-
# other places besides insert/update like for column defaults. That is
|
345
|
-
# why we are checking caller to see where we're coming from. This isn't
|
346
|
-
# perfect but It works.
|
411
|
+
# to create an empty BLOB in the db for us.
|
347
412
|
def quote(value, column = nil) # :nodoc:
|
348
|
-
if [
|
349
|
-
"CAST('#{value.
|
350
|
-
elsif value && column && [:text, :binary].include?(column.type)
|
413
|
+
if [Date, Time].include?(value.class)
|
414
|
+
"CAST('#{type_cast(value, column).to_s(:db)}' AS #{value.acts_like?(:time) ? 'TIMESTAMP' : 'DATE'})"
|
415
|
+
elsif @blobs_disabled.nonzero? && value && column && [:text, :binary].include?(column.type)
|
351
416
|
"''"
|
352
417
|
else
|
353
418
|
super
|
@@ -370,37 +435,55 @@ module ActiveRecord
|
|
370
435
|
quote(boolean_domain[:false])
|
371
436
|
end
|
372
437
|
|
438
|
+
def type_cast(value, column)
|
439
|
+
case value
|
440
|
+
when true, false
|
441
|
+
value ? boolean_domain[:true] : boolean_domain[:false]
|
442
|
+
when Date, Time
|
443
|
+
if value.acts_like?(:time)
|
444
|
+
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
|
445
|
+
value = value.send(zone_conversion_method) if value.respond_to?(zone_conversion_method)
|
446
|
+
else
|
447
|
+
value
|
448
|
+
end
|
449
|
+
else
|
450
|
+
super
|
451
|
+
end
|
452
|
+
end
|
373
453
|
|
374
454
|
# CONNECTION MANAGEMENT ====================================
|
375
|
-
|
376
455
|
def active? # :nodoc:
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
456
|
+
return false if @connection.closed?
|
457
|
+
begin
|
458
|
+
execute('select first 1 cast(1 as smallint) from rdb$database')
|
459
|
+
true
|
460
|
+
rescue
|
461
|
+
false
|
462
|
+
end
|
384
463
|
end
|
385
464
|
|
386
465
|
def disconnect! # :nodoc:
|
466
|
+
clear_cache!
|
387
467
|
@connection.close rescue nil
|
388
468
|
end
|
389
469
|
|
470
|
+
def reset!
|
471
|
+
clear_cache!
|
472
|
+
super
|
473
|
+
end
|
474
|
+
|
390
475
|
def reconnect! # :nodoc:
|
391
476
|
disconnect!
|
392
477
|
@connection = @connection.database.connect(*@connection_params)
|
393
478
|
end
|
394
479
|
|
395
|
-
|
396
480
|
# DATABASE STATEMENTS ======================================
|
397
|
-
|
398
481
|
def select_rows(sql, name = nil)
|
399
482
|
select_raw(sql, name).last
|
400
483
|
end
|
401
484
|
|
402
485
|
def execute(sql, name = nil, &block) # :nodoc:
|
403
|
-
exec_result =
|
486
|
+
exec_result = exec_query(sql, name, [], &block)
|
404
487
|
if exec_result.instance_of?(Rubyfb::ResultSet)
|
405
488
|
exec_result.close
|
406
489
|
exec_result = nil
|
@@ -408,6 +491,47 @@ module ActiveRecord
|
|
408
491
|
return exec_result
|
409
492
|
end
|
410
493
|
|
494
|
+
def clear_cache!
|
495
|
+
@statements.each_value do |s|
|
496
|
+
s.close
|
497
|
+
end
|
498
|
+
@statements.clear
|
499
|
+
end
|
500
|
+
|
501
|
+
def exec_query(sql, name = 'SQL', binds = [], &block)
|
502
|
+
log(sql, name, binds) do
|
503
|
+
unless binds.empty?
|
504
|
+
cache = @statements[sql]
|
505
|
+
binds = binds.map do |col, val|
|
506
|
+
type_cast(val, col)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
s = cache || @connection.create_statement(sql)
|
510
|
+
s.prepare(@transaction) unless s.prepared?
|
511
|
+
if Rubyfb::Statement::DDL_STATEMENT == s.type
|
512
|
+
clear_cache!
|
513
|
+
elsif cache.nil? && !binds.empty?
|
514
|
+
@statements[sql] = cache = s
|
515
|
+
end
|
516
|
+
if cache
|
517
|
+
s.exec(binds, @transaction, &block)
|
518
|
+
else
|
519
|
+
s.exec_and_close(binds, @transaction, &block)
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def exec_insert(sql, name, binds)
|
525
|
+
with_blobs_disabled do
|
526
|
+
super
|
527
|
+
end
|
528
|
+
end
|
529
|
+
alias :exec_update :exec_insert
|
530
|
+
|
531
|
+
def last_inserted_id(result)
|
532
|
+
nil #TODO
|
533
|
+
end
|
534
|
+
|
411
535
|
def begin_db_transaction() # :nodoc:
|
412
536
|
@transaction = @connection.start_transaction
|
413
537
|
end
|
@@ -436,36 +560,56 @@ module ActiveRecord
|
|
436
560
|
# called directly; used by ActiveRecord to get the next primary key value
|
437
561
|
# when inserting a new database record (see #prefetch_primary_key?).
|
438
562
|
def next_sequence_value(sequence_name)
|
439
|
-
Rubyfb::Generator.new(sequence_name, @connection).next(1)
|
563
|
+
Rubyfb::Generator.new(quote_generator_name(sequence_name), @connection).next(1, @transaction)
|
440
564
|
end
|
441
565
|
|
442
566
|
# Inserts the given fixture into the table. Overridden to properly handle blobs.
|
443
|
-
def insert_fixture(fixture, table_name)
|
444
|
-
|
445
|
-
|
446
|
-
|
567
|
+
def insert_fixture(fixture, table_name) #:nodoc:
|
568
|
+
if ActiveRecord::Base.pluralize_table_names
|
569
|
+
klass = table_name.singularize.camelize
|
570
|
+
else
|
571
|
+
klass = table_name.camelize
|
572
|
+
end
|
573
|
+
klass = klass.constantize rescue nil
|
447
574
|
if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
|
448
|
-
|
575
|
+
with_blobs_disabled do
|
576
|
+
super
|
577
|
+
end
|
578
|
+
write_blobs(table_name, klass, fixture, false)
|
579
|
+
else
|
580
|
+
super
|
449
581
|
end
|
450
582
|
end
|
451
|
-
|
583
|
+
|
452
584
|
# Writes BLOB values from attributes, as indicated by the BLOB columns of klass.
|
453
|
-
def write_blobs(table_name, klass, attributes)
|
454
|
-
|
585
|
+
def write_blobs(table_name, klass, attributes, enable_coders) #:nodoc:
|
586
|
+
# is class with composite primary key>
|
587
|
+
is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
|
588
|
+
if is_with_cpk
|
589
|
+
id = klass.primary_key.map {|pk| attributes[pk.to_s] }
|
590
|
+
else
|
591
|
+
id = quote(attributes[klass.primary_key])
|
592
|
+
end
|
455
593
|
klass.columns.select { |col| col.sql_type =~ /BLOB$/i }.each do |col|
|
456
594
|
value = attributes[col.name]
|
457
|
-
value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
|
458
|
-
value = value.read if value.respond_to?(:read)
|
459
595
|
next if value.nil? || (value == '')
|
460
|
-
|
461
|
-
|
462
|
-
|
596
|
+
|
597
|
+
klass.serialized_attributes[col.name].tap do |coder|
|
598
|
+
if enable_coders && coder
|
599
|
+
value = dump_blob_value(col, coder, value)
|
600
|
+
elsif value.respond_to?(:read)
|
601
|
+
value = value.read
|
602
|
+
end
|
603
|
+
end
|
604
|
+
uncached do
|
605
|
+
sql = is_with_cpk ? "UPDATE #{quote_table_name(table_name)} set #{quote_column_name(col.name)} = ? WHERE #{klass.composite_where_clause(id)}" :
|
606
|
+
"UPDATE #{quote_table_name(table_name)} set #{quote_column_name(col.name)} = ? WHERE #{quote_column_name(klass.primary_key)} = #{id}"
|
607
|
+
@connection.execute_for(sql, [value.to_s], @transaction)
|
608
|
+
end
|
463
609
|
end
|
464
610
|
end
|
465
611
|
|
466
|
-
|
467
612
|
# SCHEMA STATEMENTS ========================================
|
468
|
-
|
469
613
|
def current_database # :nodoc:
|
470
614
|
file = @connection.database.file.split(':').last
|
471
615
|
File.basename(file, '.*')
|
@@ -492,7 +636,7 @@ module ActiveRecord
|
|
492
636
|
def primary_key(table_name)
|
493
637
|
if pk_row = index_metadata(table_name, true).to_a.first
|
494
638
|
pk_row[2].rstrip.downcase
|
495
|
-
|
639
|
+
end
|
496
640
|
end
|
497
641
|
|
498
642
|
def indexes(table_name, name = nil) # :nodoc:
|
@@ -508,9 +652,9 @@ module ActiveRecord
|
|
508
652
|
def columns(table_name, name = nil) # :nodoc:
|
509
653
|
sql = <<-end_sql
|
510
654
|
SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
|
511
|
-
f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
|
512
|
-
COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
|
513
|
-
COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
|
655
|
+
COALESCE(f.rdb$character_length, f.rdb$field_length) as rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
|
656
|
+
COALESCE(r.rdb$default_source, f.rdb$default_source) as rdb$default_source,
|
657
|
+
COALESCE(r.rdb$null_flag, f.rdb$null_flag) as rdb$null_flag
|
514
658
|
FROM rdb$relation_fields r
|
515
659
|
JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
|
516
660
|
WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}'
|
@@ -528,28 +672,70 @@ module ActiveRecord
|
|
528
672
|
end
|
529
673
|
end
|
530
674
|
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
675
|
+
alias_method :super_create_table, :create_table
|
676
|
+
def create_table_and_sequence(name, options = {}, &block) # :nodoc:
|
677
|
+
create_sequence = options[:id] != false
|
678
|
+
table_td = nil
|
679
|
+
super_create_table(name, options) do |td|
|
680
|
+
unless create_sequence
|
681
|
+
class << td
|
682
|
+
attr_accessor :create_sequence
|
683
|
+
def primary_key(*args)
|
684
|
+
self.create_sequence = true
|
685
|
+
super(*args)
|
686
|
+
end
|
687
|
+
end
|
688
|
+
table_td = td
|
689
|
+
end
|
690
|
+
yield td if block_given?
|
538
691
|
end
|
539
|
-
|
692
|
+
if create_sequence || table_td.create_sequence
|
540
693
|
sequence_name = options[:sequence] || default_sequence_name(name)
|
541
|
-
create_sequence(sequence_name)
|
694
|
+
create_sequence(name, sequence_name)
|
542
695
|
end
|
543
696
|
end
|
697
|
+
|
698
|
+
def create_table(name, options = {}, &block) # :nodoc:
|
699
|
+
create_table_and_sequence(name, options, &block)
|
700
|
+
rescue StatementInvalid
|
701
|
+
raise unless non_existent_domain_error?
|
702
|
+
create_boolean_domain
|
703
|
+
create_table_and_sequence(name, options, &block)
|
704
|
+
end
|
544
705
|
|
545
706
|
def drop_table(name, options = {}) # :nodoc:
|
546
707
|
super(name)
|
547
708
|
unless options[:sequence] == false
|
548
709
|
sequence_name = options[:sequence] || default_sequence_name(name)
|
549
|
-
|
710
|
+
if sequence_exists?(sequence_name)
|
711
|
+
drop_sequence(sequence_name)
|
712
|
+
end
|
550
713
|
end
|
551
714
|
end
|
552
715
|
|
716
|
+
# returned shortened index name if default is too large (from oracle-enhanced)
|
717
|
+
def index_name(table_name, options) #:nodoc:
|
718
|
+
default_name = super(table_name, options).to_s
|
719
|
+
# sometimes options can be String or Array with column names
|
720
|
+
options = {} unless options.is_a?(Hash)
|
721
|
+
identifier_max_length = options[:identifier_max_length] || index_name_length
|
722
|
+
return default_name if default_name.length <= identifier_max_length
|
723
|
+
|
724
|
+
# remove 'index', 'on' and 'and' keywords
|
725
|
+
shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
|
726
|
+
|
727
|
+
# leave just first three letters from each word
|
728
|
+
if shortened_name.length > identifier_max_length
|
729
|
+
shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
|
730
|
+
end
|
731
|
+
# generate unique name using hash function
|
732
|
+
if shortened_name.length > identifier_max_length
|
733
|
+
shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1]
|
734
|
+
end
|
735
|
+
@logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
|
736
|
+
shortened_name
|
737
|
+
end
|
738
|
+
|
553
739
|
def add_column(table_name, column_name, type, options = {}) # :nodoc:
|
554
740
|
super
|
555
741
|
rescue StatementInvalid
|
@@ -582,7 +768,7 @@ module ActiveRecord
|
|
582
768
|
end_sql
|
583
769
|
transaction do
|
584
770
|
add_column(table_name, TEMP_COLUMN_NAME, :string, :default => default)
|
585
|
-
|
771
|
+
exec_query(sql)
|
586
772
|
remove_column(table_name, TEMP_COLUMN_NAME)
|
587
773
|
end
|
588
774
|
end
|
@@ -592,17 +778,17 @@ module ActiveRecord
|
|
592
778
|
column_name = column_name.to_s.upcase
|
593
779
|
|
594
780
|
unless null || default.nil?
|
595
|
-
|
781
|
+
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
596
782
|
end
|
597
|
-
|
783
|
+
exec_query("UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = #{null ? 'null' : '1'} WHERE (RDB$FIELD_NAME = '#{column_name}') and (RDB$RELATION_NAME = '#{table_name}')")
|
598
784
|
end
|
599
785
|
|
600
786
|
def rename_column(table_name, column_name, new_column_name) # :nodoc:
|
601
|
-
|
787
|
+
exec_query("ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}")
|
602
788
|
end
|
603
789
|
|
604
790
|
def remove_index(table_name, options) #:nodoc:
|
605
|
-
|
791
|
+
exec_query("DROP INDEX #{quote_column_name(index_name(table_name, options))}")
|
606
792
|
end
|
607
793
|
|
608
794
|
def rename_table(name, new_name) # :nodoc:
|
@@ -644,30 +830,37 @@ module ActiveRecord
|
|
644
830
|
@connection.prepare_call(procedure_name).execute(values, @transaction)
|
645
831
|
end
|
646
832
|
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
833
|
+
protected
|
834
|
+
|
835
|
+
def log(sql, name, binds = nil) #:nodoc:
|
836
|
+
super sql, name
|
837
|
+
end
|
838
|
+
|
839
|
+
def dump_blob_value(column, coder, value)
|
840
|
+
column.text? ? value.to_yaml : value
|
841
|
+
end
|
842
|
+
|
843
|
+
def translate_exception(exception, message)
|
844
|
+
if exception.kind_of?(Rubyfb::FireRubyException)
|
845
|
+
case exception.sql_code
|
846
|
+
when -803
|
847
|
+
RecordNotUnique.new(message, exception)
|
848
|
+
when -530
|
849
|
+
InvalidForeignKey.new(message, exception)
|
850
|
+
else
|
851
|
+
super
|
661
852
|
end
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
853
|
+
else
|
854
|
+
super
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
private
|
859
|
+
def with_blobs_disabled
|
860
|
+
@blobs_disabled += 1
|
861
|
+
yield if block_given?
|
862
|
+
ensure
|
863
|
+
@blobs_disabled -= 1
|
671
864
|
end
|
672
865
|
|
673
866
|
def integer_sql_type(limit)
|
@@ -682,8 +875,8 @@ module ActiveRecord
|
|
682
875
|
limit.to_i <= 4 ? 'float' : 'double precision'
|
683
876
|
end
|
684
877
|
|
685
|
-
def select(sql, name = nil)
|
686
|
-
fields, rows = select_raw(sql, name)
|
878
|
+
def select(sql, name = nil, binds = [])
|
879
|
+
fields, rows = select_raw(sql, name, binds)
|
687
880
|
result = []
|
688
881
|
for row in rows
|
689
882
|
row_hash = {}
|
@@ -695,18 +888,36 @@ module ActiveRecord
|
|
695
888
|
result
|
696
889
|
end
|
697
890
|
|
698
|
-
def
|
891
|
+
def create_time_with_default_timezone(value)
|
892
|
+
year, month, day, hour, min, sec, usec = case value
|
893
|
+
when Time
|
894
|
+
[value.year, value.month, value.day, value.hour, value.min, value.sec, value.usec]
|
895
|
+
else
|
896
|
+
[value.year, value.month, value.day, value.hour, value.min, value.sec, 0]
|
897
|
+
end
|
898
|
+
# code from Time.time_with_datetime_fallback
|
899
|
+
begin
|
900
|
+
Time.send(Base.default_timezone, year, month, day, hour, min, sec, usec)
|
901
|
+
rescue
|
902
|
+
offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
|
903
|
+
::DateTime.civil(year, month, day, hour, min, sec, offset)
|
904
|
+
end
|
905
|
+
end
|
906
|
+
|
907
|
+
def select_raw(sql, name = nil, binds = [])
|
699
908
|
fields = []
|
700
909
|
rows = []
|
701
|
-
|
910
|
+
exec_query(sql, name, binds) do |row|
|
702
911
|
array_row = []
|
703
912
|
row.each do |column, value|
|
704
913
|
fields << fb_to_ar_case(column) if row.number == 1
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
914
|
+
case value
|
915
|
+
when Rubyfb::Blob
|
916
|
+
temp = value.to_s
|
917
|
+
value.close
|
918
|
+
value = temp
|
919
|
+
when Time, DateTime
|
920
|
+
value = create_time_with_default_timezone(value)
|
710
921
|
end
|
711
922
|
array_row << value
|
712
923
|
end
|
@@ -729,20 +940,20 @@ module ActiveRecord
|
|
729
940
|
sql << "AND (c.rdb$constraint_type IS NULL OR c.rdb$constraint_type != 'PRIMARY KEY')\n"
|
730
941
|
end
|
731
942
|
sql << "ORDER BY i.rdb$index_name, s.rdb$field_position\n"
|
732
|
-
|
943
|
+
exec_query(sql, name)
|
733
944
|
end
|
734
945
|
|
735
946
|
def change_column_type(table_name, column_name, type, options = {})
|
736
947
|
sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit])}"
|
737
|
-
|
948
|
+
exec_query(sql)
|
738
949
|
rescue StatementInvalid
|
739
950
|
raise unless non_existent_domain_error?
|
740
951
|
create_boolean_domain
|
741
|
-
|
952
|
+
exec_query(sql)
|
742
953
|
end
|
743
954
|
|
744
955
|
def change_column_position(table_name, column_name, position)
|
745
|
-
|
956
|
+
exec_query("ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} POSITION #{position}")
|
746
957
|
end
|
747
958
|
|
748
959
|
def copy_table(from, to)
|
@@ -777,24 +988,52 @@ module ActiveRecord
|
|
777
988
|
end
|
778
989
|
|
779
990
|
def copy_table_data(from, to)
|
780
|
-
|
991
|
+
exec_query("INSERT INTO #{to} SELECT * FROM #{from}", "Copy #{from} data to #{to}")
|
781
992
|
end
|
782
993
|
|
783
994
|
def copy_sequence_value(from, to)
|
784
|
-
sequence_value = Rubyfb::Generator.new(default_sequence_name(from), @connection).last
|
785
|
-
|
995
|
+
sequence_value = Rubyfb::Generator.new(quote_generator_name(default_sequence_name(from)), @connection).last(@transaction)
|
996
|
+
exec_query("SET GENERATOR #{quote_generator_name(default_sequence_name(to))} TO #{sequence_value}")
|
997
|
+
end
|
998
|
+
|
999
|
+
def quote_generator_name(generator_name)
|
1000
|
+
quote_table_name(generator_name.to_s)
|
786
1001
|
end
|
787
1002
|
|
788
1003
|
def sequence_exists?(sequence_name)
|
789
|
-
|
1004
|
+
#don't quote - here the generatod name is used as data not as metadata
|
1005
|
+
Rubyfb::Generator.exists?(sequence_name, @connection, @transaction)
|
790
1006
|
end
|
791
1007
|
|
792
|
-
def create_sequence(sequence_name)
|
793
|
-
Rubyfb::Generator.create(sequence_name
|
1008
|
+
def create_sequence(table_name, sequence_name)
|
1009
|
+
g = Rubyfb::Generator.create(quote_generator_name(sequence_name), @connection, @transaction)
|
1010
|
+
g.next(1000, @transaction) #leave a gap for tests
|
1011
|
+
|
1012
|
+
pk_sql = <<-end_sql
|
1013
|
+
SELECT s.rdb$field_name as field_name
|
1014
|
+
from RDB$RELATION_CONSTRAINTS c
|
1015
|
+
join rdb$index_segments s on s.rdb$index_name=c.RDB$INDEX_NAME
|
1016
|
+
WHERE c.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'
|
1017
|
+
and c.rdb$relation_name='#{table_name.to_s.upcase}'
|
1018
|
+
order by s.rdb$field_position
|
1019
|
+
end_sql
|
1020
|
+
pk_fields = select_values(pk_sql)
|
1021
|
+
if 1 == pk_fields.size
|
1022
|
+
trigger_sql = <<-end_sql
|
1023
|
+
CREATE TRIGGER #{quote_table_name(table_name.to_s + '_arsq')} FOR #{quote_table_name(table_name)}
|
1024
|
+
ACTIVE BEFORE INSERT POSITION 0
|
1025
|
+
AS
|
1026
|
+
BEGIN
|
1027
|
+
IF (NEW.#{quote_column_name(pk_fields[0].strip)} IS NULL) THEN
|
1028
|
+
NEW.#{quote_column_name(pk_fields[0].strip)} = GEN_ID(#{quote_generator_name(sequence_name)},1);
|
1029
|
+
END
|
1030
|
+
end_sql
|
1031
|
+
execute(trigger_sql)
|
1032
|
+
end
|
794
1033
|
end
|
795
1034
|
|
796
1035
|
def drop_sequence(sequence_name)
|
797
|
-
Rubyfb::Generator.new(sequence_name
|
1036
|
+
Rubyfb::Generator.new(quote_generator_name(sequence_name), @connection).drop(@transaction)
|
798
1037
|
end
|
799
1038
|
|
800
1039
|
def create_boolean_domain
|
@@ -802,7 +1041,7 @@ module ActiveRecord
|
|
802
1041
|
CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]}
|
803
1042
|
CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL)
|
804
1043
|
end_sql
|
805
|
-
|
1044
|
+
exec_query(sql) rescue nil
|
806
1045
|
end
|
807
1046
|
|
808
1047
|
def table_has_constraints_or_dependencies?(table_name)
|
@@ -835,5 +1074,16 @@ module ActiveRecord
|
|
835
1074
|
column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
|
836
1075
|
end
|
837
1076
|
end
|
1077
|
+
|
1078
|
+
class RubyfbAR31Adapter < RubyfbAdapter
|
1079
|
+
protected
|
1080
|
+
def log(sql, name, binds = nil) #:nodoc:
|
1081
|
+
super sql, name, binds
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
def dump_blob_value(column, coder, value)
|
1085
|
+
coder.dump(value)
|
1086
|
+
end
|
1087
|
+
end
|
838
1088
|
end
|
839
1089
|
end
|