Class: Og::Adapter

Inherits:
Object show all
Includes:
Singleton
Defined in:
lib/og/adapter.rb

Overview

An adapter communicates with the backend datastore. The adapters for all supported datastores extend this class. Typically, an RDBMS is used to implement a datastore.

Direct Known Subclasses

MysqlAdapter, PsqlAdapter, SqliteAdapter

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeAdapter

Returns a new instance of Adapter.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/og/adapter.rb', line 31

def initialize
	@typemap = {
		Integer => 'integer',
		Fixnum => 'integer',
		Float => 'float',
		String => 'text',
		Time => 'timestamp',
		Date => 'date',
		TrueClass => 'boolean',
		Object => 'text',
		Array => 'text',
		Hash => 'text'
	}
end

Instance Attribute Details

#typemapObject

A mapping between Ruby and backend Datastore types.



22
23
24
# File 'lib/og/adapter.rb', line 22

def typemap
  @typemap
end

Class Method Details

.date(date) ⇒ Object

Output YYY-mm-dd TODO: Optimize this



68
69
70
71
# File 'lib/og/adapter.rb', line 68

def self.date(date)
	return nil unless date
	return "#{date.year}-#{date.month}-#{date.mday}" 
end

.encode(klass) ⇒ Object

Encode the name of the klass as an sql safe string. The Module separators are replaced with _ and NOT stripped out so that we can convert back to the original notation if needed. The leading module if available is removed.



119
120
121
# File 'lib/og/adapter.rb', line 119

def self.encode(klass)
	"#{klass.name.gsub(/^.*::/, "")}".gsub(/::/, "_").downcase
end

.escape(str) ⇒ Object

Escape an SQL string



52
53
54
55
# File 'lib/og/adapter.rb', line 52

