rubyfb 0.5.9 → 0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|