og 0.40.0 → 0.41.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,299 @@
1
+ # * Matt Bowen <matt.bowen@farweststeel.com>
2
+ # * George Moschovitis <gm@navel.gr>
3
+ # (c) 2004-2005 Navel, all rights reserved.
4
+ # $Id: oracle.rb 266 2005-02-28 14:50:48Z gmosx $
5
+
6
+ require 'oci8'
7
+
8
+ require 'og/store/sql'
9
+ require 'og/adapter/oracle/override'
10
+ require 'og/adapter/oracle/utils'
11
+
12
+ module Og
13
+
14
+ # The Oracle adapter. This adapter communicates with
15
+ # an Oracle rdbms. For extra documentation see
16
+ # lib/og/adapter.rb
17
+ #
18
+ # Connects to Oracle by config[:user], config[:password], config[:database],
19
+ # config[:privilege]. If you need DBA privilege, please set privilege as
20
+ # :SYSDBA or :SYSOPER.
21
+
22
+ class OracleAdapter < SqlStore
23
+ extend OracleUtils; include OracleUtils
24
+
25
+ def initialize(config)
26
+ super
27
+
28
+ @typemap.update(
29
+ Integer => 'number',
30
+ Fixnum => 'number',
31
+ String => 'varchar2(1024)', # max might be 4000 (Oracle 8)
32
+ TrueClass => 'char(1)',
33
+ Numeric => 'number',
34
+ Object => 'varchar2(1024)',
35
+ Array => 'varchar2(1024)',
36
+ Hash => 'varchar2(1024)'
37
+ )
38
+
39
+ # TODO: how to pass address etc?
40
+ @conn = OCI8.new(
41
+ config[:user],
42
+ config[:password],
43
+ config[:database],
44
+ config[:privilege]
45
+ )
46
+
47
+ # gmosx: does this work?
48
+ @conn.autocommit = true
49
+ rescue OCIException => ex
50
+ #---
51
+ # mcb:
52
+ # Oracle will raise a ORA-01017 if username, password, or
53
+ # SID aren't valid. I verified this for all three.
54
+ # irb(main):002:0> conn = Oracle.new('keebler', 'dfdfd', 'kbsid')
55
+ # /usr/local/lib/ruby/site_ruby/1.8/oracle.rb:27:in `logon': ORA-01017:
56
+ # invalid username/password; logon denied (OCIError)
57
+ #+++
58
+ if database_does_not_exist_exception? ex
59
+ Logger.info "Database '#{options[:name]}' not found!"
60
+ create_db(options)
61
+ retry
62
+ end
63
+ raise
64
+ end
65
+
66
+ def close
67
+ @conn.logoff
68
+ super
69
+ end
70
+
71
+ #--
72
+ # mcb:
73
+ # Unlike MySQL or Postgres, Oracle database/schema creation is a big deal.
74
+ # I don't know how to do it from the command line. I use Oracle's Database
75
+ # Configuration Assistant utility (dbca). I takes 30min - 1hr to create
76
+ # a full blown schema. So, your FIXME comments are fine. I'm thinking you
77
+ # won't be able to do this via Og, but once created, Og will be able to
78
+ # create tables, indexes, and other objects.
79
+ #++
80
+
81
+ def create_db(database, user = nil, password = nil)
82
+ # FIXME: what is appropriate for oracle?
83
+ # `createdb #{database} -U #{user}`
84
+ super
85
+ raise NotImplementedError, "Oracle Database/Schema creation n/a"
86
+ end
87
+
88
+ def drop_db(database, user = nil, password = nil)
89
+ # FIXME: what is appropriate for oracle?
90
+ # `dropdb #{database} -U #{user}`
91
+ super
92
+ raise NotImplementedError, "Oracle Database/Schema dropping n/a"
93
+ end
94
+
95
+ # The type used for default primary keys.
96
+
97
+ def primary_key_type
98
+ 'integer PRIMARY KEY'
99
+ end
100
+
101
+ def enchant(klass, manager)
102
+ pk = klass.primary_key
103
+
104
+ seq = if klass.schema_inheritance_child?
105
+ "#{table(klass.schema_inheritance_root_class)}_#{pk}_seq"
106
+ else
107
+ "#{table(klass)}_#{pk}_seq"
108
+ end
109
+
110
+ pkann = klass.ann[pk]
111
+
112
+ pkann[:sequence] = seq unless pkann[:sequence] == false
113
+
114
+ super
115
+ end
116
+
117
+ def query_statement(sql)
118
+ @conn.exec(sql)
119
+ end
120
+
121
+ def exec_statement(sql)
122
+ @conn.exec(sql)
123
+ end
124
+
125
+ def sql_update(sql)
126
+ Logger.debug sql if $DBG
127
+ res = @conn.exec(sql)
128
+ return res.to_i
129
+ end
130
+
131
+ # Return the last inserted row id.
132
+
133
+ def last_insert_id(klass)
134
+ seq = klass.ann[klass.primary_key][:sequence]
135
+
136
+ res = query_statement("SELECT #{seq}.nextval FROM DUAL")
137
+ lid = Integer(res.first_value)
138
+ res.close
139
+
140
+ return lid
141
+ end
142
+
143
+ # The insert sql statements.
144
+
145
+ def insert_sql(sql, klass)
146
+ str = ''
147
+
148
+ if klass.ann[klass.primary_key][:sequence]
149
+ str << "@#{klass.primary_key} = store.last_insert_id(#{klass})\n"
150
+ end
151
+
152
+ str << "store.exec \"#{sql}\""
153
+
154
+ return str
155
+ end
156
+
157
+ # :section: Transaction methods.
158
+
159
+ # Start a new transaction.
160
+
161
+ def start
162
+ @conn.autocommit = false
163
+
164
+ @transaction_nesting += 1
165
+ end
166
+
167
+ # Commit a transaction.
168
+
169
+ def commit
170
+ @transaction_nesting -= 1
171
+ @conn.commit if @transaction_nesting < 1
172
+ ensure
173
+ @conn.autocommit = true
174
+ end
175
+
176
+ # Rollback a transaction.
177
+
178
+ def rollback
179
+ @transaction_nesting -= 1
180
+ @conn.rollbackif @transaction_nesting < 1
181
+ ensure
182
+ @conn.autocommit = true
183
+ end
184
+
185
+
186
+ def create_table(klass)
187
+ super
188
+
189
+ seq = klass.ann[klass.primary_key][:sequence]
190
+ # Create the sequence for this table.
191
+ begin
192
+ exec_statement("CREATE SEQUENCE #{seq} INCREMENT BY 1 START WITH 1 NOMAXVALUE NOMINVALUE NOCYCLE")
193
+ Logger.info "Created sequence '#{seq}'."
194
+ rescue OCIError => ex
195
+ if table_already_exists_exception?(ex)
196
+ Logger.debug "Sequence #{seq} already exists" if $DBG
197
+ else
198
+ raise
199
+ end
200
+ end
201
+
202
+ end
203
+
204
+ def drop_table(klass)
205
+ super
206
+
207
+ seq = klass.ann[klass.primary_key][:sequence]
208
+ # Create the sequence for this table.
209
+ begin
210
+ exec_statement("DROP SEQUENCE #{seq}")
211
+ Logger.info "Dropped sequence '#{seq}'."
212
+ rescue OCIError => ex
213
+ if sequence_does_not_exist_exception?(ex)
214
+ Logger.debug "Sequence #{seq} didn't exist" if $DBG
215
+ else
216
+ raise
217
+ end
218
+ end
219
+
220
+ end
221
+
222
+ def read_attr(s, a, col)
223
+ store = self.class
224
+ {
225
+ String => nil,
226
+ Integer => :parse_int,
227
+ Float => :parse_float,
228
+ Time => :parse_timestamp,
229
+ Date => :parse_date,
230
+ TrueClass => :parse_boolean,
231
+ Og::Blob => :parse_blob
232
+ }.each do |klass, meth|
233
+ if a.class.ancestor? klass
234
+ return meth ?
235
+ "#{store}.#{meth}(res[#{col} + offset])" : "res[#{col} + offset]"
236
+ end
237
+ end
238
+
239
+ # else try to load it via YAML
240
+ "YAML::load(res[#{col} + offset])"
241
+ end
242
+
243
+ # Get the fields from the database table. Also handles the
244
+ # change of ordering of the fields in the table.
245
+ #
246
+ # To ignore a database field use the ignore_fields annotation
247
+ # ie,
248
+ #
249
+ # class Article
250
+ # ann self, :ignore_fields => [ :tsearch_idx, :ext_field ]
251
+ # end
252
+ #
253
+ # other aliases for ignore_fiels: ignore_field, ignore_column.
254
+ #--
255
+ # Even though great care has been taken to make this
256
+ # method reusable, oveeride if needed in your adapter.
257
+ #++
258
+
259
+ def create_field_map(klass)
260
+ # gmosx: This is incredible!!! argh!
261
+ # res = db.query "SELECT * FROM #{klass::DBTABLE} # LIMIT 1"
262
+ res = query "SELECT * FROM (SELECT * FROM #{table(klass)}) WHERE ROWNUM <= 1"
263
+ map = {}
264
+
265
+ # Check if the field should be ignored.
266
+
267
+ ignore = klass.ann.self[:ignore_field] || klass.ann.self[:ignore_fields] || klass.ann.self[:ignore_columns]
268
+
269
+ res.get_col_names.each_with_index do |f, i|
270
+ field_name = f.to_sym
271
+ unless (ignore and ignore.include?(field_name))
272
+ map[field_name] = i
273
+ end
274
+ end
275
+
276
+ return map
277
+ end
278
+
279
+ private
280
+
281
+ def database_does_not_exist_exception?(ex)
282
+ raise ex unless ex.kind_of?(OCIException)
283
+ ex.message =~ /ORA-01017/i
284
+ end
285
+
286
+ def table_already_exists_exception?(ex)
287
+ raise ex unless ex.kind_of?(OCIException)
288
+ ex.message =~ /ORA-00955/i
289
+ end
290
+
291
+ def sequence_does_not_exist_exception?(ex)
292
+ raise ex unless ex.kind_of?(OCIException)
293
+ ex.message =~ /ORA-02289/i
294
+ end
295
+
296
+ end
297
+
298
+ end
299
+
@@ -0,0 +1,23 @@
1
+
2
+ class OCI8::Cursor # :nodoc: all
3
+ def blank?
4
+ 0 == row_count
5
+ end
6
+
7
+ def next
8
+ fetch
9
+ end
10
+
11
+ def each_row
12
+ idx = 0
13
+ while row = fetch
14
+ yield(row, idx)
15
+ idx += 1
16
+ end
17
+ end
18
+
19
+ def first_value
20
+ fetch[0]
21
+ end
22
+
23
+ end
@@ -0,0 +1,9 @@
1
+ require 'og/store/sql/utils'
2
+
3
+ module Og
4
+
5
+ module OracleUtils
6
+ include SqlUtils
7
+ end
8
+
9
+ end
@@ -185,22 +185,6 @@ class PostgresqlAdapter < SqlStore
185
185
  "YAML::load(res[#{col} + offset])"
