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.
- data/doc/RELEASES +26 -0
- data/lib/glue/taggable.rb +17 -8
- data/lib/og.rb +1 -1
- data/lib/og/adapter/mysql.rb +6 -6
- data/lib/og/adapter/oracle.old.rb +509 -0
- data/lib/og/adapter/oracle.rb +299 -0
- data/lib/og/adapter/oracle/override.rb +23 -0
- data/lib/og/adapter/oracle/utils.rb +9 -0
- data/lib/og/adapter/postgresql.rb +0 -16
- data/lib/og/adapter/postgresql/script.rb +2 -2
- data/lib/og/entity.rb +36 -13
- data/lib/og/markers.rb +10 -2
- data/lib/og/store/sql.rb +11 -14
- data/lib/og/store/sql/evolution.rb +1 -1
- data/test/glue/tc_taggable.rb +47 -12
- data/test/og/CONFIG.rb +1 -1
- data/test/og/store/tc_sti2.rb +23 -2
- data/test/og/tc_inheritance2.rb +5 -3
- data/test/og/tc_polymorphic.rb +40 -13
- data/test/og/tc_reldelete.rb +11 -2
- metadata +9 -4
@@ -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
|
@@ -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
|
1
|
+
# Helper for psql scripts.
|
2
2
|
#
|
3
3
|
# === Example
|
4
4
|
#
|
5
|
-
#
|
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;
|
data/lib/og/entity.rb
CHANGED
@@ -184,14 +184,22 @@ module EntityMixin
|
|
184
184
|
return obj
|
185
185
|
end
|
186
186
|
|
187
|
-
# An alternative creation helper,
|
188
|
-
#
|
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 =
|
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 :
|
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
|
-
|
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
|
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
|
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
|
-
|
657
|
-
|
658
|
-
|
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.
|
729
|
+
Og::Manager.managed_classes.each do |klass|
|
707
730
|
if klass.name == str
|
708
731
|
res = klass
|
709
732
|
break
|