def self.escape(str)
	return nil unless str
	return str.gsub( /'/, "''" )
end

.for_name(name) ⇒ Object

Lookup the adapter instance from the adapter name.



26
27
28
29
# File 'lib/og/adapter.rb', line 26

def self.for_name(name)
	require "og/adapters/#{name}"
	eval %{	return  #{name.capitalize}Adapter.instance }
end

.join_table(klass1, klass2) ⇒ Object

The name of the join table for the two given classes.



132
133
134
# File 'lib/og/adapter.rb', line 132

def self.join_table(klass1, klass2)
	"_#{Og.table_prefix}j_#{encode(klass1)}_#{encode(klass2)}"
end

.parse_date(str) ⇒ Object

Input YYYY-mm-dd TODO: Optimize this



83
84
85
86
# File 'lib/og/adapter.rb', line 83

def self.parse_date(str)
	return nil unless str
	return Date.strptime(str)
end

.parse_timestamp(str) ⇒ Object

Parse sql datetime TODO: Optimize this



76
77
78
# File 'lib/og/adapter.rb', line 76

def self.parse_timestamp(str)
	return Time.parse(str)		
end

.table(klass) ⇒ Object

The name of the SQL table where objects of this class are stored.



126
127
128
# File 'lib/og/adapter.rb', line 126

def self.table(klass)
	"_#{Og.table_prefix}#{encode(klass)}"
end

.timestamp(time = Time.now) ⇒ Object

Convert a ruby time to an sql timestamp. TODO: Optimize this



60
61
62
63
# File 'lib/og/adapter.rb', line 60

def self.timestamp(time = Time.now)
	return nil unless time
	return time.strftime("%Y-%m-%d %H:%M:%S")
end

Instance Method Details

#calc_field_index(klass, og) ⇒ Object

Generate the mapping of the database fields to the object properties.



237
238
239
# File 'lib/og/adapter.rb', line 237

def calc_field_index(klass, og)
	raise 'Not implemented!'
end

#create_db(database, user = nil, password = nil) ⇒ Object

Create the database.



94
95
96
# File 'lib/og/adapter.rb', line 94

def create_db(database, user = nil, password = nil)
	Logger.info "Creating database '#{database}'."
end

#create_fields(klass) ⇒ Object

Create the fields that correpsond to the klass properties. The generated fields array is used in create_table. If the property has an :sql metadata this overrides the default mapping. If the property has an :extra_sql metadata the extra sql is appended after the default mapping.



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/og/adapter.rb', line 188

def create_fields(klass)
	fields = []
	
	klass.__props.each do |p|
		klass.sql_index(p.symbol) if p.meta[:sql_index]
			
		field = "#{p.symbol}"
		
		if p.meta and p.meta[:sql]
			field << " #{p.meta[:sql]}"
		else
			field << " #{@typemap[p.klass]}"
			# attach extra sql
			if p.meta and extra_sql = p.meta[:extra_sql]
				field << " #{extra_sql}"
			end
		end
		
		fields << field 
	end

	return fields
end

#create_table(klass) ⇒ Object

Create the managed object table. The properties of the object are mapped to the table columns. Additional sql relations and constrains are created (indicices, sequences, etc).



216
217
218
# File 'lib/og/adapter.rb', line 216

def create_table(klass)
	raise 'Not implemented!'	
end

#drop_db(database, user = nil, password = nil) ⇒ Object

Drop the database.



100
101
102
# File 'lib/og/adapter.rb', line 100

def drop_db(database, user = nil, password = nil)
	Logger.info "Dropping database '#{database}'."
end

#eval_og_insert(klass, db) ⇒ Object

Precompile the insert code for the given class. The generated code sets the oid when inserting!



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/og/adapter.rb', line 256

def eval_og_insert(klass, db)
	if klass.instance_methods.include?('og_pre_insert')
		pre_cb = 'og_pre_insert(conn);'
	else
		pre_cb = ''
	end

	if klass.instance_methods.include?('og_post_insert')
		post_cb = 'og_post_insert(conn);'
	else
		post_cb = ''
	end

	if klass.instance_methods.include?('og_pre_insert_update')
		pre_cb << 'og_pre_insert_update(conn);'
	end

	if klass.instance_methods.include?('og_post_insert_update')
		post_cb << 'og_post_insert_update(conn);'
	end
	
	klass.class_eval %{
		def og_insert(conn)
			#{insert_code(klass, db, pre_cb, post_cb)}
		end
	}
end

#eval_og_oid(klass) ⇒ Object

Generate the property for oid.



247
248
249
250
251
# File 'lib/og/adapter.rb', line 247

def eval_og_oid(klass)
	klass.class_eval %{
		prop_accessor :oid, Fixnum, :sql => "integer PRIMARY KEY"
	}
end

#eval_og_read(klass, db) ⇒ Object

Precompile the code to read (deserialize) objects of the given class from the backend. In order to allow for changing field/attribute orders we have to use a field mapping hash.



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/og/adapter.rb', line 329

def eval_og_read(klass, db)
	calc_field_index(klass, db)
	
	props = klass.__props 
	code = []

	props.each do |p|
		if idx = db.managed_classes[klass].field_index[p.name]
			# more fault tolerant if a new field is added and it 
			# doesnt exist in the database.
			code << "@#{p.name} = #{read_prop(p, idx)}"
		end
	end
	
	klass.class_eval %{
 		def og_read(res, tuple = nil)
			#{code.join('; ')}
		end
	}
end

#eval_og_update(klass, db) ⇒ Object

Precompile the update code for the given class. Ignore the oid when updating!



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/og/adapter.rb', line 287

def eval_og_update(klass, db)
	props = klass.__props.reject { |p| :oid == p.symbol }
	
	updates = props.collect { |p|
		"#{p.name}=#{write_prop(p)}"
	}

	sql = "UPDATE #{klass::DBTABLE} SET #{updates.join(', ')} WHERE oid=#\{@oid\}"

	if klass.instance_methods.include?('og_pre_update')
		pre_cb = 'og_pre_update(conn);'
	else
		pre_cb = ''
	end

	if klass.instance_methods.include?('og_post_update')
		post_cb = 'og_post_update(conn);'
	else
		post_cb = ''
	end

	if klass.instance_methods.include?('og_pre_insert_update')
		pre_cb << 'og_pre_insert_update(conn);'
	end

	if klass.instance_methods.include?('og_post_insert_update')
		post_cb << 'og_post_insert_update(conn);'
	end
	
	klass.class_eval %{
		def og_update(conn)
			#{pre_cb}
			conn.exec "#{sql}"
			#{post_cb}
		end
	}
end

#insert_code(klass, sql, pre_cb, post_cb) ⇒ Object

Returns the code that actually inserts the object into the database. Returns the code as String.



230
231
232
# File 'lib/og/adapter.rb', line 230

def insert_code(klass, sql, pre_cb, post_cb)
	raise 'Not implemented!'
end

#new_connection(db) ⇒ Object

Create a new connection to the backend.



106
107
108
# File 'lib/og/adapter.rb', line 106

def new_connection(db)
	return Og::Connection.new(db)
end

#props_for_insert(klass) ⇒ Object

Returns the props that will be included in the insert query. For some backends the oid should be stripped.



223
224
225
# File 'lib/og/adapter.rb', line 223

def props_for_insert(klass)
	klass.__props
end

#read_prop(p, idx) ⇒ Object

Return an evaluator for reading the property. No need to optimize this, used only to precalculate code.



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/og/adapter.rb', line 164

def read_prop(p, idx)
	if p.klass.ancestors.include?(Integer)
		return "res[#{idx}].to_i"
	elsif p.klass.ancestors.include?(Float)
		return "res[#{idx}].to_f"
	elsif p.klass.ancestors.include?(String)
		return "res[#{idx}]"
	elsif p.klass.ancestors.include?(Time)
		return "#{self.class}.parse_timestamp(res[#{idx}])"
	elsif p.klass.ancestors.include?(Date)
		return "#{self.class}.parse_date(res[#{idx}])"
	elsif p.klass.ancestors.include?(TrueClass)
		return "('0' != res[#{idx}])"
	else 
		return "YAML::load(res[#{idx}])"
	end				
end

#write_prop(p) ⇒ Object

Return an sql string evaluator for the property. No need to optimize this, used only to precalculate code. YAML is used to store general Ruby objects to be more portable.

FIXME: add extra handling for float.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/og/adapter.rb', line 143

def write_prop(p)
	if p.klass.ancestors.include?(Integer)
		return "#\{@#{p.symbol} || 'NULL'\}"
	elsif p.klass.ancestors.include?(Float)
		return "#\{@#{p.symbol} || 'NULL'\}"		
	elsif p.klass.ancestors.include?(String)
		return "'#\{#{self.class}.escape(@#{p.symbol})\}'"
	elsif p.klass.ancestors.include?(Time)
		return %|#\{@#{p.symbol} ? "'#\{#{self.class}.timestamp(@#{p.symbol})\}'" : 'NULL'\}|
	elsif p.klass.ancestors.include?(Date)
		return %|#\{@#{p.symbol} ? "'#\{#{self.class}.date(@#{p.symbol})\}'" : 'NULL'\}|
	elsif p.klass.ancestors.include?(TrueClass)
		return "#\{@#{p.symbol} ? \"'t'\" : 'NULL' \}"
	else 
		return %|#\{@#{p.symbol} ? "'#\{#{self.class}.escape(@#{p.symbol}.to_yaml)\}'" : "''"\}|
	end
end