186
186
  end
187
187
 
188
- def eval_og_allocate(klass)
189
- if klass.schema_inheritance?
190
- klass.module_eval %{
191
- def self.og_allocate(res, row = 0)
192
- Object.constant(res[0]).allocate
193
- end
194
- }
195
- else
196
- klass.module_eval %{
197
- def self.og_allocate(res, row = 0)
198
- self.allocate
199
- end
200
- }
201
- end
202
- end
203
-
204
188
  def read_row(obj, res, res_row, row)
205
189
  res.fields.each_with_index do |field, idx|
206
190
  obj.instance_variable_set "@#{field}", res.getvalue(row, idx)
@@ -1,8 +1,8 @@
1
- # Helper for mysql scripts.
1
+ # Helper for psql scripts.
2
2
  #
3
3
  # === Example
4
4
  #
5
- # mysql '-u root -p', %{
5
+ # psql '-u root -p', %{
6
6
  # drop database if exists weblog_development;
7
7
  # create database weblog_development;
8
8
  # grant all on weblog_development.* to #{`id -un`.strip}@localhost;
@@ -184,14 +184,22 @@ module EntityMixin
184
184
  return obj
185
185
  end
186
186
 
187
- # An alternative creation helper, only works
188
- # with objects that have an initialize method
189
- # tha works with no arguments.
187
+ # An alternative creation helper, does _not_ call the
188
+ # initialize method when there are mandatory elements.
190
189
 
