Rubernate 0.1.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/README +30 -0
- data/db/mysql.sql +35 -0
- data/db/oracle.sql +29 -0
- data/lib/rubernate/callbacks.rb +70 -0
- data/lib/rubernate/entity.rb +85 -0
- data/lib/rubernate/impl/dbigeneric.rb +286 -0
- data/lib/rubernate/impl/dbimysql.rb +28 -0
- data/lib/rubernate/impl/dbioracle.rb +29 -0
- data/lib/rubernate/impl/memory.rb +147 -0
- data/lib/rubernate/mixins.rb +91 -0
- data/lib/rubernate/peer.rb +70 -0
- data/lib/rubernate/queries.rb +444 -0
- data/lib/rubernate/runtime.rb +215 -0
- data/lib/rubernate.rb +127 -0
- data/tests/README +16 -0
- data/tests/all_tests.rb +12 -0
- data/tests/config.rb +34 -0
- data/tests/rubernate/callbacks_test.rb +120 -0
- data/tests/rubernate/fixtures.rb +27 -0
- data/tests/rubernate/impl/dbigeneric_stub.rb +635 -0
- data/tests/rubernate/impl/dbimysql_test.rb +19 -0
- data/tests/rubernate/impl/dbioracle_test.rb +19 -0
- data/tests/rubernate/impl/memory_test.rb +188 -0
- data/tests/rubernate/queries_test.rb +176 -0
- data/tests/rubernate/rubernate_test.rb +326 -0
- data/tests/rubernate/utils_test.rb +42 -0
- metadata +74 -0
data/README
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
== Rubernate
|
2
|
+
|
3
|
+
Rubernate is an object-oriented storage for Ruby objects based on relational database model.
|
4
|
+
|
5
|
+
Rubernate provides an ability to create persistent object hierarchies with minimal restrictions
|
6
|
+
on their structure. The main difference of Rubernate from traditional ORM is that it uses common
|
7
|
+
database tables to store all classes of persistent objects. All object's-related data are stored in
|
8
|
+
fixed set of tables.
|
9
|
+
|
10
|
+
This approach has following advantages:
|
11
|
+
|
12
|
+
* Simplicity - it simplifies greatly persistent classes creation and their modifications, too, because
|
13
|
+
it prevents database structure changes.
|
14
|
+
|
15
|
+
* Reusability - as persistence doesn't depend on database structure its possible to reuse objects in different projects.
|
16
|
+
|
17
|
+
* Object oriented storage - it is possible to put conditions on objects classes during a search.
|
18
|
+
|
19
|
+
Rubernate has following features:
|
20
|
+
|
21
|
+
* Classes can have persistent parameters of following types: Integer, String, Date, Time, Reference to Persistent Object, Array of References, Hash of References with key of type Integer or String or Date or Time.
|
22
|
+
|
23
|
+
* Built-in query language and support for native queries
|
24
|
+
|
25
|
+
* Support for Oracle and MySQL.
|
26
|
+
|
27
|
+
* Can be used independently or with other ORM like ActiveRecord
|
28
|
+
|
29
|
+
* Extendable architecture - can be easily extended by user functionality.
|
30
|
+
|
data/db/mysql.sql
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
/**
|
2
|
+
* Creates Rubernate tables for MySQL database.
|
3
|
+
* Copyright (C) 2006 Andrey Ryabov <andrey_ryabov@bk.ru>
|
4
|
+
*/
|
5
|
+
|
6
|
+
# Creates database named rubernate_db change name and uncomment if necessary
|
7
|
+
# CREATE DATABASE RUBERNATE_DB;
|
8
|
+
# USE RUBERNATE_DB;
|
9
|
+
|
10
|
+
|
11
|
+
DROP TABLE IF EXISTS R_PARAMS;
|
12
|
+
DROP TABLE IF EXISTS R_OBJECTS;
|
13
|
+
|
14
|
+
CREATE TABLE R_OBJECTS (
|
15
|
+
OBJECT_PK INTEGER(20) PRIMARY KEY AUTO_INCREMENT,
|
16
|
+
OBJECT_CLASS VARCHAR(100) NOT NULL) ENGINE=InnoDB;
|
17
|
+
|
18
|
+
CREATE TABLE R_PARAMS (
|
19
|
+
OBJECT_PK INTEGER(20) NOT NULL,
|
20
|
+
NAME VARCHAR(100) NOT NULL,
|
21
|
+
FLAGS INTEGER(5) NOT NULL,
|
22
|
+
INT_VALUE INTEGER(20),
|
23
|
+
STR_VALUE VARCHAR(255),
|
24
|
+
DAT_VALUE DATETIME,
|
25
|
+
REF_VALUE INTEGER(20),
|
26
|
+
CONSTRAINT R_PARAM_FK FOREIGN KEY (OBJECT_PK) REFERENCES R_OBJECTS(OBJECT_PK) ON DELETE CASCADE,
|
27
|
+
CONSTRAINT R_REF_FK FOREIGN KEY (REF_VALUE) REFERENCES R_OBJECTS(OBJECT_PK) ON DELETE CASCADE) ENGINE=InnoDB;
|
28
|
+
|
29
|
+
CREATE INDEX R_O_PK_CLASS ON R_OBJECTS (OBJECT_PK ASC, OBJECT_CLASS);
|
30
|
+
CREATE INDEX R_P_PK_NAME ON R_PARAMS (OBJECT_PK ASC, NAME);
|
31
|
+
|
32
|
+
|
33
|
+
/**
|
34
|
+
* End
|
35
|
+
*/
|
data/db/oracle.sql
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
/**
|
2
|
+
* Creates Rubernate tables for Oracle database.
|
3
|
+
* Copyright (C) 2006 Andrey Ryabov <andrey_ryabov@bk.ru>
|
4
|
+
*/
|
5
|
+
|
6
|
+
CREATE TABLE R_OBJECTS (
|
7
|
+
OBJECT_PK NUMBER(20) PRIMARY KEY,
|
8
|
+
OBJECT_CLASS VARCHAR2(100) NOT NULL)
|
9
|
+
/
|
10
|
+
CREATE TABLE R_PARAMS (
|
11
|
+
OBJECT_PK NUMBER(20) NOT NULL,
|
12
|
+
NAME VARCHAR2(100) NOT NULL,
|
13
|
+
FLAGS NUMBER(5) NOT NULL,
|
14
|
+
INT_VALUE NUMBER(20),
|
15
|
+
STR_VALUE VARCHAR2(1000),
|
16
|
+
DAT_VALUE DATE,
|
17
|
+
REF_VALUE NUMBER(20),
|
18
|
+
CONSTRAINT R_PARAM_FK FOREIGN KEY (OBJECT_PK) REFERENCES R_OBJECTS(OBJECT_PK) ON DELETE CASCADE,
|
19
|
+
CONSTRAINT R_REF_FK FOREIGN KEY (REF_VALUE) REFERENCES R_OBJECTS(OBJECT_PK) ON DELETE CASCADE)
|
20
|
+
/
|
21
|
+
CREATE INDEX R_O_PK_CLASS ON R_OBJECTS (OBJECT_PK ASC, OBJECT_CLASS)
|
22
|
+
/
|
23
|
+
CREATE INDEX R_P_PK_NAME ON R_PARAMS (OBJECT_PK ASC, NAME)
|
24
|
+
/
|
25
|
+
CREATE SEQUENCE R_PK_SEQUENCE START WITH 1001 INCREMENT BY 1
|
26
|
+
/
|
27
|
+
/**
|
28
|
+
* End
|
29
|
+
*/
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Rubernate
|
2
|
+
|
3
|
+
# This module is holder for other callback modules.
|
4
|
+
# Methods in these modules are intended to be overridden by user
|
5
|
+
# in other to extend basic functionality.
|
6
|
+
module Callbacks
|
7
|
+
# This module contains callback methods and
|
8
|
+
# included in module Rubernate::Entity
|
9
|
+
module Entity
|
10
|
+
# Invoked if object has just been created in database.
|
11
|
+
def on_create
|
12
|
+
end
|
13
|
+
|
14
|
+
# Invoked when object is about to be removed from database.
|
15
|
+
def on_remove
|
16
|
+
end
|
17
|
+
|
18
|
+
# Invoked when object is about to be stored in database.
|
19
|
+
def on_save
|
20
|
+
end
|
21
|
+
|
22
|
+
# Invoked if object has just been loaded from database.
|
23
|
+
def on_load
|
24
|
+
end
|
25
|
+
|
26
|
+
# Invoked if property of object has just been reassigned by +=+ operator.
|
27
|
+
def on_change prop_name, old_value, new_value
|
28
|
+
end
|
29
|
+
|
30
|
+
# Invoked if Hash or Array property has been modified.
|
31
|
+
def on_modify prop_name, old_value, new_value
|
32
|
+
end
|
33
|
+
|
34
|
+
# Invoked if object referred by this one has been deleted
|
35
|
+
# TODO: implement invokations
|
36
|
+
def on_lose_ref refered_by
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# This module contains callback method and
|
41
|
+
# included in class Rubernate::Runtime.
|
42
|
+
module Runtime
|
43
|
+
# Invoked on session creation.
|
44
|
+
def on_begin
|
45
|
+
end
|
46
|
+
|
47
|
+
# Invoked if session is about to be rolled back.
|
48
|
+
def on_rollback
|
49
|
+
end
|
50
|
+
|
51
|
+
# Invoked if session is about to be commited.
|
52
|
+
def before_commit
|
53
|
+
end
|
54
|
+
|
55
|
+
# Invoked if session has just been commited
|
56
|
+
def after_commit
|
57
|
+
end
|
58
|
+
|
59
|
+
# Invoked when Rubernate needs to flush modified objects
|
60
|
+
# It can happens when find_by_query is called or if session is going to be commited
|
61
|
+
def before_flush modified
|
62
|
+
end
|
63
|
+
|
64
|
+
# Invoked if after flushing modified objects
|
65
|
+
# This method is invoked only if any objects has been actually flushed
|
66
|
+
def after_flush
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Rubernate
|
2
|
+
|
3
|
+
# This module contains methods that will be included into persistent objects
|
4
|
+
module Entity
|
5
|
+
include Rubernate
|
6
|
+
include Callbacks::Entity
|
7
|
+
|
8
|
+
# This property holds objects primary key.
|
9
|
+
# The primary key is not +nil+ for objects attached to session.
|
10
|
+
attr :primary_key, true
|
11
|
+
|
12
|
+
# Attaches object to session returns self
|
13
|
+
def attach
|
14
|
+
Rubernate.runtime.attach self
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Deletes object.
|
19
|
+
def remove!
|
20
|
+
Rubernate.runtime.remove self
|
21
|
+
@removed = true
|
22
|
+
end
|
23
|
+
|
24
|
+
# Checks if object has been removed in current session
|
25
|
+
def removed?
|
26
|
+
@removed
|
27
|
+
end
|
28
|
+
|
29
|
+
# Retruns objects +peer+ if ther is one or +nil+ in other case
|
30
|
+
def peer
|
31
|
+
@peer
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets peer if there is no one and invokes on_load callback else skip.
|
35
|
+
# If value is _nil_ peer will be lazy initialized in ensure_loaded.
|
36
|
+
# This method should be invoked only once with not nil value.
|
37
|
+
def peer= value
|
38
|
+
return if @peer
|
39
|
+
if value
|
40
|
+
disable_ensure
|
41
|
+
else
|
42
|
+
def self.ensure_loaded
|
43
|
+
Rubernate.runtime.find_by_pk @primary_key, true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@peer = value
|
47
|
+
end
|
48
|
+
|
49
|
+
def dirty?
|
50
|
+
peer.dirty? &on_modify_callback
|
51
|
+
end
|
52
|
+
|
53
|
+
# Defines metod == in for rubernated classes
|
54
|
+
def self.included klass
|
55
|
+
klass.module_eval %{
|
56
|
+
def == other
|
57
|
+
return self.primary_key == other.primary_key if other.is_a? Entity
|
58
|
+
super
|
59
|
+
end
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def ensure_loaded
|
65
|
+
self.peer = Peer.new
|
66
|
+
end
|
67
|
+
|
68
|
+
def disable_ensure
|
69
|
+
def self.ensure_loaded
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_modify_callback
|
74
|
+
@__on_modify_callback ||= proc {|p_name, old_value, new_value|
|
75
|
+
on_modify p_name, old_value, new_value # callback
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def on_change_callback
|
80
|
+
@__on_change_callback ||= proc {|p_name, old_value, new_value|
|
81
|
+
on_change p_name, old_value, new_value # callback
|
82
|
+
}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
require 'dbi'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Rubernate
|
5
|
+
module DBI
|
6
|
+
# Param flags constants
|
7
|
+
PARAM_FLAG_INT = (1 << 0).to_i
|
8
|
+
PARAM_FLAG_STRING = (1 << 1).to_i
|
9
|
+
PARAM_FLAG_TIME = (1 << 2).to_i
|
10
|
+
PARAM_FLAG_DATE = (1 << 3).to_i
|
11
|
+
PARAM_FLAG_REF = (1 << 4).to_i
|
12
|
+
PARAM_FLAG_ARRAY = (1 << 5).to_i
|
13
|
+
PARAM_FLAG_HASH = (1 << 6).to_i
|
14
|
+
|
15
|
+
# Array and Hashs combinations
|
16
|
+
ARRAY_INT_REF = (PARAM_FLAG_ARRAY | PARAM_FLAG_INT).to_i
|
17
|
+
HASH_INT_REF = (PARAM_FLAG_HASH | PARAM_FLAG_INT).to_i
|
18
|
+
HASH_STRING_REF = (PARAM_FLAG_HASH | PARAM_FLAG_STRING).to_i
|
19
|
+
HASH_TIME_REF = (PARAM_FLAG_HASH | PARAM_FLAG_TIME).to_i
|
20
|
+
HASH_DATE_REF = (PARAM_FLAG_HASH | PARAM_FLAG_DATE).to_i
|
21
|
+
|
22
|
+
# Represents factory for +Runtime+ implementors.
|
23
|
+
class RuntimeFactory
|
24
|
+
# Accepts +Runtime+ impl. class, database url, user name, and user password
|
25
|
+
def initialize klass, db_url, db_user, db_password
|
26
|
+
@klass, @db_url, @db_user, @db_password = klass, db_url, db_user, db_password
|
27
|
+
# @params = {'AutoCommit' => false} #TODO: refine
|
28
|
+
end
|
29
|
+
|
30
|
+
# Creates initialized PeerHost
|
31
|
+
def create
|
32
|
+
@klass.new connect
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates connection and pass it in block if present
|
36
|
+
# else return it if there is no block given.
|
37
|
+
def connect
|
38
|
+
return ::DBI.connect(@db_url, @db_user, @db_password, @params) unless block_given?
|
39
|
+
::DBI.connect(@db_url, @db_user, @db_password, @params) { |dbh| yield dbh }
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
"runtime impl: #{@klass}, db_url: #{@db_url}, db_user: #{@db_user}, db_password: #{@db_password}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Represent base class for all Runtime implementation.
|
48
|
+
# Contains generic sql implementations of all required methods except +create+.
|
49
|
+
# The +create+ method should be implemented in subclasses.
|
50
|
+
class Runtime < Rubernate::Runtime
|
51
|
+
include DBI
|
52
|
+
|
53
|
+
# Parameters are deleted automatically with corresponding r_object records
|
54
|
+
#DELETE_PARAMS = <<-SQL
|
55
|
+
# DELETE FROM R_PARAMS WHERE OBJECT_PK = ?
|
56
|
+
# SQL
|
57
|
+
DELETE_PARAMS_FOR = <<-SQL
|
58
|
+
DELETE FROM R_PARAMS WHERE OBJECT_PK IN
|
59
|
+
SQL
|
60
|
+
DELETE_OBJECT = <<-SQL
|
61
|
+
DELETE FROM R_OBJECTS WHERE OBJECT_PK = ?
|
62
|
+
SQL
|
63
|
+
SAVE_PARAMS = <<-SQL
|
64
|
+
INSERT INTO R_PARAMS VALUES (?, ?, ?, ?, ?, ?, ?)
|
65
|
+
SQL
|
66
|
+
SELECT_PARAMS = <<-SQL
|
67
|
+
SELECT P.*, O.OBJECT_CLASS, R.OBJECT_CLASS
|
68
|
+
FROM R_PARAMS P JOIN R_OBJECTS O ON (O.OBJECT_PK = P.OBJECT_PK)
|
69
|
+
LEFT OUTER JOIN R_OBJECTS R ON (P.REF_VALUE = R.OBJECT_PK)
|
70
|
+
WHERE O.OBJECT_PK IN
|
71
|
+
SQL
|
72
|
+
SELECT_ONE_OBJECT = <<-SQL
|
73
|
+
SELECT O.OBJECT_PK, P.NAME, P.FLAGS, P.INT_VALUE, P.STR_VALUE,
|
74
|
+
P.DAT_VALUE, P.REF_VALUE, O.OBJECT_CLASS, R.OBJECT_CLASS
|
75
|
+
FROM R_OBJECTS O
|
76
|
+
LEFT OUTER JOIN R_PARAMS P ON (O.OBJECT_PK = P.OBJECT_PK)
|
77
|
+
LEFT OUTER JOIN R_OBJECTS R ON (P.REF_VALUE = R.OBJECT_PK)
|
78
|
+
WHERE O.OBJECT_PK = ?
|
79
|
+
SQL
|
80
|
+
|
81
|
+
# R_OBJECTS table's column's indexes
|
82
|
+
CI_O_PK = 0
|
83
|
+
CI_O_CLASS = 1
|
84
|
+
|
85
|
+
# R_PARAMS table's column's indexes
|
86
|
+
CI_P_PK = 0
|
87
|
+
CI_P_NAME = 1
|
88
|
+
CI_P_FLAGS = 2
|
89
|
+
CI_P_INT = 3
|
90
|
+
CI_P_STR = 4
|
91
|
+
CI_P_TIME = 5
|
92
|
+
CI_P_REF = 6
|
93
|
+
|
94
|
+
attr :dbh, false
|
95
|
+
|
96
|
+
def initialize dbh
|
97
|
+
super()
|
98
|
+
@dbh = dbh
|
99
|
+
end
|
100
|
+
|
101
|
+
# Updates object state in database
|
102
|
+
def save objects
|
103
|
+
objects = [objects] unless objects.kind_of? Array
|
104
|
+
return if objects.empty?
|
105
|
+
# clear r_params for objects
|
106
|
+
@dbh.do DELETE_PARAMS_FOR + object_ids_p(objects)
|
107
|
+
# store r_params for objects
|
108
|
+
@dbh.prepare SAVE_PARAMS do |sth|
|
109
|
+
for object in objects
|
110
|
+
object.peer.each {|name, param|
|
111
|
+
save_param sth, object.primary_key, name.to_s, param
|
112
|
+
}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
# clear dirty flag
|
116
|
+
for object in objects
|
117
|
+
object.peer.dirty = false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Loads object by primary_key
|
122
|
+
def load_by_pk pk
|
123
|
+
klass, peer = nil
|
124
|
+
dbh.execute(SELECT_ONE_OBJECT, pk) { |sth|
|
125
|
+
sth.fetch { |row|
|
126
|
+
klass, peer = row[7], Rubernate::Peer.new unless klass
|
127
|
+
fetch_param peer, row
|
128
|
+
}
|
129
|
+
}
|
130
|
+
return nil unless klass
|
131
|
+
obj = instantiate pk, class_by_name(klass), peer
|
132
|
+
post_load obj
|
133
|
+
obj
|
134
|
+
end
|
135
|
+
|
136
|
+
# Loads objects by query with specified params.
|
137
|
+
def load_by_query query, params=[]
|
138
|
+
result = []
|
139
|
+
buffer = {} # buffer for not loaded objects (whose peer is nil)
|
140
|
+
# selects objects and stores not loaded to buffer
|
141
|
+
dbh.execute(query, *params) { |sth|
|
142
|
+
sth.fetch { |row|
|
143
|
+
obj = instantiate row[0].to_i, class_by_name(row[1])
|
144
|
+
buffer[obj.primary_key], obj.peer = obj, Rubernate::Peer.new unless obj.peer
|
145
|
+
result << obj
|
146
|
+
}
|
147
|
+
}
|
148
|
+
return result if buffer.empty?
|
149
|
+
# load peers for objects in buffer.
|
150
|
+
dbh.execute(SELECT_PARAMS + object_ids_p(buffer.values)) {|sth|
|
151
|
+
sth.fetch {|row|
|
152
|
+
fetch_param buffer[row[0].to_i].peer, row
|
153
|
+
}
|
154
|
+
}
|
155
|
+
# clears dirty flags
|
156
|
+
buffer.values.each {|object| post_load object}
|
157
|
+
result
|
158
|
+
end
|
159
|
+
|
160
|
+
# Deletes object from database
|
161
|
+
def delete object
|
162
|
+
# Rows from r_params should be deleted automaticaly
|
163
|
+
# by foregin key contraint
|
164
|
+
#dbh.do DELETE_PARAMS, object.primary_key
|
165
|
+
dbh.do DELETE_OBJECT, object.primary_key
|
166
|
+
object.primary_key = nil
|
167
|
+
end
|
168
|
+
|
169
|
+
def close
|
170
|
+
@dbh.commit
|
171
|
+
@dbh.disconnect
|
172
|
+
end
|
173
|
+
|
174
|
+
def failed
|
175
|
+
@dbh.rollback rescue Log.error 'Error during rollback in session failed state'
|
176
|
+
@dbh.disconnect rescue Log.error 'Error during disconnect in session failed state'
|
177
|
+
end
|
178
|
+
private
|
179
|
+
def save_param sth, pk, name, param
|
180
|
+
case param
|
181
|
+
when Entity: save_ref sth, pk, name, param
|
182
|
+
when Hash: save_hash sth, pk, name, param
|
183
|
+
when Array: save_array sth, pk, name, param
|
184
|
+
when Integer: save_int sth, pk, name, param
|
185
|
+
when Time: save_time sth, pk, name, param
|
186
|
+
when Date: save_date sth, pk, name, param
|
187
|
+
else save_str sth, pk, name, param
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def save_ref sth, pk, name, ref
|
192
|
+
sth.execute pk, name, PARAM_FLAG_REF, nil, nil, nil, ref.primary_key unless ref.removed?
|
193
|
+
end
|
194
|
+
|
195
|
+
def save_time sth, pk, name, time
|
196
|
+
sth.execute pk, name, PARAM_FLAG_TIME, nil, nil, ::DBI::Timestamp.new(time), nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def save_date sth, pk, name, date
|
200
|
+
sth.execute pk, name, PARAM_FLAG_DATE, nil, nil, ::DBI::Timestamp.new(date), nil
|
201
|
+
end
|
202
|
+
|
203
|
+
def save_hash sth, pk, name, hash
|
204
|
+
hash.delete_if {|key, ref| ref.removed?}
|
205
|
+
if hash.empty?
|
206
|
+
sth.execute pk, name, PARAM_FLAG_HASH, 0, nil, nil, nil
|
207
|
+
else
|
208
|
+
hash.each {|key, ref| save_key_value sth, pk, name, key, ref.primary_key}
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def save_key_value sth, pk, name, key, ref
|
213
|
+
if key.is_a? String
|
214
|
+
sth.execute pk, name, HASH_STRING_REF, nil, key, nil, ref
|
215
|
+
elsif key.is_a? Integer
|
216
|
+
sth.execute pk, name, HASH_INT_REF, key, nil, nil, ref
|
217
|
+
elsif key.is_a? ::Time # TODO: chek time and date scopes?
|
218
|
+
sth.execute pk, name, HASH_TIME_REF, nil, nil, ::DBI::Timestamp.new(key), ref
|
219
|
+
elsif key.is_a? ::Date
|
220
|
+
sth.execute pk, name, HASH_DATE_REF, nil, nil, ::DBI::Timestamp.new(key), ref
|
221
|
+
elsif key.nil?
|
222
|
+
sth.execute pk, name, PARAM_FLAG_HASH, nil, nil, nil, ref
|
223
|
+
else
|
224
|
+
raise "invalid hash key value #{key}"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def save_array sth, pk, name, array
|
229
|
+
array.delete_if {|ref| !ref or ref.removed?} # Reject removed and nil items
|
230
|
+
if array.empty? # Empty arrays should also be stored
|
231
|
+
sth.execute pk, name, ARRAY_INT_REF, 0, nil, nil, nil
|
232
|
+
else
|
233
|
+
array.each_index { |idx| sth.execute pk, name,
|
234
|
+
ARRAY_INT_REF, idx, nil, nil, array[idx].primary_key}
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def save_int sth, pk, name, param
|
239
|
+
sth.execute pk, name, PARAM_FLAG_INT, param, nil, nil, nil
|
240
|
+
end
|
241
|
+
|
242
|
+
def save_str sth, pk, name, param
|
243
|
+
sth.execute pk, name, PARAM_FLAG_STRING, nil, param.to_s, nil, nil
|
244
|
+
end
|
245
|
+
|
246
|
+
def fetch_param peer, row
|
247
|
+
return unless row[1] # there are no params for this object
|
248
|
+
name, flags, ref = row[1].to_sym, row[2].to_i
|
249
|
+
if flags & PARAM_FLAG_ARRAY != 0
|
250
|
+
peer[name] = [] unless peer[name]
|
251
|
+
peer[name][fetch_value(row, flags)] = ref if ref = fetch_ref_value(row)
|
252
|
+
elsif flags & PARAM_FLAG_HASH != 0
|
253
|
+
peer[name] = {} unless peer[name]
|
254
|
+
peer[name][fetch_value(row, flags)] = ref if ref = fetch_ref_value(row)
|
255
|
+
else
|
256
|
+
peer[name] = fetch_value row, flags
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def fetch_value row, flags
|
261
|
+
return row[CI_P_INT].to_i if flags & PARAM_FLAG_INT != 0
|
262
|
+
return row[CI_P_STR].to_s if flags & PARAM_FLAG_STRING != 0
|
263
|
+
return instantiate(row[CI_P_REF].to_i, class_by_name(row[CI_P_REF + 2])) if flags & PARAM_FLAG_REF != 0
|
264
|
+
return row[CI_P_TIME].to_time if flags & PARAM_FLAG_TIME != 0
|
265
|
+
return row[CI_P_TIME].to_date if flags & PARAM_FLAG_DATE != 0
|
266
|
+
return nil
|
267
|
+
end
|
268
|
+
|
269
|
+
def fetch_ref_value row
|
270
|
+
ref_pk = nil
|
271
|
+
instantiate ref_pk, class_by_name(row[8]) if ref_pk = row[6]
|
272
|
+
end
|
273
|
+
|
274
|
+
def object_ids objects
|
275
|
+
ids = objects.collect {|o| o.primary_key}
|
276
|
+
ids.uniq!
|
277
|
+
ids.map! {|id| id.to_s}
|
278
|
+
ids.join ', '
|
279
|
+
end
|
280
|
+
|
281
|
+
def object_ids_p objects
|
282
|
+
'(' + object_ids(objects) + ')'
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'dbi'
|
2
|
+
|
3
|
+
module Rubernate
|
4
|
+
module DBI
|
5
|
+
class MySqlRuntime < Runtime
|
6
|
+
SELECT_NEXT_PK = <<-SQL
|
7
|
+
SELECT LAST_INSERT_ID()
|
8
|
+
SQL
|
9
|
+
CREATE_PEER = <<-SQL
|
10
|
+
INSERT INTO R_OBJECTS (OBJECT_CLASS) values (?)
|
11
|
+
SQL
|
12
|
+
|
13
|
+
def initialize dbh
|
14
|
+
super
|
15
|
+
dbh.do 'SET AUTOCOMMIT=0'
|
16
|
+
end
|
17
|
+
|
18
|
+
# Creates record in r_objects for specified object
|
19
|
+
def create object
|
20
|
+
object.peer = Rubernate::Peer.new
|
21
|
+
@dbh.do CREATE_PEER, object.class.name
|
22
|
+
object.primary_key = @dbh.select_one(SELECT_NEXT_PK)[0].to_i
|
23
|
+
object.peer.dirty = true
|
24
|
+
object.primary_key
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'dbi'
|
2
|
+
|
3
|
+
module Rubernate
|
4
|
+
module DBI
|
5
|
+
class OracleRuntime < Runtime
|
6
|
+
SELECT_NEXT_PK = <<-SQL
|
7
|
+
SELECT R_PK_SEQUENCE.NEXTVAL FROM DUAL
|
8
|
+
SQL
|
9
|
+
CREATE_PEER = <<-SQL
|
10
|
+
INSERT INTO R_OBJECTS (OBJECT_PK, OBJECT_CLASS) values (?, ?)
|
11
|
+
SQL
|
12
|
+
|
13
|
+
# TODO: make it work
|
14
|
+
def initialize dbh
|
15
|
+
super
|
16
|
+
dbh.do "ALTER session SET nls_date_format = 'YYYY/MM/DD HH24:MI:SS'"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Creates record in r_objects for specified object
|
20
|
+
def create object
|
21
|
+
object.peer = Rubernate::Peer.new
|
22
|
+
object.primary_key = @dbh.select_one(SELECT_NEXT_PK)[0].to_i
|
23
|
+
@dbh.do CREATE_PEER, object.primary_key, object.class.name
|
24
|
+
object.peer.dirty = true
|
25
|
+
object.primary_key
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|