norma 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/COPYING ADDED
@@ -0,0 +1,168 @@
1
+ = GNU LESSER GENERAL PUBLIC LICENSE
2
+
3
+ Version 3, 29 June 2007
4
+
5
+
6
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
7
+ Everyone is permitted to copy and distribute verbatim copies
8
+ of this license document, but changing it is not allowed.
9
+
10
+
11
+ This version of the GNU Lesser General Public License incorporates
12
+ the terms and conditions of version 3 of the GNU General Public
13
+ License, supplemented by the additional permissions listed below.
14
+
15
+ == 0. Additional Definitions.
16
+
17
+ As used herein, "this License" refers to version 3 of the GNU Lesser
18
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
19
+ General Public License.
20
+
21
+ "The Library" refers to a covered work governed by this License,
22
+ other than an Application or a Combined Work as defined below.
23
+
24
+ An "Application" is any work that makes use of an interface provided
25
+ by the Library, but which is not otherwise based on the Library.
26
+ Defining a subclass of a class defined by the Library is deemed a mode
27
+ of using an interface provided by the Library.
28
+
29
+ A "Combined Work" is a work produced by combining or linking an
30
+ Application with the Library. The particular version of the Library
31
+ with which the Combined Work was made is also called the "Linked
32
+ Version".
33
+
34
+ The "Minimal Corresponding Source" for a Combined Work means the
35
+ Corresponding Source for the Combined Work, excluding any source code
36
+ for portions of the Combined Work that, considered in isolation, are
37
+ based on the Application, and not on the Linked Version.
38
+
39
+ The "Corresponding Application Code" for a Combined Work means the
40
+ object code and/or source code for the Application, including any data
41
+ and utility programs needed for reproducing the Combined Work from the
42
+ Application, but excluding the System Libraries of the Combined Work.
43
+
44
+ == 1. Exception to Section 3 of the GNU GPL.
45
+
46
+ You may convey a covered work under sections 3 and 4 of this License
47
+ without being bound by section 3 of the GNU GPL.
48
+
49
+ == 2. Conveying Modified Versions.
50
+
51
+ If you modify a copy of the Library, and, in your modifications, a
52
+ facility refers to a function or data to be supplied by an Application
53
+ that uses the facility (other than as an argument passed when the
54
+ facility is invoked), then you may convey a copy of the modified
55
+ version:
56
+
57
+ a) under this License, provided that you make a good faith effort to
58
+ ensure that, in the event an Application does not supply the
59
+ function or data, the facility still operates, and performs
60
+ whatever part of its purpose remains meaningful, or
61
+
62
+ b) under the GNU GPL, with none of the additional permissions of
63
+ this License applicable to that copy.
64
+
65
+ == 3. Object Code Incorporating Material from Library Header Files.
66
+
67
+ The object code form of an Application may incorporate material from
68
+ a header file that is part of the Library. You may convey such object
69
+ code under terms of your choice, provided that, if the incorporated
70
+ material is not limited to numerical parameters, data structure
71
+ layouts and accessors, or small macros, inline functions and templates
72
+ (ten or fewer lines in length), you do both of the following:
73
+
74
+ a) Give prominent notice with each copy of the object code that the
75
+ Library is used in it and that the Library and its use are
76
+ covered by this License.
77
+
78
+ b) Accompany the object code with a copy of the GNU GPL and this license
79
+ document.
80
+
81
+ == 4. Combined Works.
82
+
83
+ You may convey a Combined Work under terms of your choice that,
84
+ taken together, effectively do not restrict modification of the
85
+ portions of the Library contained in the Combined Work and reverse
86
+ engineering for debugging such modifications, if you also do each of
87
+ the following:
88
+
89
+ a) Give prominent notice with each copy of the Combined Work that
90
+ the Library is used in it and that the Library and its use are
91
+ covered by this License.
92
+
93
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
94
+ document.
95
+
96
+ c) For a Combined Work that displays copyright notices during
97
+ execution, include the copyright notice for the Library among
98
+ these notices, as well as a reference directing the user to the
99
+ copies of the GNU GPL and this license document.
100
+
101
+ d) Do one of the following:
102
+
103
+ d-0) Convey the Minimal Corresponding Source under the terms of this
104
+ License, and the Corresponding Application Code in a form
105
+ suitable for, and under terms that permit, the user to
106
+ recombine or relink the Application with a modified version of
107
+ the Linked Version to produce a modified Combined Work, in the
108
+ manner specified by section 6 of the GNU GPL for conveying
109
+ Corresponding Source.
110
+
111
+ d-1) Use a suitable shared library mechanism for linking with the
112
+ Library. A suitable mechanism is one that (a) uses at run time
113
+ a copy of the Library already present on the user's computer
114
+ system, and (b) will operate properly with a modified version
115
+ of the Library that is interface-compatible with the Linked
116
+ Version.
117
+
118
+ e) Provide Installation Information, but only if you would otherwise
119
+ be required to provide such information under section 6 of the
120
+ GNU GPL, and only to the extent that such information is
121
+ necessary to install and execute a modified version of the
122
+ Combined Work produced by recombining or relinking the
123
+ Application with a modified version of the Linked Version. (If
124
+ you use option 4d0, the Installation Information must accompany
125
+ the Minimal Corresponding Source and Corresponding Application
126
+ Code. If you use option 4d1, you must provide the Installation
127
+ Information in the manner specified by section 6 of the GNU GPL
128
+ for conveying Corresponding Source.)
129
+
130
+ == 5. Combined Libraries.
131
+
132
+ You may place library facilities that are a work based on the
133
+ Library side by side in a single library together with other library
134
+ facilities that are not Applications and are not covered by this
135
+ License, and convey such a combined library under terms of your
136
+ choice, if you do both of the following:
137
+
138
+ a) Accompany the combined library with a copy of the same work based
139
+ on the Library, uncombined with any other library facilities,
140
+ conveyed under the terms of this License.
141
+
142
+ b) Give prominent notice with the combined library that part of it
143
+ is a work based on the Library, and explaining where to find the
144
+ accompanying uncombined form of the same work.
145
+
146
+ == 6. Revised Versions of the GNU Lesser General Public License.
147
+
148
+ The Free Software Foundation may publish revised and/or new versions
149
+ of the GNU Lesser General Public License from time to time. Such new
150
+ versions will be similar in spirit to the present version, but may
151
+ differ in detail to address new problems or concerns.
152
+
153
+ Each version is given a distinguishing version number. If the
154
+ Library as you received it specifies that a certain numbered version
155
+ of the GNU Lesser General Public License "or any later version"
156
+ applies to it, you have the option of following the terms and
157
+ conditions either of that published version or of any later version
158
+ published by the Free Software Foundation. If the Library as you
159
+ received it does not specify a version number of the GNU Lesser
160
+ General Public License, you may choose any version of the GNU Lesser
161
+ General Public License ever published by the Free Software Foundation.
162
+
163
+ If the Library as you received it specifies that a proxy can decide
164
+ whether future versions of the GNU Lesser General Public License shall
165
+ apply, that proxy's public statement of acceptance of any version is
166
+ permanent authorization for you to choose that version for the
167
+ Library.
168
+
@@ -0,0 +1,26 @@
1
+ #!mast demo lib meta [A-Z]*
2
+ demo
3
+ demo/bootstrap.rb
4
+ demo/model.rb
5
+ demo/setup.rb
6
+ demo/store.rb
7
+ lib
8
+ lib/norma
9
+ lib/norma/bootstrap.rb
10
+ lib/norma/database.rb
11
+ lib/norma/database_adapter
12
+ lib/norma/database_adapter/mysql
13
+ lib/norma/database_adapter/mysql/adapter.rb
14
+ lib/norma/database_adapter/mysql/down.sql
15
+ lib/norma/database_adapter/mysql/up.sql
16
+ lib/norma/database_adapter/postgres
17
+ lib/norma/database_adapter/postgres/adapter.rb
18
+ lib/norma/database_adapter/postgres/down.sql
19
+ lib/norma/database_adapter/postgres/up.sql
20
+ lib/norma/orm.rb
21
+ lib/norma.rb
22
+ meta
23
+ meta/package
24
+ meta/project
25
+ meta/version
26
+ README
data/README ADDED
File without changes
@@ -0,0 +1,36 @@
1
+ require 'norma'
2
+ require './model.rb'
3
+ require './store.rb'
4
+
5
+ $norma_store.bootstrap
6
+
7
+ p MyClass.records
8
+
9
+ #p store.count( MyClass )
10
+
11
+ #Norma.bootstrap
12
+
13
+ #MyClass.record_store( store )
14
+
15
+ #p store.count( MyClass )
16
+
17
+ #obj = MyClass.new(1,2,3)
18
+ #obj.as_record.save
19
+
20
+ #obj2 = MyClass.record_load( obj.record_id )
21
+ #p obj2
22
+
23
+ # obj2 = MyClass.new( 4,5,6 )
24
+ #
25
+ # p obj1
26
+ # p obj2
27
+ #
28
+ # obj1.record_save
29
+ # obj2.record_save
30
+ #
31
+ # p obj1.record_id
32
+ # p obj2.record_id
33
+ #
34
+ # p obj1
35
+ # p obj2
36
+
@@ -0,0 +1,13 @@
1
+ class MyClass
2
+
3
+ attr_accessor :a, :b, :c, :d
4
+
5
+ def initialize( a,b,c )
6
+ @a = a
7
+ @b = b
8
+ @c = c
9
+ @d = [ a, b, c ]
10
+ end
11
+
12
+ end
13
+
@@ -0,0 +1,30 @@
1
+ require 'norma'
2
+ require './model.rb'
3
+ require './store.rb'
4
+
5
+ $norma_store.down
6
+ $norma_store.up
7
+
8
+ #MyClass.record_store(store)
9
+
10
+ # create and save
11
+ obj = MyClass.new(1,2,3)
12
+ obj.as_record.save
13
+
14
+ #obj2 = MyClass.record_load( obj.record_id )
15
+ #p obj2
16
+
17
+ # obj2 = MyClass.new( 4,5,6 )
18
+ #
19
+ # p obj1
20
+ # p obj2
21
+ #
22
+ # obj1.record_save
23
+ # obj2.record_save
24
+ #
25
+ # p obj1.record_id
26
+ # p obj2.record_id
27
+ #
28
+ # p obj1
29
+ # p obj2
30
+
@@ -0,0 +1,7 @@
1
+ $norma_store = Norma::Database::Postgres.connect(
2
+ :database => 'norma_development',
3
+ :username => 'trans',
4
+ :password => 'april',
5
+ :port => 5432
6
+ )
7
+
@@ -0,0 +1,3 @@
1
+ require 'norma/bootstrap'
2
+ require 'norma/database'
3
+ require 'norma/orm'
@@ -0,0 +1,10 @@
1
+ module Norma
2
+
3
+ def self.bootstrap
4
+ ObjectSpace.each_object(Class) do |c|
5
+ c.store.bootstrap if c.store
6
+ end
7
+ end
8
+
9
+ end
10
+
@@ -0,0 +1,233 @@
1
+ module Norma
2
+ ### Base class for database adapters.
3
+ ### This class is modeled for a minimal Postgresql
4
+ ### adapter as a reference point.
5
+ class Database
6
+
7
+ ### Database Interface Error class
8
+ class InterfaceError < TypeError ; end
9
+
10
+ #
11
+ CORE = [String, Symbol, Fixnum, Float, Range, Time, Array, Hash]
12
+
13
+ #
14
+ def initialize(connection, logger = nil)
15
+ @connection = connection
16
+ @logger = logger
17
+ @runtime = 0
18
+ @cache = {}
19
+ end
20
+
21
+ def bootstrap
22
+ raise InterfaceError, "no bootstrap method"
23
+ end
24
+
25
+ # Read a record.
26
+ def read(id, object=nil)
27
+ raise InterfaceError, "no read method"
28
+ end
29
+
30
+ # Save (or insert) a record.
31
+ def save(klass, obj, id=nil)
32
+ raise InterfaceError, "no save method"
33
+ end
34
+
35
+ # Delete a record.
36
+ def delete(klass, id)
37
+ raise InterfaceError, "no save method"
38
+ end
39
+
40
+ # Select records.
41
+ def select(&block)
42
+ raise InterfaceError, "no select method"
43
+ end
44
+
45
+ # Return next available object record index.
46
+ def mint_id
47
+ raise InterfaceError, "no mint_id method"
48
+ end
49
+
50
+ # Load a record. This calls read, but caches
51
+ # the result. This is very important, otherwise
52
+ # an object will be loaded more than once.
53
+ #
54
+ def load( id, object=nil )
55
+ @cache[id] ||= read(id)
56
+ end
57
+
58
+ #
59
+ def update_sql(object)
60
+ update_object_sql(object) + update_class_sql(object)
61
+ end
62
+
63
+ #
64
+ def insert_sql(object)
65
+ insert_object_sql(object) + insert_class_sql(object)
66
+ end
67
+
68
+ #
69
+ def update_object_sql(object)
70
+ sql = ''
71
+ sql << %|UPDATE obj\n|
72
+ sql << %|SET class='#{object.class}'\n|
73
+ sql << %|WHERE id=#{object.record_id}|
74
+ sql << ';'
75
+ sql
76
+ end
77
+
78
+ #
79
+ def insert_object_sql(object)
80
+ sql = ''
81
+ sql << %|INSERT INTO obj |
82
+ sql << %|(id, class)\n|
83
+ sql << %|VALUES (#{object.record_id}, '#{object.class}')|
84
+ sql << ';'
85
+ sql
86
+ end
87
+
88
+ #
89
+ def update_class_sql(object)
90
+ return '' unless respond_to?("sql#{object.class}")
91
+ sql = ''
92
+ sql << %|UPDATE obj#{object.class}\n|
93
+ sql << %|SET |
94
+ sql << send("sql#{object.class}").map{|k,v|"#{k}=#{v}"}.join(' ')
95
+ sql << %|\nWHERE id=#{object.id}|
96
+ sql << ';'
97
+ sql
98
+ end
99
+
100
+ #
101
+ def insert_class_sql(object)
102
+ return '' unless respond_to?("sql#{object.class}")
103
+
104
+ atts = send("sql#{object.class}", object)
105
+ keys = atts.keys.join(',')
106
+ vals = atts.values.join(',')
107
+
108
+ sql = ''
109
+ sql << %|INSERT INTO obj#{object.class} |
110
+ sql << %|(id, #{keys})\n|
111
+ sql << %|VALUES (#{object.record_id}, #{vals})|
112
+ sql << ';'
113
+ sql
114
+ end
115
+
116
+ #
117
+ def update_ivar_sql(object, field, value)
118
+ name = field.sub(/^@/,'')
119
+ sql = ''
120
+ sql << %|UPDATE ivar\n|
121
+ sql << %|SET val_id=#{value.record_id}\n|
122
+ sql << %|WHERE obj_id=#{object.record_id} AND name='#{name}'|
123
+ sql << ';'
124
+ end
125
+
126
+ #
127
+ def insert_ivar_sql(object, field, value)
128
+ name = field.sub(/^@/,'')
129
+ sql = ''
130
+ sql << %|INSERT INTO ivar |
131
+ sql << %|(obj_id, val_id, name)\n|
132
+ sql << %|VALUES (#{object.record_id}, #{value.record_id}, '#{name}')|
133
+ sql << ';'
134
+ end
135
+
136
+ ####################
137
+ # Object -> Record #
138
+ ####################
139
+
140
+ #
141
+ #def sqlObject(object)
142
+ # object.inspect
143
+ #end
144
+
145
+ def sqlSymbol(object)
146
+ { :value => object.to_s.inspect }
147
+ end
148
+
149
+ def sqlString(object)
150
+ { :value => object.inspect }
151
+ end
152
+
153
+ def sqlFixnum(object)
154
+ { :value => object.to_i }
155
+ end
156
+
157
+ def sqlFloat(object)
158
+ { :value => object.to_f }
159
+ end
160
+
161
+ def sqlRange(object)
162
+ { :begins => object.begin,
163
+ :ends => object.end,
164
+ :exclusive => object.exclude_end? }
165
+ end
166
+
167
+ def sqlTime(object)
168
+ { :value => object.strftime('%Y-%m-%d %H:%M:%S').inspect }
169
+ end
170
+
171
+ def sqlArray(object)
172
+ { :values => "'{" + object.map{|o|"#{o.record_id}"}.join(',') + "}'" }
173
+ end
174
+
175
+ def sqlHash(object)
176
+ { :keys => object.keys,
177
+ :values => object.values
178
+ }
179
+ end
180
+
181
+ ####################
182
+ # Record -> Object #
183
+ ####################
184
+
185
+ def newSymbol(rec)
186
+ rec[1].to_sym
187
+ end
188
+
189
+ #
190
+ def newString(rec)
191
+ rec[1]
192
+ end
193
+
194
+ def newFixnum(rec)
195
+ Integer(rec[1])
196
+ end
197
+
198
+ def newFloat(rec)
199
+ Float(rec[1])
200
+ end
201
+
202
+ def newRange(rec)
203
+ Range.new(rec[1], rec[2], rec[3])
204
+ end
205
+
206
+ def newTime(rec)
207
+ rec[1] # TODO: parse time string
208
+ end
209
+
210
+ def newArray(rec)
211
+ ary = []
212
+ rec[1].scan(/\d+/).each do |i|
213
+ ary << load(i.to_i)
214
+ end
215
+ ary
216
+ end
217
+
218
+ def newHash(rec)
219
+ keys = []
220
+ rec[1].scan(/\d+/).each do |id|
221
+ keys << load(id.to_i)
222
+ end
223
+ vals = []
224
+ rec[2].scan(/\d+/).each do |id|
225
+ vals << load(id.to_i)
226
+ end
227
+ Hash[*keys.zip(vals)]
228
+ end
229
+
230
+ end # class Database
231
+
232
+ end # module Norma
233
+
@@ -0,0 +1,143 @@
1
+ begin
2
+ require 'mysql'
3
+ rescue Object
4
+ puts 'Ruby MySQL bindings are not installed!'
5
+ end
6
+
7
+
8
+ module Norma
9
+
10
+ # Mysql Store
11
+ class Database::Mysql < Database
12
+
13
+ # Create a connection to database.
14
+ def self.connect(config)
15
+ host = config[:host] || "localhost"
16
+ port = config[:port] || 5433 unless host.nil?
17
+ username = config[:username] || ""
18
+ password = config[:password] || ""
19
+
20
+ if config.key?(:database)
21
+ database = config[:database]
22
+ else
23
+ raise ArgumentError, "No database specified. Missing argument: database."
24
+ end
25
+
26
+ return new(
27
+ PGconn.connect(host, port, "", "", database, username, password) #, logger
28
+ )
29
+ end
30
+
31
+ # Return database conguration.
32
+ def config
33
+ { :database => @connection.db,
34
+ :port => @connection.port,
35
+ :host => @connection.host,
36
+ :username => @connection.user
37
+ }
38
+ end
39
+
40
+ # Execute SQL
41
+ def execute(sql)
42
+ #puts "\n#{sql}\n" if $DEBUG
43
+ @connection.exec(sql)
44
+ end
45
+
46
+ # Create object link tables.
47
+ def up
48
+ file = File.join(File.dirname(__FILE__), 'up.sql')
49
+ sql = File.read(file)
50
+ execute sql
51
+ end
52
+
53
+ # Destroy object link tables.
54
+ def down
55
+ file = File.join(File.dirname(__FILE__), 'down.sql')
56
+ sql = File.read(file)
57
+ execute sql
58
+ end
59
+
60
+ def bootstrap
61
+ sql = "SELECT id FROM obj;"
62
+ res = execute(sql)
63
+ res.each do |row|
64
+ load(row['id'])
65
+ end
66
+ end
67
+
68
+ # Get a new object id.
69
+ def mint_id
70
+ r = execute "SELECT nextval('obj_sequence');"
71
+ r[0][0].to_i
72
+ end
73
+
74
+ # Save-out an object to the database.
75
+ def save(object)
76
+ #puts "Saving a #{object.class} id(#{object.record_id})"
77
+ sql = []
78
+ if object.as_record.persisted?
79
+ sql << update_sql(object)
80
+ object.state.each do |field, value|
81
+ value.as_record.save unless value.as_record.persisted?
82
+ sql << update_ivar_sql(object, field, value)
83
+ end
84
+ else
85
+ sql << insert_sql(object)
86
+ object.state.each do |field, value|
87
+ value.as_record.save unless value.as_record.persisted?
88
+ sql << insert_ivar_sql(object, field, value)
89
+ end
90
+ end
91
+ sql = sql.join("\n")
92
+ execute(sql)
93
+ object.as_record.persisted!
94
+ end
95
+
96
+ # Read object from database.
97
+ #
98
+ # TODO: Use a join query if possible and if more efficient.
99
+ #
100
+ def read(id, object=nil)
101
+ sql = "SELECT * FROM obj WHERE id=#{id};"
102
+ obj = execute(sql)
103
+
104
+ return nil unless obj # THINK: raise error instead?
105
+
106
+ classname = obj[0][1]
107
+
108
+ klass = Object.const_get(classname)
109
+
110
+ immutable = (klass<=NilClass || klass<=FalseClass || klass<=TrueClass || klass<=Fixnum || klass<=Symbol)
111
+
112
+ case klass
113
+ when NilClass
114
+ object = nil
115
+ when FalseClass
116
+ object = false
117
+ when TrueClass
118
+ object = true
119
+ else
120
+ if CORE.include?(klass)
121
+ sql = "SELECT * FROM obj#{klass} WHERE id=#{id};"
122
+ rec = execute(sql)
123
+ object = __send__("new#{klass}", rec[0])
124
+ else
125
+ object = klass.allocate
126
+ end
127
+ end
128
+
129
+ unless immutable
130
+ sql = "SELECT * FROM ivar WHERE obj_id=#{id};"
131
+ var = execute(sql)
132
+ var.each do |row|
133
+ object.__send__(:instance_variable_set, "@#{row['name']}", load(row['val_id']))
134
+ end
135
+ end
136
+
137
+ object
138
+ end
139
+
140
+ end # class Database::Postgres
141
+
142
+ end # module Norma
143
+
@@ -0,0 +1,17 @@
1
+ /* -*- sql -*-
2
+ * Note that ivar_obj_index is automatically
3
+ * dropped with the ivar table.
4
+ */
5
+ DROP TABLE ivar;
6
+ DROP TABLE obj;
7
+ DROP TABLE objSymbol;
8
+ DROP TABLE objString;
9
+ DROP TABLE objFixnum;
10
+ DROP TABLE objFloat;
11
+ DROP TABLE objRange;
12
+ DROP TABLE objTime;
13
+ DROP TABLE objArray;
14
+ DROP TABLE objHash;
15
+
16
+ DROP SEQUENCE obj_sequence;
17
+
@@ -0,0 +1,83 @@
1
+ /* Sequence for object ids. (Use uuids instead?) */
2
+
3
+ CREATE SEQUENCE obj_sequence;
4
+
5
+ /* Every object has an entry in the obj table.
6
+ * If the object is a core-class object, then it will also
7
+ * have an entry in one of the obj* class tables with the
8
+ * same id.
9
+ */
10
+
11
+ /* Should class allow NULL? */
12
+
13
+ CREATE TABLE obj (
14
+ id int,
15
+ class text NULL,
16
+ PRIMARY KEY (id)
17
+ );
18
+
19
+ CREATE TABLE objFixnum (
20
+ id int,
21
+ value int,
22
+ PRIMARY KEY (id)
23
+ );
24
+
25
+ CREATE TABLE objSymbol (
26
+ id int,
27
+ value text,
28
+ PRIMARY KEY (id)
29
+ );
30
+
31
+ CREATE TABLE objString (
32
+ id int,
33
+ value text,
34
+ PRIMARY KEY (id)
35
+ );
36
+
37
+ CREATE TABLE objFloat (
38
+ id int,
39
+ value float,
40
+ PRIMARY KEY (id)
41
+ );
42
+
43
+ CREATE TABLE objRange (
44
+ id int,
45
+ begins numeric,
46
+ ends numeric,
47
+ exclusive bool,
48
+ PRIMARY KEY (id)
49
+ );
50
+
51
+ CREATE TABLE objTime (
52
+ id int,
53
+ value timestamp,
54
+ PRIMARY KEY (id)
55
+ );
56
+
57
+ CREATE TABLE objArray (
58
+ id int,
59
+ values int[],
60
+ PRIMARY KEY (id)
61
+ );
62
+
63
+ CREATE TABLE objHash (
64
+ id int,
65
+ keys int[],
66
+ values int[],
67
+ PRIMARY KEY (id)
68
+ );
69
+
70
+ /***
71
+ *** Instance Variable Table. All instance
72
+ *** variables are stored in this table.
73
+ ***/
74
+ CREATE TABLE ivar (
75
+ name text NOT NULL,
76
+ obj_id int REFERENCES obj(id),
77
+ val_id int REFERENCES obj(id),
78
+ PRIMARY KEY (obj_id, name)
79
+ );
80
+
81
+ CREATE INDEX ivar_obj_index ON ivar(obj_id);
82
+
83
+
@@ -0,0 +1,145 @@
1
+ begin
2
+ require 'postgres'
3
+ rescue Object => ex
4
+ puts 'Ruby-PostgreSQL bindings are not installed!'
5
+ #Logger.error 'Ruby-PostgreSQL bindings are not installed!'
6
+ #Logger.error ex
7
+ end
8
+
9
+
10
+ module Norma
11
+
12
+ # Postgresql Store
13
+ class Database::Postgres < Database
14
+
15
+ # Create a connection to database.
16
+ def self.connect(config)
17
+ host = config[:host] || "localhost"
18
+ port = config[:port] || 5433 unless host.nil?
19
+ username = config[:username] || ""
20
+ password = config[:password] || ""
21
+
22
+ if config.key?(:database)
23
+ database = config[:database]
24
+ else
25
+ raise ArgumentError, "No database specified. Missing argument: database."
26
+ end
27
+
28
+ return new(
29
+ PGconn.connect(host, port, "", "", database, username, password) #, logger
30
+ )
31
+ end
32
+
33
+ # Return database conguration.
34
+ def config
35
+ { :database => @connection.db,
36
+ :port => @connection.port,
37
+ :host => @connection.host,
38
+ :username => @connection.user
39
+ }
40
+ end
41
+
42
+ # Execute SQL
43
+ def execute(sql)
44
+ #puts "\n#{sql}\n" if $DEBUG
45
+ @connection.exec(sql)
46
+ end
47
+
48
+ # Create object link tables.
49
+ def up
50
+ file = File.join(File.dirname(__FILE__), 'up.sql')
51
+ sql = File.read(file)
52
+ execute sql
53
+ end
54
+
55
+ # Destroy object link tables.
56
+ def down
57
+ file = File.join(File.dirname(__FILE__), 'down.sql')
58
+ sql = File.read(file)
59
+ execute sql
60
+ end
61
+
62
+ def bootstrap
63
+ sql = "SELECT id FROM obj;"
64
+ res = execute(sql)
65
+ res.each do |row|
66
+ load(row['id'])
67
+ end
68
+ end
69
+
70
+ # Get a new object id.
71
+ def mint_id
72
+ r = execute "SELECT nextval('obj_sequence');"
73
+ r[0][0].to_i
74
+ end
75
+
76
+ # Save-out an object to the database.
77
+ def save(object)
78
+ #puts "Saving a #{object.class} id(#{object.record_id})"
79
+ sql = []
80
+ if object.as_record.persisted?
81
+ sql << update_sql(object)
82
+ object.state.each do |field, value|
83
+ value.as_record.save unless value.as_record.persisted?
84
+ sql << update_ivar_sql(object, field, value)
85
+ end
86
+ else
87
+ sql << insert_sql(object)
88
+ object.state.each do |field, value|
89
+ value.as_record.save unless value.as_record.persisted?
90
+ sql << insert_ivar_sql(object, field, value)
91
+ end
92
+ end
93
+ sql = sql.join("\n")
94
+ execute(sql)
95
+ object.as_record.persisted!
96
+ end
97
+
98
+ # Read object from database.
99
+ #
100
+ # TODO: Use a join query if possible and if more efficient.
101
+ #
102
+ def read(id, object=nil)
103
+ sql = "SELECT * FROM obj WHERE id=#{id};"
104
+ obj = execute(sql)
105
+
106
+ return nil unless obj # THINK: raise error instead?
107
+
108
+ classname = obj[0][1]
109
+
110
+ klass = Object.const_get(classname)
111
+
112
+ immutable = (klass<=NilClass || klass<=FalseClass || klass<=TrueClass || klass<=Fixnum || klass<=Symbol)
113
+
114
+ case klass
115
+ when NilClass
116
+ object = nil
117
+ when FalseClass
118
+ object = false
119
+ when TrueClass
120
+ object = true
121
+ else
122
+ if CORE.include?(klass)
123
+ sql = "SELECT * FROM obj#{klass} WHERE id=#{id};"
124
+ rec = execute(sql)
125
+ object = __send__("new#{klass}", rec[0])
126
+ else
127
+ object = klass.allocate
128
+ end
129
+ end
130
+
131
+ unless immutable
132
+ sql = "SELECT * FROM ivar WHERE obj_id=#{id};"
133
+ var = execute(sql)
134
+ var.each do |row|
135
+ object.__send__(:instance_variable_set, "@#{row['name']}", load(row['val_id']))
136
+ end
137
+ end
138
+
139
+ object
140
+ end
141
+
142
+ end # class Database::Postgres
143
+
144
+ end # module Norma
145
+
@@ -0,0 +1,17 @@
1
+ /* -*- sql -*-
2
+ * Note that ivar_obj_index is automatically
3
+ * dropped with the ivar table.
4
+ */
5
+ DROP TABLE ivar;
6
+ DROP TABLE obj;
7
+ DROP TABLE objSymbol;
8
+ DROP TABLE objString;
9
+ DROP TABLE objFixnum;
10
+ DROP TABLE objFloat;
11
+ DROP TABLE objRange;
12
+ DROP TABLE objTime;
13
+ DROP TABLE objArray;
14
+ DROP TABLE objHash;
15
+
16
+ DROP SEQUENCE obj_sequence;
17
+
@@ -0,0 +1,83 @@
1
+ /* Sequence for object ids. (Use uuids instead?) */
2
+
3
+ CREATE SEQUENCE obj_sequence;
4
+
5
+ /* Every object has an entry in the obj table.
6
+ * If the object is a core-class object, then it will also
7
+ * have an entry in one of the obj* class tables with the
8
+ * same id.
9
+ */
10
+
11
+ /* Should class allow NULL? */
12
+
13
+ CREATE TABLE obj (
14
+ id int,
15
+ class text NULL,
16
+ PRIMARY KEY (id)
17
+ );
18
+
19
+ CREATE TABLE objFixnum (
20
+ id int,
21
+ value int,
22
+ PRIMARY KEY (id)
23
+ );
24
+
25
+ CREATE TABLE objSymbol (
26
+ id int,
27
+ value text,
28
+ PRIMARY KEY (id)
29
+ );
30
+
31
+ CREATE TABLE objString (
32
+ id int,
33
+ value text,
34
+ PRIMARY KEY (id)
35
+ );
36
+
37
+ CREATE TABLE objFloat (
38
+ id int,
39
+ value float,
40
+ PRIMARY KEY (id)
41
+ );
42
+
43
+ CREATE TABLE objRange (
44
+ id int,
45
+ begins numeric,
46
+ ends numeric,
47
+ exclusive bool,
48
+ PRIMARY KEY (id)
49
+ );
50
+
51
+ CREATE TABLE objTime (
52
+ id int,
53
+ value timestamp,
54
+ PRIMARY KEY (id)
55
+ );
56
+
57
+ CREATE TABLE objArray (
58
+ id int,
59
+ values int[],
60
+ PRIMARY KEY (id)
61
+ );
62
+
63
+ CREATE TABLE objHash (
64
+ id int,
65
+ keys int[],
66
+ values int[],
67
+ PRIMARY KEY (id)
68
+ );
69
+
70
+ /***
71
+ *** Instance Variable Table. All instance
72
+ *** variables are stored in this table.
73
+ ***/
74
+ CREATE TABLE ivar (
75
+ name text NOT NULL,
76
+ obj_id int REFERENCES obj(id),
77
+ val_id int REFERENCES obj(id),
78
+ PRIMARY KEY (obj_id, name)
79
+ );
80
+
81
+ CREATE INDEX ivar_obj_index ON ivar(obj_id);
82
+
83
+
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'norma/database'
4
+ require 'norma/database_adapter/postgres/adapter.rb'
5
+
6
+
7
+ module Norma
8
+
9
+ EXCLUDE_FIELDS = [ '@as_record' ]
10
+
11
+ # Record delegator
12
+ class Record
13
+
14
+ # Access the delegate object.
15
+ attr :object
16
+
17
+ #
18
+ def initialize( object )
19
+ @object = object
20
+ end
21
+
22
+ #
23
+ def store
24
+ @object.class.record_store
25
+ end
26
+
27
+ #
28
+ def id
29
+ @id ||= store.mint_id
30
+ end
31
+
32
+ # Is the object persisted in the database?
33
+ def persisted? ; @persisted ; end
34
+ def persisted! ; @persisted = true ; end
35
+
36
+ # Load
37
+ def reload
38
+ store.load(id, @object)
39
+ end
40
+
41
+ # Save
42
+ def save
43
+ store.save(@object)
44
+ end
45
+
46
+ end
47
+
48
+ #
49
+ class ::Class
50
+
51
+ # Set the storage adapter for the class.
52
+ # This allows differnt classes to be stored
53
+ # in different databases. By default the
54
+ # store set in the global $norma_store is
55
+ # used.
56
+ def record_store(store=nil)
57
+ @store = store if store
58
+ @store ||= $norma_store
59
+ end
60
+
61
+ def records
62
+ a = []
63
+ ObjectSpace.each_object(self) do |o| ; a << o ; end
64
+ a
65
+ end
66
+
67
+ end
68
+
69
+ #
70
+ class ::Object
71
+
72
+ def as_record
73
+ @as_record ||= Norma::Record.new(self)
74
+ end
75
+
76
+ def record_id
77
+ as_record.id
78
+ end
79
+
80
+ # TODO: rename to record_state ?
81
+ # TODO: Add user define include/exlcude of persitant variables.
82
+ def state
83
+ fields = instance_variables - Norma::EXCLUDE_FIELDS
84
+ values = fields.collect { |v| instance_variable_get(v) }
85
+ return fields.zip(values)
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+
@@ -0,0 +1 @@
1
+ norma
@@ -0,0 +1 @@
1
+ rubyworks
@@ -0,0 +1 @@
1
+ 0.2
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: norma
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.2"
5
+ platform: ruby
6
+ authors:
7
+ - "- Thomas Sawyer"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-23 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: ""
17
+ email: "- Thomas Sawyer"
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - MANIFEST
25
+ - COPYING
26
+ files:
27
+ - demo/bootstrap.rb
28
+ - demo/model.rb
29
+ - demo/setup.rb
30
+ - demo/store.rb
31
+ - lib/norma/bootstrap.rb
32
+ - lib/norma/database.rb
33
+ - lib/norma/database_adapter/mysql/adapter.rb
34
+ - lib/norma/database_adapter/mysql/down.sql
35
+ - lib/norma/database_adapter/mysql/up.sql
36
+ - lib/norma/database_adapter/postgres/adapter.rb
37
+ - lib/norma/database_adapter/postgres/down.sql
38
+ - lib/norma/database_adapter/postgres/up.sql
39
+ - lib/norma/orm.rb
40
+ - lib/norma.rb
41
+ - meta/package
42
+ - meta/project
43
+ - meta/version
44
+ - README
45
+ - MANIFEST
46
+ - COPYING
47
+ has_rdoc: true
48
+ homepage: http://rubyworks.github.com/norma
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --inline-source
54
+ - --title
55
+ - norma api
56
+ - --main
57
+ - README
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ requirements: []
73
+
74
+ rubyforge_project: norma
75
+ rubygems_version: 1.3.5
76
+ signing_key:
77
+ specification_version: 3
78
+ summary: ""
79
+ test_files: []
80
+