og 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/CHANGELOG +485 -0
  2. data/README +35 -12
  3. data/Rakefile +4 -7
  4. data/benchmark/bench.rb +1 -1
  5. data/doc/AUTHORS +3 -3
  6. data/doc/RELEASES +153 -2
  7. data/doc/config.txt +0 -7
  8. data/doc/tutorial.txt +7 -0
  9. data/examples/README +5 -0
  10. data/examples/mysql_to_psql.rb +25 -50
  11. data/examples/run.rb +62 -77
  12. data/install.rb +1 -1
  13. data/lib/og.rb +45 -106
  14. data/lib/og/collection.rb +156 -0
  15. data/lib/og/entity.rb +131 -0
  16. data/lib/og/errors.rb +10 -15
  17. data/lib/og/manager.rb +115 -0
  18. data/lib/og/{mixins → mixin}/hierarchical.rb +43 -37
  19. data/lib/og/{mixins → mixin}/orderable.rb +35 -35
  20. data/lib/og/{mixins → mixin}/timestamped.rb +0 -6
  21. data/lib/og/{mixins → mixin}/tree.rb +0 -4
  22. data/lib/og/relation.rb +178 -0
  23. data/lib/og/relation/belongs_to.rb +14 -0
  24. data/lib/og/relation/has_many.rb +62 -0
  25. data/lib/og/relation/has_one.rb +17 -0
  26. data/lib/og/relation/joins_many.rb +69 -0
  27. data/lib/og/relation/many_to_many.rb +17 -0
  28. data/lib/og/relation/refers_to.rb +31 -0
  29. data/lib/og/store.rb +223 -0
  30. data/lib/og/store/filesys.rb +113 -0
  31. data/lib/og/store/madeleine.rb +4 -0
  32. data/lib/og/store/memory.rb +291 -0
  33. data/lib/og/store/mysql.rb +283 -0
  34. data/lib/og/store/psql.rb +238 -0
  35. data/lib/og/store/sql.rb +599 -0
  36. data/lib/og/store/sqlite.rb +190 -0
  37. data/lib/og/store/sqlserver.rb +262 -0
  38. data/lib/og/types.rb +19 -0
  39. data/lib/og/validation.rb +0 -4
  40. data/test/og/{mixins → mixin}/tc_hierarchical.rb +21 -23
  41. data/test/og/{mixins → mixin}/tc_orderable.rb +15 -14
  42. data/test/og/mixin/tc_timestamped.rb +38 -0
  43. data/test/og/store/tc_filesys.rb +71 -0
  44. data/test/og/tc_relation.rb +36 -0
  45. data/test/og/tc_store.rb +290 -0
  46. data/test/og/tc_types.rb +21 -0
  47. metadata +54 -40
  48. data/examples/mock_example.rb +0 -50
  49. data/lib/og/adapters/base.rb +0 -706
  50. data/lib/og/adapters/filesys.rb +0 -117
  51. data/lib/og/adapters/mysql.rb +0 -350
  52. data/lib/og/adapters/oracle.rb +0 -368
  53. data/lib/og/adapters/psql.rb +0 -272
  54. data/lib/og/adapters/sqlite.rb +0 -265
  55. data/lib/og/adapters/sqlserver.rb +0 -356
  56. data/lib/og/database.rb +0 -290
  57. data/lib/og/enchant.rb +0 -149
  58. data/lib/og/meta.rb +0 -407
  59. data/lib/og/testing/mock.rb +0 -165
  60. data/lib/og/typemacros.rb +0 -24
  61. data/test/og/adapters/tc_filesys.rb +0 -83
  62. data/test/og/adapters/tc_sqlite.rb +0 -86
  63. data/test/og/adapters/tc_sqlserver.rb +0 -96
  64. data/test/og/tc_automanage.rb +0 -46
  65. data/test/og/tc_lifecycle.rb +0 -105
  66. data/test/og/tc_many_to_many.rb +0 -61
  67. data/test/og/tc_meta.rb +0 -55
  68. data/test/og/tc_validation.rb +0 -89
  69. data/test/tc_og.rb +0 -364
