og 0.16.0 → 0.17.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.
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