191
190
  def create_with(hash)
192
- obj = self.new
191
+ obj = nil
192
+ arity = self.method(:initialize).arity
193
+
194
+ if arity > 0 || arity < -1
195
+ obj = self.allocate
196
+ else
197
+ obj = self.new
198
+ end
199
+
193
200
  obj.assign_with(hash)
194
201
  ogmanager.store.save(obj)
202
+
195
203
  return obj
196
204
  end
197
205
 
@@ -323,7 +331,7 @@ module EntityMixin
323
331
  ogmanager.store.find(options)
324
332
  end
325
333
  alias_method :qbe, :query_by_example
326
- alias_method :find_with_properties, :query_by_example
334
+ alias_method :find_with_attributes, :query_by_example
327
335
 
328
336
  # :section: Aggregations / Calculations
329
337
 
@@ -641,21 +649,36 @@ private
641
649
 
642
650
  def finder(match, args)
643
651
  finder = (match.captures.first == 'all_by' ? :find : :find_one)
652
+
644
653
  attrs = match.captures.last.split('_and_')
654
+
645
655
  options = (annotation[:find_options] || {}).dup
646
656
  options = args.pop if args.last.is_a?(Hash)
647
- relations_map = relations.map{|r| [r.name.to_s,r]}
657
+
658
+ relations_map = {}
659
+ relations.each {|r| relations_map[r.name.to_s] = r }
660
+
648
661
  condition = attrs.zip(args).map do |name, value|