@@ -0,0 +1,190 @@
1
+ begin
2
+ require 'sqlite3'
3
+ rescue Object => ex
4
+ Logger.error 'Ruby-Sqlite3 bindings are not installed!'
5
+ Logger.error ex
6
+ end
7
+
8
+ require 'fileutils'
9
+
10
+ require 'og/store/sql'
11
+
12
+ # Customize the standard postgres resultset to make
13
+ # more compatible with Og.
14
+
15
+ class SQLite3::ResultSet
16
+ def blank?
17
+ false
18
+ end
19
+
20
+ def each_row
21
+ each do |row|
22
+ yield(row, 0)
23
+ end
24
+ end
25
+
26
+ def first_value
27
+ val = self.next[0]
28
+ close
29
+ return val
30
+ end
31
+ end
32
+
33
+ module Og
34
+
35
+ # A Store that persists objects into an Sqlite3 database.
36
+ # To read documentation about the methods, consult the documentation
37
+ # for SqlStore and Store.
38
+
39
+ class SqliteStore < SqlStore
40
+
41
+ def self.destroy(options)
42
+ begin
43
+ FileUtils.rm("#{options[:name]}.db")
44
+ super
45
+ rescue Object
46
+ Logger.info "Cannot drop '#{options[:name]}'!"
47
+ end
48
+ end
49
+
50
+ def initialize(options)
51
+ super
52
+ @conn = SQLite3::Database.new("#{options[:name]}.db")
53
+ end
54
+
55
+ def close
56
+ # FIXME: problems when closing due to unfinalised statements.
57
+ # @conn.close
58
+ super
59
+ end
60
+
61
+ def enchant(klass, manager)
62
+ klass.property :oid, Fixnum, :sql => 'integer PRIMARY KEY'
63
+ super
64
+ end
65
+
66
+ def query(sql)
67
+ Logger.debug sql if $DBG
68
+ return @conn.query(sql)
69
+ rescue => ex
70
+ handle_sql_exception(ex, sql)
71
+ end
72
+
73
+ def exec(sql)
74
+ Logger.debug sql if $DBG
75
+ @conn.query(sql).close
76
+ rescue => ex
77
+ handle_sql_exception(ex, sql)
78
+ end
79
+
80
+ def start
81
+ @conn.transaction if @transaction_nesting < 1
82
+ @transaction_nesting += 1
83
+ end
84
+
85
+ def commit
86
+ @transaction_nesting -= 1
87
+ @conn.commit if @transaction_nesting < 1
88
+ end
89
+
90
+ def rollback
91
+ @transaction_nesting -= 1
92
+ @conn.rollback if @transaction_nesting < 1
93
+ end
94
+
95
+ private
96
+
97
+ def create_table(klass)
98
+ columns = columns_for_class(klass)
99
+
100
+ sql = "CREATE TABLE #{klass::OGTABLE} (#{columns.join(', ')}"
101
+
102
+ # Create table constrains.
103
+
104
+ if klass.__meta and constrains = klass.__meta[:sql_constrain]
105
+ sql << ", #{constrains.join(', ')}"
106
+ end
107
+
108
+ sql << ");"
109
+
110
+ # Create indices.
111
+
112
+ if klass.__meta and indices = klass.__meta[:index]
113
+ for data in indices
114
+ idx, options = *data
115
+ idx = idx.to_s
116
+ pre_sql, post_sql = options[:pre], options[:post]
117
+ idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
118
+ sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"
119
+ end
120
+ end
121
+
122
+ begin
123
+ @conn.query(sql).close
124
+ Logger.info "Created table '#{klass::OGTABLE}'."
125
+ rescue Object => ex
126
+ # gmosx: any idea how to better test this?
127
+ if ex.to_s =~ /table .* already exists/i
128
+ Logger.debug 'Table already exists' if $DBG
129
+ return
130
+ else
131
+ raise
132
+ end
133
+ end
134
+
135
+ # Create join tables if needed. Join tables are used in
136
+ # 'many_to_many' relations.
137
+
138
+ if klass.__meta and join_tables = klass.__meta[:join_tables]
139
+ for join_table in join_tables
140
+ begin
141
+ @conn.query("CREATE TABLE #{join_table} (key1 integer NOT NULL, key2 integer NOT NULL)").close
142
+ @conn.query("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").close
143
+ @conn.query("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").close
144
+ rescue Object => ex
145
+ # gmosx: any idea how to better test this?
146
+ if ex.to_s =~ /table .* already exists/i
147
+ Logger.debug 'Join table already exists' if $DBG
148
+ else
149
+ raise
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ def create_column_map(klass)
157
+ res = @conn.query "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
158
+ map = {}
159
+
160
+ columns = res.columns
161
+
162
+ columns.size.times do |i|
163
+ map[columns[i].intern] = i
164
+ end
165
+
166
+ return map
167
+ ensure
168
+ res.close if res
169
+ end
170
+
171
+ def eval_og_insert(klass)
172
+ pk = klass.primary_key.first
173
+ props = klass.properties
174
+ values = props.collect { |p| write_prop(p) }.join(',')
175
+
176
+ sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
177
+
178
+ klass.class_eval %{
179
+ def og_insert(store)
180
+ #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
181
+ store.conn.query("#{sql}").close
182
+ @#{pk} = store.conn.last_insert_row_id
183
+ #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
184
+ end
185
+ }
186
+ end
187
+
188
+ end
189
+
190
+ end
@@ -0,0 +1,262 @@
1
+ begin
2
+ require 'dbi'
3
+ rescue Object => ex
4
+ Logger.error 'Ruby-DBI bindings not present or ADO support not available.'
5
+ Logger.error ex
6
+ end
7
+
8
+ require 'og/store/sql'
9
+
10
+ # Customize the standard SqlServer resultset to make
11
+ # more compatible with Og.
12
+ =begin
13
+ class Sqlserver::Result
14
+ def blank?
15
+ 0 == num_rows
16
+ end
17
+
18
+ alias_method :next, :fetch_row
19
+
20
+ def each_row
21
+ each do |row|
22
+ yield(row, 0)
23
+ end
24
+ end
25
+
26
+ def first_value
27
+ val = fetch_row[0]
28
+ free
29
+ return val
30
+ end
31
+
32
+ alias_method :close, :free
33
+ end
34
+ =end
35
+
36
+ module Og
37
+
38
+ module SqlserverUtils
39
+ include SqlUtils
40
+
41
+ def escape(str)
42
+ return nil unless str
43
+ return Sqlserver.quote(str)
44
+ end
45
+
46
+ def quote(val)
47
+ case val
48
+ when Fixnum, Integer, Float
49
+ val ? val.to_s : 'NULL'
50
+ when String
51
+ val ? "'#{escape(val)}'" : 'NULL'
52
+ when Time
53
+ val ? "'#{timestamp(val)}'" : 'NULL'
54
+ when Date
55
+ val ? "'#{date(val)}'" : 'NULL'
56
+ when TrueClass
57
+ val ? "'1'" : 'NULL'
58
+ else
59
+ # gmosx: keep the '' for nil symbols.
60
+ val ? escape(val.to_yaml) : ''
61
+ end
62
+ end
63
+ end
64
+
65
+ # A Store that persists objects into a Sqlserver database.
66
+ # To read documentation about the methods, consult the documentation
67
+ # for SqlStore and Store.
68
+
69
+ class SqlserverStore < SqlStore
70
+ extend SqlserverUtils
71
+ include SqlserverUtils
72
+
73
+ def self.create(options)
74
+ raise 'Not implemented'
75
+ end
76
+
77
+ def self.destroy(options)
78
+ raise 'Not implemented'
79
+ end
80
+
81
+ def initialize(options)
82
+ super
83
+
84
+ begin
85
+ @conn = DBI.connect("DBI:ADO:Provider=SQLOLEDB;Data Source=#{options[:address]};Initial Catalog=#{options[:name]};User Id=#{options[:user]};Password=#{options[:password]};")
86
+ rescue => ex
87
+ # gmosx, FIXME: drak, fix this!
88
+ if ex.to_s =~ /database .* does not exist/i
89
+ Logger.info "Database '#{options[:name]}' not found!"
90
+ self.class.create(options)
91
+ retry
92
+ end
93
+ raise
94
+ end
95
+ end
96
+
97
+ def close
98
+ @conn.disconnect
99
+ super
100
+ end
101
+
102
+ def enchant(klass, manager)
103
+ klass.property :oid, Fixnum, :sql => 'integer AUTO_INCREMENT PRIMARY KEY'
104
+ super
105
+ end
106
+
107
+ def query(sql)
108
+ # Logger.debug sql if $DBG
109
+ return @conn.select_all(sql)
110
+ rescue => ex
111
+ handle_sql_exception(ex, sql)
112
+ end
113
+
114
+ def exec(sql)
115
+ # Logger.debug sql if $DBG
116
+ @conn.execute(sql).finish
117
+ rescue => ex
118
+ handle_sql_exception(ex, sql)
119
+ end
120
+
121
+ private
122
+
123
+ def create_table(klass)
124
+ columns = columns_for_class(klass)
125
+
126
+ sql = "CREATE TABLE #{klass::OGTABLE} (#{columns.join(', ')}"
127
+
128
+ # Create table constrains.
129
+
130
+ if klass.__meta and constrains = klass.__meta[:sql_constrain]
131
+ sql << ", #{constrains.join(', ')}"
132
+ end
133
+
134
+ sql << ");"
135
+
136
+ # Create indices.
137
+
138
+ if klass.__meta and indices = klass.__meta[:index]
139
+ for data in indices
140
+ idx, options = *data
141
+ idx = idx.to_s
142
+ pre_sql, post_sql = options[:pre], options[:post]
143
+ idxname = idx.gsub(/ /, "").gsub(/,/, "_").gsub(/\(.*\)/, "")
144
+ sql << " CREATE #{pre_sql} INDEX #{klass::OGTABLE}_#{idxname}_idx #{post_sql} ON #{klass::OGTABLE} (#{idx});"
145
+ end
146
+ end
147
+
148
+ conn.query_with_result = false
149
+
150
+ begin
151
+ conn.query(sql)
152
+ Logger.info "Created table '#{klass::OGTABLE}'."
153
+ rescue => ex
154
+ # gmosx: any idea how to better test this?
155
+ if ex.errno == 1050 # table already exists.
156
+ Logger.debug 'Table already exists' if $DBG
157
+ return
158
+ else
159
+ raise
160
+ end
161
+ end
162
+ =begin
163
+ # Create join tables if needed. Join tables are used in
164
+ # 'many_to_many' relations.
165
+
166
+ if klass.__meta and joins = klass.__meta[:sql_join]
167
+ for data in joins
168
+ # the class to join to and some options.
169
+ join_name, join_class, options = *data
170
+
171
+ join_table = "#{self.class.join_table(klass, join_class, join_name)}"
172
+ join_src = "#{self.class.encode(klass)}_oid"
173
+ join_dst = "#{self.class.encode(join_class)}_oid"
174
+
175
+ begin
176
+ conn.exec("CREATE TABLE #{join_table} ( key1 integer NOT NULL, key2 integer NOT NULL )").clear
177
+ conn.exec("CREATE INDEX #{join_table}_key1_idx ON #{join_table} (key1)").clear
178
+ conn.exec("CREATE INDEX #{join_table}_key2_idx ON #{join_table} (key2)").clear
179
+ rescue => ex
180
+ # gmosx: any idea how to better test this?
181
+ if ex.errno == 1050 # table already exists.
182
+ Logger.debug 'Join table already exists'
183
+ else
184
+ raise
185
+ end
186
+ end
187
+ end
188
+ end
189
+ =end
190
+ end
191
+
192
+ def create_column_map(klass)
193
+ conn.query_with_result = true
194
+ res = @conn.query "SELECT * FROM #{klass::OGTABLE} LIMIT 1"
195
+ map = {}
196
+
197
+ res.num_fields.times do |i|
198
+ map[res.fetch_field.name.intern] = i
199
+ end
200
+
201
+ return map
202
+ ensure
203
+ res.close if res
204
+ end
205
+
206
+ def write_prop(p)
207
+ if p.klass.ancestors.include?(Integer)
208
+ return "#\{@#{p.symbol} || 'NULL'\}"
209
+ elsif p.klass.ancestors.include?(Float)
210
+ return "#\{@#{p.symbol} || 'NULL'\}"
211
+ elsif p.klass.ancestors.include?(String)
212
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol})\}'" : 'NULL'\}|
213
+ elsif p.klass.ancestors.include?(Time)
214
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
215
+ elsif p.klass.ancestors.include?(Date)
216
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
217
+ elsif p.klass.ancestors.include?(TrueClass)
218
+ return "#\{@#{p.symbol} ? \"'1'\" : 'NULL' \}"
219
+ else
220
+ # gmosx: keep the '' for nil symbols.
221
+ return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
222
+ end
223
+ end
224
+
225
+ def read_prop(p, col)
226
+ if p.klass.ancestors.include?(Integer)
227
+ return "res[#{col} + offset].to_i"
228
+ elsif p.klass.ancestors.include?(Float)
229
+ return "res[#{col} + offset].to_f"
230
+ elsif p.klass.ancestors.include?(String)
231
+ return "res[#{col} + offset]"
232
+ elsif p.klass.ancestors.include?(Time)
233
+ return "#{self.class}.parse_timestamp(res[#{col} + offset])"
234
+ elsif p.klass.ancestors.include?(Date)
235
+ return "#{self.class}.parse_date(res[#{col} + offset])"
236
+ elsif p.klass.ancestors.include?(TrueClass)
237
+ return "('0' != res[#{col} + offset])"
238
+ else
239
+ return "YAML.load(res[#{col} + offset])"
240
+ end
241
+ end
242
+
243
+ def eval_og_insert(klass)
244
+ props = klass.properties
245
+ values = props.collect { |p| write_prop(p) }.join(',')
246
+
247
+ sql = "INSERT INTO #{klass::OGTABLE} (#{props.collect {|p| p.symbol.to_s}.join(',')}) VALUES (#{values})"
248
+
249
+ klass.class_eval %{
250
+ def og_insert(store)
251
+ #{Aspects.gen_advice_code(:og_insert, klass.advices, :pre) if klass.respond_to?(:advices)}
252
+ store.conn.query_with_result = false
253
+ store.conn.query "#{sql}"
254
+ @#{klass.pk_symbol} = store.conn.insert_id
255
+ #{Aspects.gen_advice_code(:og_insert, klass.advices, :post) if klass.respond_to?(:advices)}
256
+ end
257
+ }
258
+ end
259
+
260
+ end
261
+
262
+ end