caruby-core 1.5.5 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -0
- data/History.md +5 -1
- data/lib/caruby.rb +3 -5
- data/lib/caruby/caruby-src.tar.gz +0 -0
- data/lib/caruby/database.rb +53 -69
- data/lib/caruby/database/application_service.rb +25 -0
- data/lib/caruby/database/cache.rb +60 -0
- data/lib/caruby/database/fetched_matcher.rb +52 -38
- data/lib/caruby/database/lazy_loader.rb +4 -4
- data/lib/caruby/database/operation.rb +34 -0
- data/lib/caruby/database/persistable.rb +171 -86
- data/lib/caruby/database/persistence_service.rb +32 -34
- data/lib/caruby/database/persistifier.rb +100 -43
- data/lib/caruby/database/reader.rb +107 -85
- data/lib/caruby/database/reader_template_builder.rb +60 -0
- data/lib/caruby/database/saved_matcher.rb +3 -3
- data/lib/caruby/database/sql_executor.rb +88 -17
- data/lib/caruby/database/writer.rb +213 -177
- data/lib/caruby/database/writer_template_builder.rb +334 -0
- data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
- data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
- data/lib/caruby/{util → helpers}/person.rb +3 -3
- data/lib/caruby/{util → helpers}/properties.rb +7 -9
- data/lib/caruby/{util → helpers}/roman.rb +2 -2
- data/lib/caruby/{util → helpers}/version.rb +1 -1
- data/lib/caruby/json/deserializer.rb +2 -2
- data/lib/caruby/json/serializer.rb +49 -7
- data/lib/caruby/metadata.rb +30 -0
- data/lib/caruby/metadata/java_property.rb +21 -0
- data/lib/caruby/metadata/propertied.rb +191 -0
- data/lib/caruby/metadata/property.rb +22 -0
- data/lib/caruby/metadata/property_characteristics.rb +201 -0
- data/lib/caruby/migration/migratable.rb +11 -182
- data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
- data/lib/caruby/resource.rb +20 -823
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/database/cache_test.rb +54 -0
- data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
- data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
- data/test/lib/caruby/helpers/properties_test.rb +34 -0
- data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
- data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
- data/test/lib/helper.rb +7 -0
- metadata +161 -214
- data/lib/caruby/cli/application.rb +0 -36
- data/lib/caruby/cli/command.rb +0 -202
- data/lib/caruby/csv/csv_mapper.rb +0 -159
- data/lib/caruby/csv/csvio.rb +0 -203
- data/lib/caruby/database/search_template_builder.rb +0 -56
- data/lib/caruby/database/store_template_builder.rb +0 -278
- data/lib/caruby/domain.rb +0 -193
- data/lib/caruby/domain/attribute.rb +0 -584
- data/lib/caruby/domain/attributes.rb +0 -628
- data/lib/caruby/domain/dependency.rb +0 -225
- data/lib/caruby/domain/id_alias.rb +0 -22
- data/lib/caruby/domain/importer.rb +0 -183
- data/lib/caruby/domain/introspection.rb +0 -176
- data/lib/caruby/domain/inverse.rb +0 -172
- data/lib/caruby/domain/inversible.rb +0 -90
- data/lib/caruby/domain/java_attribute.rb +0 -173
- data/lib/caruby/domain/merge.rb +0 -185
- data/lib/caruby/domain/metadata.rb +0 -142
- data/lib/caruby/domain/mixin.rb +0 -35
- data/lib/caruby/domain/properties.rb +0 -95
- data/lib/caruby/domain/reference_visitor.rb +0 -428
- data/lib/caruby/domain/uniquify.rb +0 -50
- data/lib/caruby/import/java.rb +0 -387
- data/lib/caruby/migration/migrator.rb +0 -918
- data/lib/caruby/migration/resource_module.rb +0 -9
- data/lib/caruby/migration/uniquify.rb +0 -17
- data/lib/caruby/util/attribute_path.rb +0 -44
- data/lib/caruby/util/cache.rb +0 -56
- data/lib/caruby/util/class.rb +0 -149
- data/lib/caruby/util/collection.rb +0 -1152
- data/lib/caruby/util/domain_extent.rb +0 -46
- data/lib/caruby/util/file_separator.rb +0 -65
- data/lib/caruby/util/inflector.rb +0 -27
- data/lib/caruby/util/log.rb +0 -95
- data/lib/caruby/util/math.rb +0 -12
- data/lib/caruby/util/merge.rb +0 -59
- data/lib/caruby/util/module.rb +0 -18
- data/lib/caruby/util/options.rb +0 -97
- data/lib/caruby/util/partial_order.rb +0 -35
- data/lib/caruby/util/pretty_print.rb +0 -204
- data/lib/caruby/util/stopwatch.rb +0 -74
- data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
- data/lib/caruby/util/transitive_closure.rb +0 -55
- data/lib/caruby/util/tree.rb +0 -48
- data/lib/caruby/util/trie.rb +0 -37
- data/lib/caruby/util/uniquifier.rb +0 -30
- data/lib/caruby/util/validation.rb +0 -20
- data/lib/caruby/util/visitor.rb +0 -365
- data/lib/caruby/util/weak_hash.rb +0 -36
- data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
- data/test/lib/caruby/csv/csvio_test.rb +0 -69
- data/test/lib/caruby/database/persistable_test.rb +0 -92
- data/test/lib/caruby/domain/domain_test.rb +0 -112
- data/test/lib/caruby/domain/inversible_test.rb +0 -99
- data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
- data/test/lib/caruby/import/java_test.rb +0 -80
- data/test/lib/caruby/import/mixed_case_test.rb +0 -14
- data/test/lib/caruby/migration/test_case.rb +0 -102
- data/test/lib/caruby/test_case.rb +0 -230
- data/test/lib/caruby/util/cache_test.rb +0 -23
- data/test/lib/caruby/util/class_test.rb +0 -61
- data/test/lib/caruby/util/collection_test.rb +0 -398
- data/test/lib/caruby/util/command_test.rb +0 -55
- data/test/lib/caruby/util/domain_extent_test.rb +0 -60
- data/test/lib/caruby/util/file_separator_test.rb +0 -30
- data/test/lib/caruby/util/inflector_test.rb +0 -12
- data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
- data/test/lib/caruby/util/merge_test.rb +0 -83
- data/test/lib/caruby/util/module_test.rb +0 -25
- data/test/lib/caruby/util/options_test.rb +0 -59
- data/test/lib/caruby/util/partial_order_test.rb +0 -42
- data/test/lib/caruby/util/pretty_print_test.rb +0 -85
- data/test/lib/caruby/util/properties_test.rb +0 -50
- data/test/lib/caruby/util/stopwatch_test.rb +0 -18
- data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
- data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
- data/test/lib/caruby/util/tree_test.rb +0 -23
- data/test/lib/caruby/util/trie_test.rb +0 -14
- data/test/lib/caruby/util/visitor_test.rb +0 -278
- data/test/lib/caruby/util/weak_hash_test.rb +0 -45
- data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
- data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
@@ -1,188 +1,17 @@
|
|
1
|
-
require '
|
1
|
+
require 'jinx/migration/migratable'
|
2
2
|
|
3
3
|
module CaRuby
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# is as follows:
|
7
|
-
#
|
8
|
-
# 1. The migrator creates the Resource using the empty constructor.
|
9
|
-
#
|
10
|
-
# 2. Each input field value which maps to a Resource attribute is obtained from the
|
11
|
-
# migration source.
|
12
|
-
#
|
13
|
-
# 3. If the Resource class implements a method +migrate_+_attribute_ for the
|
14
|
-
# migration _attribute_, then that migrate method is called with the input value
|
15
|
-
# argument. If there is a migrate method, then the attribute is set to the
|
16
|
-
# result of calling that method, otherwise the attribute is set to the original
|
17
|
-
# input value.
|
18
|
-
#
|
19
|
-
# For example, if the +Name+ input field maps to +Participant.name+, then a
|
20
|
-
# custom +Participant+ +migrate_name+ shim method can be defined to reformat
|
21
|
-
# the input name.
|
22
|
-
#
|
23
|
-
# 4. The Resource attribute is set to the (possibly modified) value.
|
24
|
-
#
|
25
|
-
# 5. After all input fields are processed, then {#migration_valid?} is called to
|
26
|
-
# determine whether the migrated object can be used. {#migration_valid?} is true
|
27
|
-
# by default, but a migration shim can add a validation check,
|
28
|
-
# migrated Resource class to return false for special cases.
|
29
|
-
#
|
30
|
-
# For example, a custom +Participant+ +migration_valid?+ shim method can be
|
31
|
-
# defined to return whether there is a non-empty input field value.
|
32
|
-
#
|
33
|
-
# 6. After the migrated objects are validated, then the Migrator fills in
|
34
|
-
# dependency hierarchy gaps. For example, if the Resource class +Participant+
|
35
|
-
# owns the +enrollments+ dependent which in turn owns the +encounters+ dependent
|
36
|
-
# and the migration has created a +Participant+ and an +Encounter+ but no +Enrollment+,
|
37
|
-
# then an empty +Enrollment+ is created which is owned by the migrated +Participant+
|
38
|
-
# and owns the migrated +Encounter+.
|
39
|
-
#
|
40
|
-
# 7. After all dependencies are filled in, then the independent references are set
|
41
|
-
# for each created Resource (including the new dependents). If a created
|
42
|
-
# Resource has an independent non-collection Resource reference attribute
|
43
|
-
# and there is a migrated instance of that attribute type, then the attribute
|
44
|
-
# is set to that migrated instance.
|
45
|
-
#
|
46
|
-
# For example, if +Enrollment+ has a +study+ attribute and there is a
|
47
|
-
# single migrated +Study+ instance, then the +study+ attribute is set
|
48
|
-
# to that migrated +Study+ instance.
|
49
|
-
#
|
50
|
-
# If the referencing class implements a method +migrate_+_attribute_ for the
|
51
|
-
# migration _attribute_, then that migrate method is called with the referenced
|
52
|
-
# instance argument. The result is used to set the attribute. Otherwise, the
|
53
|
-
# attribute is set to the original referenced instance.
|
54
|
-
#
|
55
|
-
# There must be a single unambiguous candidate independent instance, e.g. in the
|
56
|
-
# unlikely but conceivable case that two +Study+ instances are migrated, then the
|
57
|
-
# +study+ attribute is not set. Similarly, collection attributes are not set,
|
58
|
-
# e.g. a +Study+ +protocols+ attribute is not set to a migrated +Protocol+
|
59
|
-
# instance.
|
60
|
-
#
|
61
|
-
# 8. The {#migrate} method is called to complete the migration. As described in the
|
62
|
-
# method documentation, a migration shim Resource subclass can override the
|
63
|
-
# method for custom migration processing, e.g. to migrate the ambiguous or
|
64
|
-
# collection attributes mentioned above, or to fill in missing values.
|
65
|
-
#
|
66
|
-
# Note that there is an extensive set of attribute defaults defined in
|
67
|
-
# the CaRuby::Metadata application domain classes. These defaults
|
68
|
-
# are applied in a migration database save action and need not be set in
|
69
|
-
# a migration shim. For example, if an acceptable default for a +Study+
|
70
|
-
# +active?+ flag is defined in the +Study+ meta-data, then the flag does not
|
71
|
-
# need to be set in a migration shim.
|
4
|
+
# The Migratable mix-in adds migration support for CaRuby {Resource} domain objects.
|
5
|
+
# This module augments the +Jinx::Migratable+ mix-in.
|
72
6
|
module Migratable
|
73
|
-
|
74
|
-
|
75
|
-
#
|
76
|
-
#
|
7
|
+
include Jinx::Migratable
|
8
|
+
|
9
|
+
# Overrides the default +Jinx::Migratable+ method to return this Resource's class
|
10
|
+
# {Propertied#saved_independent_attributes}.
|
77
11
|
#
|
78
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
#
|
82
|
-
# @param [{Symbol => Object}] row the input row field => value hash
|
83
|
-
# @param [<Resource>] migrated the migrated instances, including this domain object
|
84
|
-
def migrate(row, migrated)
|
85
|
-
end
|
86
|
-
|
87
|
-
# Returns whether this migration target domain object is valid. The default is true.
|
88
|
-
# A migration shim should override this method on the target if there are conditions
|
89
|
-
# which determine whether the migration should be skipped for this target object.
|
90
|
-
#
|
91
|
-
# @return [Boolean] whether this migration target domain object is valid
|
92
|
-
def migration_valid?
|
93
|
-
true
|
94
|
-
end
|
95
|
-
|
96
|
-
# Migrates this domain object's migratable references. This method is called by the
|
97
|
-
# CaRuby::Migrator and should not be overridden by subclasses. Subclasses tailor
|
98
|
-
# individual reference attribute migration by defining a +migrate_+_attribute_ method
|
99
|
-
# for the _attribute_ to modify.
|
100
|
-
#
|
101
|
-
# The migratable reference attributes consist of the non-collection
|
102
|
-
# {Domain::Attributes#saved_independent_attributes} and
|
103
|
-
# {Domain::Attributes#unidirectional_dependent_attributes} which don't already have a value.
|
104
|
-
# For each such migratable attribute, if there is a single instance of the attribute
|
105
|
-
# type in the given migrated domain objects, then the attribute is set to that
|
106
|
-
# migrated instance.
|
107
|
-
#
|
108
|
-
# If the attribute is associated with a method in mth_hash, then that method is called
|
109
|
-
# on the migrated instance and input row. The attribute is set to the method return value.
|
110
|
-
# mth_hash includes an entry for each +migrate_+_attribute_ method defined by this
|
111
|
-
# Resource's class.
|
112
|
-
#
|
113
|
-
# @param [{Symbol => Object}] row the input row field => value hash
|
114
|
-
# @param [<Resource>] migrated the migrated instances, including this Resource
|
115
|
-
# @param [{Symbol => String}, nil] mth_hash a hash that associates this domain object's
|
116
|
-
# attributes to migration method names
|
117
|
-
def migrate_references(row, migrated, mth_hash=nil)
|
118
|
-
# migrate the owner
|
119
|
-
migratable__migrate_owner(row, migrated, mth_hash)
|
120
|
-
# migrate the remaining attributes
|
121
|
-
migratable__set_nonowner_references(self.class.saved_independent_attributes, row, migrated, mth_hash)
|
122
|
-
migratable__set_nonowner_references(self.class.unidirectional_dependent_attributes, row, migrated, mth_hash)
|
123
|
-
end
|
124
|
-
|
125
|
-
private
|
126
|
-
|
127
|
-
# Migrates the owner, if there is a unique owner in the migrated set.
|
128
|
-
#
|
129
|
-
# @param row (see #migrate_references)
|
130
|
-
# @param migrated (see #migrate_references)
|
131
|
-
# @param mth_hash (see #migrate_references)
|
132
|
-
def migratable__migrate_owner(row, migrated, mth_hash=nil)
|
133
|
-
# the owner attributes=> migrated reference hash
|
134
|
-
ovh = self.class.owner_attributes.to_compact_hash do |attr|
|
135
|
-
attr_md = self.class.attribute_metadata(attr)
|
136
|
-
migratable__target_value(attr_md, row, migrated, mth_hash=nil)
|
137
|
-
end
|
138
|
-
if ovh.size > 1 then
|
139
|
-
logger.debug { "The migrated dependent #{qp} has ambiguous migrated owner references #{ovh.qp}." }
|
140
|
-
elsif ovh.size == 1 then
|
141
|
-
attr, ref = ovh.first
|
142
|
-
set_attribute(attr, ref)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
# @param [Attribute::Filter] the attributes to set
|
147
|
-
# @param row (see #migrate_references)
|
148
|
-
# @param migrated (see #migrate_references)
|
149
|
-
# @param mth_hash (see #migrate_references)
|
150
|
-
def migratable__set_nonowner_references(attr_filter, row, migrated, mth_hash=nil)
|
151
|
-
attr_filter.each_pair do |attr, attr_md|
|
152
|
-
# skip owners
|
153
|
-
next if attr_md.owner?
|
154
|
-
# the target value
|
155
|
-
ref = migratable__target_value(attr_md, row, migrated, mth_hash) || next
|
156
|
-
if attr_md.collection? then
|
157
|
-
# the current value
|
158
|
-
value = send(attr_md.reader) || next
|
159
|
-
value << ref
|
160
|
-
logger.debug { "Added the migrated #{ref.qp} to #{qp} #{attr}." }
|
161
|
-
else
|
162
|
-
set_attribute(attr, ref)
|
163
|
-
logger.debug { "Set the #{qp} #{attr} to migrated #{ref.qp}." }
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
# @param [Attribute] attr_md the reference attribute
|
169
|
-
# @param row (see #migrate_references)
|
170
|
-
# @param migrated (see #migrate_references)
|
171
|
-
# @param mth_hash (see #migrate_references)
|
172
|
-
# @return [Resource, nil] the migrated instance of the given class, or nil if there is not
|
173
|
-
# exactly one such instance
|
174
|
-
def migratable__target_value(attr_md, row, migrated, mth_hash=nil)
|
175
|
-
# the migrated references which are instances of the attribute type
|
176
|
-
refs = migrated.select { |other| other != self and attr_md.type === other }
|
177
|
-
# skip ambiguous references
|
178
|
-
if refs.size > 1 then logger.debug { "Migrator did not set references to ambiguous targets #{refs.pp_s}." } end
|
179
|
-
return unless refs.size == 1
|
180
|
-
# the single reference
|
181
|
-
ref = refs.first
|
182
|
-
# the shim method, if any
|
183
|
-
mth = mth_hash[attr_md.to_sym] if mth_hash
|
184
|
-
# if there is a shim method, then call it
|
185
|
-
mth && respond_to?(mth) ? send(mth, ref, row) : ref
|
12
|
+
# @return the attributes to migrate
|
13
|
+
def migratable_independent_attributes
|
14
|
+
self.class.saved_independent_attributes
|
186
15
|
end
|
187
16
|
end
|
188
|
-
end
|
17
|
+
end
|
@@ -0,0 +1,446 @@
|
|
1
|
+
require 'java'
|
2
|
+
require 'rdbi'
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
class RDBI::Driver::JDBC < RDBI::Driver
|
6
|
+
def initialize(*args)
|
7
|
+
super Database, *args
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class RDBI::Driver::JDBC < RDBI::Driver
|
12
|
+
|
13
|
+
SQL_TYPES = {
|
14
|
+
1 => {:type => "CHAR", :ruby_type => :default},
|
15
|
+
2 => {:type => "NUMERIC", :ruby_type => :decimal},
|
16
|
+
3 => {:type => "DECIMAL", :ruby_type => :decimal},
|
17
|
+
4 => {:type => "INTEGER", :ruby_type => :integer},
|
18
|
+
5 => {:type => "SMALLINT", :ruby_type => :integer},
|
19
|
+
6 => {:type => "FLOAT", :ruby_type => :decimal},
|
20
|
+
7 => {:type => "REAL", :ruby_type => :decimal},
|
21
|
+
8 => {:type => "DOUBLE", :ruby_type => :decimal},
|
22
|
+
9 => {:type => "DATE", :ruby_type => :date},
|
23
|
+
10 => {:type => "TIME", :ruby_type => :time},
|
24
|
+
11 => {:type => "TIMESTAMP", :ruby_type => :timestamp},
|
25
|
+
12 => {:type => "VARCHAR", :ruby_type => :default},
|
26
|
+
13 => {:type => "BOOLEAN", :ruby_type => :boolean},
|
27
|
+
91 => {:type => "DATE", :ruby_type => :date},
|
28
|
+
92 => {:type => "TIME", :ruby_type => :time},
|
29
|
+
93 => {:type => "TIMESTAMP", :ruby_type => :timestamp},
|
30
|
+
100 => {:type => nil, :ruby_type => :default},
|
31
|
+
-1 => {:type => "LONG VARCHAR", :ruby_type => :default},
|
32
|
+
-2 => {:type => "BINARY", :ruby_type => :default},
|
33
|
+
-3 => {:type => "VARBINARY", :ruby_type => :default},
|
34
|
+
-4 => {:type => "LONG VARBINARY", :ruby_type => :default},
|
35
|
+
-5 => {:type => "BIGINT", :ruby_type => :integer},
|
36
|
+
-6 => {:type => "TINYINT", :ruby_type => :integer},
|
37
|
+
-7 => {:type => "BIT", :ruby_type => :default},
|
38
|
+
-8 => {:type => "CHAR", :ruby_type => :default},
|
39
|
+
-10 => {:type => "BLOB", :ruby_type => :default},
|
40
|
+
-11 => {:type => "CLOB", :ruby_type => :default},
|
41
|
+
}
|
42
|
+
|
43
|
+
class Database < RDBI::Database
|
44
|
+
|
45
|
+
attr_accessor :handle
|
46
|
+
|
47
|
+
def initialize(*args)
|
48
|
+
super *args
|
49
|
+
|
50
|
+
database = @connect_args[:database] || @connect_args[:dbname] ||
|
51
|
+
@connect_args[:db]
|
52
|
+
username = @connect_args[:username] || @connect_args[:user]
|
53
|
+
password = @connect_args[:password] || @connect_args[:pass]
|
54
|
+
|
55
|
+
# the driver class
|
56
|
+
driver_class = @connect_args[:driver_class]
|
57
|
+
raise DatabaseError.new('Missing JDBC driver class') unless driver_class
|
58
|
+
clazz = java.lang.Class.forName(driver_class, true, JRuby.runtime.jruby_class_loader)
|
59
|
+
java.sql.DriverManager.registerDriver(clazz.newInstance)
|
60
|
+
|
61
|
+
@handle = java.sql.DriverManager.getConnection(
|
62
|
+
"#{database}",
|
63
|
+
username,
|
64
|
+
password
|
65
|
+
)
|
66
|
+
|
67
|
+
self.database_name = @handle.getCatalog
|
68
|
+
end
|
69
|
+
|
70
|
+
def disconnect
|
71
|
+
@handle.rollback if @handle.getAutoCommit == false
|
72
|
+
@handle.close
|
73
|
+
super
|
74
|
+
end
|
75
|
+
|
76
|
+
def transaction(&block)
|
77
|
+
raise RDBI::TransactionError, "Already in transaction" if in_transaction?
|
78
|
+
@handle.setAutoCommit false
|
79
|
+
@handle.setSavepoint
|
80
|
+
super
|
81
|
+
@handle.commit
|
82
|
+
@handle.setAutoCommit true
|
83
|
+
end
|
84
|
+
|
85
|
+
def rollback
|
86
|
+
@handle.rollback if @handle.getAutoCommit == false
|
87
|
+
super
|
88
|
+
end
|
89
|
+
|
90
|
+
def commit
|
91
|
+
@handle.commit if @handle.getAutoCommit == false
|
92
|
+
super
|
93
|
+
end
|
94
|
+
|
95
|
+
def new_statement(query)
|
96
|
+
Statement.new(query, self)
|
97
|
+
end
|
98
|
+
|
99
|
+
def table_schema(table_name)
|
100
|
+
new_statement(
|
101
|
+
"SELECT * FROM #{table_name} WHERE 1=2"
|
102
|
+
).new_execution[1]
|
103
|
+
end
|
104
|
+
|
105
|
+
def schema
|
106
|
+
rs = @handle.getMetaData.getTables(nil, nil, nil, nil)
|
107
|
+
tables = []
|
108
|
+
while rs.next
|
109
|
+
tables << table_schema(rs.getString(3))
|
110
|
+
end
|
111
|
+
tables
|
112
|
+
end
|
113
|
+
|
114
|
+
def ping
|
115
|
+
!@handle.isClosed
|
116
|
+
end
|
117
|
+
|
118
|
+
def quote(item)
|
119
|
+
case item
|
120
|
+
when Numeric
|
121
|
+
item.to_s
|
122
|
+
when TrueClass
|
123
|
+
"1"
|
124
|
+
when FalseClass
|
125
|
+
"0"
|
126
|
+
when NilClass
|
127
|
+
"NULL"
|
128
|
+
else
|
129
|
+
"'#{item.to_s}'"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
class Cursor < RDBI::Cursor
|
136
|
+
|
137
|
+
# TODO: update this to use real calls, not array
|
138
|
+
# to get this working, we'll just build the array for now.
|
139
|
+
def initialize(handle)
|
140
|
+
super handle
|
141
|
+
|
142
|
+
@index = 0
|
143
|
+
@rs = []
|
144
|
+
|
145
|
+
return if handle.nil?
|
146
|
+
|
147
|
+
rs = handle.getResultSet
|
148
|
+
metadata = rs.getMetaData
|
149
|
+
|
150
|
+
while rs.next
|
151
|
+
data = []
|
152
|
+
(1..metadata.getColumnCount).each do |n|
|
153
|
+
data << parse_column(rs, n, metadata)
|
154
|
+
end
|
155
|
+
@rs << data
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def next_row
|
160
|
+
return nil if last_row?
|
161
|
+
val = @rs[@index]
|
162
|
+
@index += 1
|
163
|
+
val
|
164
|
+
end
|
165
|
+
|
166
|
+
def result_count
|
167
|
+
@rs.size
|
168
|
+
end
|
169
|
+
|
170
|
+
def affected_count
|
171
|
+
0
|
172
|
+
end
|
173
|
+
|
174
|
+
def first
|
175
|
+
@rs.first
|
176
|
+
end
|
177
|
+
|
178
|
+
def last
|
179
|
+
@rs.last
|
180
|
+
end
|
181
|
+
|
182
|
+
def rest
|
183
|
+
@rs[@index..-1]
|
184
|
+
end
|
185
|
+
|
186
|
+
def all
|
187
|
+
@rs
|
188
|
+
end
|
189
|
+
|
190
|
+
def fetch(count = 1)
|
191
|
+
return [] if last_row?
|
192
|
+
@rs[@index, count]
|
193
|
+
end
|
194
|
+
|
195
|
+
def [](index)
|
196
|
+
@rs[index]
|
197
|
+
end
|
198
|
+
|
199
|
+
def last_row?
|
200
|
+
@index == @rs.size
|
201
|
+
end
|
202
|
+
|
203
|
+
def empty?
|
204
|
+
@rs.empty?
|
205
|
+
end
|
206
|
+
|
207
|
+
def rewind
|
208
|
+
@index = 0
|
209
|
+
end
|
210
|
+
|
211
|
+
def size
|
212
|
+
@rs.length
|
213
|
+
end
|
214
|
+
|
215
|
+
def finish
|
216
|
+
@handle.close
|
217
|
+
end
|
218
|
+
|
219
|
+
def coerce_to_array
|
220
|
+
@rs
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
def parse_column(rs, n, metadata)
|
226
|
+
return nil unless rs.getObject(n)
|
227
|
+
case metadata.getColumnType(n)
|
228
|
+
when java.sql.Types::BIT
|
229
|
+
rs.getBoolean(n)
|
230
|
+
when java.sql.Types::NUMERIC, java.sql.Types::DECIMAL
|
231
|
+
case metadata.getScale(n)
|
232
|
+
when 0
|
233
|
+
rs.getLong(n)
|
234
|
+
else
|
235
|
+
rs.getDouble(n)
|
236
|
+
end
|
237
|
+
when java.sql.Types::DATE
|
238
|
+
cal = calendar_for rs.getDate(n)
|
239
|
+
|
240
|
+
Date.new(cal.get(java.util.Calendar::YEAR),
|
241
|
+
cal.get(java.util.Calendar::MONTH)+1,
|
242
|
+
cal.get(java.util.Calendar::DAY_OF_MONTH)
|
243
|
+
)
|
244
|
+
when java.sql.Types::TIME
|
245
|
+
cal = calendar_for rs.getTime(n)
|
246
|
+
|
247
|
+
Time.mktime(cal.get(java.util.Calendar::YEAR),
|
248
|
+
cal.get(java.util.Calendar::MONTH)+1,
|
249
|
+
cal.get(java.util.Calendar::DAY_OF_MONTH),
|
250
|
+
cal.get(java.util.Calendar::HOUR_OF_DAY),
|
251
|
+
cal.get(java.util.Calendar::MINUTE),
|
252
|
+
cal.get(java.util.Calendar::SECOND),
|
253
|
+
cal.get(java.util.Calendar::MILLISECOND) * 1000
|
254
|
+
)
|
255
|
+
when java.sql.Types::TIMESTAMP
|
256
|
+
cal = calendar_for rs.getTimestamp(n)
|
257
|
+
|
258
|
+
DateTime.new(cal.get(java.util.Calendar::YEAR),
|
259
|
+
cal.get(java.util.Calendar::MONTH)+1,
|
260
|
+
cal.get(java.util.Calendar::DAY_OF_MONTH),
|
261
|
+
cal.get(java.util.Calendar::HOUR_OF_DAY),
|
262
|
+
cal.get(java.util.Calendar::MINUTE),
|
263
|
+
cal.get(java.util.Calendar::SECOND),
|
264
|
+
cal.get(java.util.Calendar::MILLISECOND) * 1000
|
265
|
+
)
|
266
|
+
else
|
267
|
+
rs.getObject(n)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def calendar_for(col)
|
272
|
+
cal = java.util.Calendar.getInstance
|
273
|
+
cal.setTime(java.util.Date.new(col.getTime))
|
274
|
+
cal
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
class Statement < RDBI::Statement
|
279
|
+
|
280
|
+
attr_accessor :handle
|
281
|
+
|
282
|
+
def initialize(query, dbh)
|
283
|
+
super
|
284
|
+
|
285
|
+
@handle = @dbh.handle.prepareStatement(query)
|
286
|
+
@input_type_map = build_input_type_map
|
287
|
+
@output_type_map = build_output_type_map
|
288
|
+
end
|
289
|
+
|
290
|
+
def new_execution(*binds)
|
291
|
+
apply_bindings(*binds)
|
292
|
+
|
293
|
+
if @handle.execute
|
294
|
+
metadata = @handle.getResultSet.getMetaData
|
295
|
+
|
296
|
+
columns, tables = [], []
|
297
|
+
|
298
|
+
(1..metadata.getColumnCount).each do |n|
|
299
|
+
newcol = RDBI::Column.new
|
300
|
+
newcol.name = metadata.getColumnName(n).to_sym
|
301
|
+
newcol.type = SQL_TYPES[metadata.getColumnType(n)][:type]
|
302
|
+
newcol.ruby_type = SQL_TYPES[metadata.getColumnType(n)][:ruby_type]
|
303
|
+
newcol.precision = metadata.getPrecision(n)
|
304
|
+
newcol.scale = metadata.getScale(n)
|
305
|
+
newcol.nullable = metadata.isNullable(n) == 1 ? true : false
|
306
|
+
newcol.table = metadata.getTableName(n)
|
307
|
+
#newcol.primary_key = false
|
308
|
+
|
309
|
+
columns << newcol
|
310
|
+
end
|
311
|
+
tables = columns.map(&:table).uniq.reject{|t| t == ""}
|
312
|
+
|
313
|
+
# primary_keys = Hash.new{|h,k| h[k] = []}
|
314
|
+
# tables.each do |tbl|
|
315
|
+
# rs = @dbh.handle.getMetaData.getPrimaryKeys(nil, nil, tbl)
|
316
|
+
# while rs.next
|
317
|
+
# primary_keys[tbl] << rs.getString("COLUMN_NAME").to_sym
|
318
|
+
# end
|
319
|
+
# end
|
320
|
+
# columns.each do |col|
|
321
|
+
# col.primary_key = true if primary_keys[col.table].include? col.name
|
322
|
+
# end
|
323
|
+
return [
|
324
|
+
Cursor.new(@handle),
|
325
|
+
RDBI::Schema.new(columns, tables),
|
326
|
+
@output_type_map
|
327
|
+
]
|
328
|
+
end
|
329
|
+
|
330
|
+
return [
|
331
|
+
Cursor.new(nil),
|
332
|
+
RDBI::Schema.new(nil, nil),
|
333
|
+
@output_type_map
|
334
|
+
]
|
335
|
+
end
|
336
|
+
|
337
|
+
def finish
|
338
|
+
@handle.close
|
339
|
+
super
|
340
|
+
end
|
341
|
+
|
342
|
+
private
|
343
|
+
|
344
|
+
def build_input_type_map
|
345
|
+
input_type_map = RDBI::Type.create_type_hash(RDBI::Type::In)
|
346
|
+
|
347
|
+
input_type_map[NilClass] = [TypeLib::Filter.new(
|
348
|
+
proc{|o| o.nil?},
|
349
|
+
proc{|o| java.sql.Types::VARCHAR}
|
350
|
+
)]
|
351
|
+
|
352
|
+
input_type_map[String] = [TypeLib::Filter.new(
|
353
|
+
proc{|o| o.is_a? String},
|
354
|
+
proc{|o| java.lang.String.new(o)}
|
355
|
+
)]
|
356
|
+
|
357
|
+
input_type_map[Date] = [TypeLib::Filter.new(
|
358
|
+
proc{|o| o.is_a? Date},
|
359
|
+
proc{|o|
|
360
|
+
cal = apply_date_fields(java.util.Calendar.getInstance, o)
|
361
|
+
java.sql.Date.new(cal.getTime.getTime)
|
362
|
+
}
|
363
|
+
)]
|
364
|
+
|
365
|
+
input_type_map[Time] = [TypeLib::Filter.new(
|
366
|
+
proc{|o| o.is_a? Time},
|
367
|
+
proc{|o|
|
368
|
+
cal = apply_time_fields(java.util.Calendar.getInstance, o)
|
369
|
+
java.sql.Time.new(cal.getTime.getTime)
|
370
|
+
}
|
371
|
+
)]
|
372
|
+
|
373
|
+
input_type_map[DateTime] = [TypeLib::Filter.new(
|
374
|
+
proc{|o| o.is_a? DateTime},
|
375
|
+
proc{|o|
|
376
|
+
cal = apply_date_fields(java.util.Calendar.getInstance, o)
|
377
|
+
cal = apply_time_fields(cal, o)
|
378
|
+
java.sql.Timestamp.new(cal.getTime.getTime)
|
379
|
+
}
|
380
|
+
)]
|
381
|
+
|
382
|
+
input_type_map
|
383
|
+
end
|
384
|
+
|
385
|
+
def build_output_type_map
|
386
|
+
RDBI::Type.create_type_hash(RDBI::Type::Out)
|
387
|
+
end
|
388
|
+
|
389
|
+
def apply_bindings(*binds)
|
390
|
+
@handle.clearParameters
|
391
|
+
binds.each_with_index do |val, n|
|
392
|
+
bind_param val, n+1
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def bind_param(val, n)
|
397
|
+
case val
|
398
|
+
when nil
|
399
|
+
@handle.setNull(n, val)
|
400
|
+
when String
|
401
|
+
@handle.setString(n, val)
|
402
|
+
when Java::JavaLang::String
|
403
|
+
@handle.setString(n, val)
|
404
|
+
when Fixnum
|
405
|
+
@handle.setLong(n, val)
|
406
|
+
when Java::JavaLang::Integer
|
407
|
+
@handle.setLong(n, val)
|
408
|
+
when Java::JavaLang::Character
|
409
|
+
@handle.setLong(n, val)
|
410
|
+
when Java::JavaLang::Short
|
411
|
+
@handle.setLong(n, val)
|
412
|
+
when Java::JavaLang::Long
|
413
|
+
@handle.setLong(n, val)
|
414
|
+
when Numeric
|
415
|
+
@handle.setDouble(n, val)
|
416
|
+
when Java::JavaLang::Float
|
417
|
+
@handle.setDouble(n, val)
|
418
|
+
when Java::JavaLang::Double
|
419
|
+
@handle.setDouble(n, val)
|
420
|
+
when Date
|
421
|
+
@handle.setDate(n, val)
|
422
|
+
when Time
|
423
|
+
@handle.setTime(n, val)
|
424
|
+
when DateTime
|
425
|
+
@handle.setTimestamp(n, val)
|
426
|
+
else
|
427
|
+
@handle.setObject(n, val)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def apply_date_fields(cal, date)
|
432
|
+
cal.set(java.util.Calendar::YEAR, date.year)
|
433
|
+
cal.set(java.util.Calendar::MONTH, date.month-1)
|
434
|
+
cal.set(java.util.Calendar::DAY_OF_MONTH, date.day)
|
435
|
+
cal
|
436
|
+
end
|
437
|
+
|
438
|
+
def apply_time_fields(cal, time)
|
439
|
+
cal.set(java.util.Calendar::HOUR_OF_DAY, time.hour)
|
440
|
+
cal.set(java.util.Calendar::MINUTE, time.min)
|
441
|
+
cal.set(java.util.Calendar::SECOND, time.sec)
|
442
|
+
cal.set(java.util.Calendar::MILLISECOND, time.strftime("%L").to_i)
|
443
|
+
cal
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|