og 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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