649
- if relation = relations_map.assoc(name)
650
- relation = relation.last
662
+ if relation = relations_map[name]
651
663
  field_name = relation.foreign_key
652
- value = value.send(relation.target_class.primary_key.symbol)
664
+ value = value.send(relation.target_class.primary_key)
665
+ value = ogmanager.store.quote(value)
666
+ elsif name =~ /^(#{relations_map.keys.join('|')})_(.*)$/
667
+ r = relations_map[$1]
668
+ tc = r.target_class
669
+ if tc.serializable_attributes.include?($2.to_sym)
670
+ field_name = r.foreign_key
671
+ value = "(SELECT #{tc.primary_key} FROM #{tc::OGTABLE} WHERE #{$2} = '#{value}')"
672
+ end
653
673
  else
654
674
  anno = ann(name.to_sym)
655
675
  field_name = anno[:field] || anno[:name] || name.to_sym
656
- end
657
- options["#{name}_op".to_sym] = 'IN' if value.is_a?(Array)
658
- %|#{field_name} #{options.delete("#{name}_op".to_sym) || '='} #{ogmanager.store.quote(value)}|
676
+ value = ogmanager.store.quote(value)
677
+ end
678
+
679
+ options["#{name}_op".to_sym] ||= 'IN' if value.is_a?(Array)
680
+
681
+ %|#{field_name} #{options.delete("#{name}_op".to_sym) || '='} #{value}|
659
682
  end.join(' AND ')
660
683
 
661
684
  options.merge!(
@@ -703,7 +726,7 @@ class Entity
703
726
 
704
727
  def entity_from_string(str)
705
728
  res = nil
706
- Og.manager.managed_classes.each do |klass|
729
+ Og::Manager.managed_classes.each do |klass|
707
730
  if klass.name == str
708
731
  res = klass
709
732
  break