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.
Files changed (126) hide show
  1. data/Gemfile +9 -0
  2. data/History.md +5 -1
  3. data/lib/caruby.rb +3 -5
  4. data/lib/caruby/caruby-src.tar.gz +0 -0
  5. data/lib/caruby/database.rb +53 -69
  6. data/lib/caruby/database/application_service.rb +25 -0
  7. data/lib/caruby/database/cache.rb +60 -0
  8. data/lib/caruby/database/fetched_matcher.rb +52 -38
  9. data/lib/caruby/database/lazy_loader.rb +4 -4
  10. data/lib/caruby/database/operation.rb +34 -0
  11. data/lib/caruby/database/persistable.rb +171 -86
  12. data/lib/caruby/database/persistence_service.rb +32 -34
  13. data/lib/caruby/database/persistifier.rb +100 -43
  14. data/lib/caruby/database/reader.rb +107 -85
  15. data/lib/caruby/database/reader_template_builder.rb +60 -0
  16. data/lib/caruby/database/saved_matcher.rb +3 -3
  17. data/lib/caruby/database/sql_executor.rb +88 -17
  18. data/lib/caruby/database/writer.rb +213 -177
  19. data/lib/caruby/database/writer_template_builder.rb +334 -0
  20. data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
  21. data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
  22. data/lib/caruby/{util → helpers}/person.rb +3 -3
  23. data/lib/caruby/{util → helpers}/properties.rb +7 -9
  24. data/lib/caruby/{util → helpers}/roman.rb +2 -2
  25. data/lib/caruby/{util → helpers}/version.rb +1 -1
  26. data/lib/caruby/json/deserializer.rb +2 -2
  27. data/lib/caruby/json/serializer.rb +49 -7
  28. data/lib/caruby/metadata.rb +30 -0
  29. data/lib/caruby/metadata/java_property.rb +21 -0
  30. data/lib/caruby/metadata/propertied.rb +191 -0
  31. data/lib/caruby/metadata/property.rb +22 -0
  32. data/lib/caruby/metadata/property_characteristics.rb +201 -0
  33. data/lib/caruby/migration/migratable.rb +11 -182
  34. data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
  35. data/lib/caruby/resource.rb +20 -823
  36. data/lib/caruby/version.rb +1 -1
  37. data/test/lib/caruby/database/cache_test.rb +54 -0
  38. data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
  39. data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
  40. data/test/lib/caruby/helpers/properties_test.rb +34 -0
  41. data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
  42. data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
  43. data/test/lib/helper.rb +7 -0
  44. metadata +161 -214
  45. data/lib/caruby/cli/application.rb +0 -36
  46. data/lib/caruby/cli/command.rb +0 -202
  47. data/lib/caruby/csv/csv_mapper.rb +0 -159
  48. data/lib/caruby/csv/csvio.rb +0 -203
  49. data/lib/caruby/database/search_template_builder.rb +0 -56
  50. data/lib/caruby/database/store_template_builder.rb +0 -278
  51. data/lib/caruby/domain.rb +0 -193
  52. data/lib/caruby/domain/attribute.rb +0 -584
  53. data/lib/caruby/domain/attributes.rb +0 -628
  54. data/lib/caruby/domain/dependency.rb +0 -225
  55. data/lib/caruby/domain/id_alias.rb +0 -22
  56. data/lib/caruby/domain/importer.rb +0 -183
  57. data/lib/caruby/domain/introspection.rb +0 -176
  58. data/lib/caruby/domain/inverse.rb +0 -172
  59. data/lib/caruby/domain/inversible.rb +0 -90
  60. data/lib/caruby/domain/java_attribute.rb +0 -173
  61. data/lib/caruby/domain/merge.rb +0 -185
  62. data/lib/caruby/domain/metadata.rb +0 -142
  63. data/lib/caruby/domain/mixin.rb +0 -35
  64. data/lib/caruby/domain/properties.rb +0 -95
  65. data/lib/caruby/domain/reference_visitor.rb +0 -428
  66. data/lib/caruby/domain/uniquify.rb +0 -50
  67. data/lib/caruby/import/java.rb +0 -387
  68. data/lib/caruby/migration/migrator.rb +0 -918
  69. data/lib/caruby/migration/resource_module.rb +0 -9
  70. data/lib/caruby/migration/uniquify.rb +0 -17
  71. data/lib/caruby/util/attribute_path.rb +0 -44
  72. data/lib/caruby/util/cache.rb +0 -56
  73. data/lib/caruby/util/class.rb +0 -149
  74. data/lib/caruby/util/collection.rb +0 -1152
  75. data/lib/caruby/util/domain_extent.rb +0 -46
  76. data/lib/caruby/util/file_separator.rb +0 -65
  77. data/lib/caruby/util/inflector.rb +0 -27
  78. data/lib/caruby/util/log.rb +0 -95
  79. data/lib/caruby/util/math.rb +0 -12
  80. data/lib/caruby/util/merge.rb +0 -59
  81. data/lib/caruby/util/module.rb +0 -18
  82. data/lib/caruby/util/options.rb +0 -97
  83. data/lib/caruby/util/partial_order.rb +0 -35
  84. data/lib/caruby/util/pretty_print.rb +0 -204
  85. data/lib/caruby/util/stopwatch.rb +0 -74
  86. data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
  87. data/lib/caruby/util/transitive_closure.rb +0 -55
  88. data/lib/caruby/util/tree.rb +0 -48
  89. data/lib/caruby/util/trie.rb +0 -37
  90. data/lib/caruby/util/uniquifier.rb +0 -30
  91. data/lib/caruby/util/validation.rb +0 -20
  92. data/lib/caruby/util/visitor.rb +0 -365
  93. data/lib/caruby/util/weak_hash.rb +0 -36
  94. data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
  95. data/test/lib/caruby/csv/csvio_test.rb +0 -69
  96. data/test/lib/caruby/database/persistable_test.rb +0 -92
  97. data/test/lib/caruby/domain/domain_test.rb +0 -112
  98. data/test/lib/caruby/domain/inversible_test.rb +0 -99
  99. data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
  100. data/test/lib/caruby/import/java_test.rb +0 -80
  101. data/test/lib/caruby/import/mixed_case_test.rb +0 -14
  102. data/test/lib/caruby/migration/test_case.rb +0 -102
  103. data/test/lib/caruby/test_case.rb +0 -230
  104. data/test/lib/caruby/util/cache_test.rb +0 -23
  105. data/test/lib/caruby/util/class_test.rb +0 -61
  106. data/test/lib/caruby/util/collection_test.rb +0 -398
  107. data/test/lib/caruby/util/command_test.rb +0 -55
  108. data/test/lib/caruby/util/domain_extent_test.rb +0 -60
  109. data/test/lib/caruby/util/file_separator_test.rb +0 -30
  110. data/test/lib/caruby/util/inflector_test.rb +0 -12
  111. data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
  112. data/test/lib/caruby/util/merge_test.rb +0 -83
  113. data/test/lib/caruby/util/module_test.rb +0 -25
  114. data/test/lib/caruby/util/options_test.rb +0 -59
  115. data/test/lib/caruby/util/partial_order_test.rb +0 -42
  116. data/test/lib/caruby/util/pretty_print_test.rb +0 -85
  117. data/test/lib/caruby/util/properties_test.rb +0 -50
  118. data/test/lib/caruby/util/stopwatch_test.rb +0 -18
  119. data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
  120. data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
  121. data/test/lib/caruby/util/tree_test.rb +0 -23
  122. data/test/lib/caruby/util/trie_test.rb +0 -14
  123. data/test/lib/caruby/util/visitor_test.rb +0 -278
  124. data/test/lib/caruby/util/weak_hash_test.rb +0 -45
  125. data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
  126. data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
@@ -1,188 +1,17 @@
1
- require 'caruby/migration/resource_module'
1
+ require 'jinx/migration/migratable'
2
2
 
3
3
  module CaRuby
4
- # A Migratable mix-in adds migration support for Resource domain objects.
5
- # For each migration Resource created by a CaRuby::Migrator, the migration process
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
- # Completes setting this Migratable domain object's attributes from the given input row.
74
- # This method is responsible for migrating attributes which are not mapped
75
- # in the configuration. It is called after the configuration attributes for
76
- # the given row are migrated and before {#migrate_references}.
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
- # This base implementation is a no-op.
79
- # Subclasses can modify this method to complete the migration. The overridden
80
- # methods should call +super+ to pick up the superclass migration.
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