og 0.31.0 → 0.40.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/doc/{AUTHORS → CONTRIBUTORS} +26 -10
- data/doc/LICENSE +2 -3
- data/doc/RELEASES +56 -7
- data/doc/tutorial.txt +15 -15
- data/lib/glue/cacheable.rb +2 -5
- data/lib/glue/hierarchical.rb +1 -4
- data/lib/glue/optimistic_locking.rb +0 -2
- data/lib/glue/orderable.rb +79 -75
- data/lib/glue/revisable.rb +19 -24
- data/lib/glue/searchable.rb +0 -2
- data/lib/glue/taggable.rb +31 -29
- data/lib/glue/timestamped.rb +4 -2
- data/lib/og.rb +50 -29
- data/lib/og/adapter.rb +19 -0
- data/lib/og/adapter/mysql.rb +212 -0
- data/lib/og/adapter/mysql/override.rb +34 -0
- data/lib/og/adapter/mysql/script.rb +15 -0
- data/lib/og/adapter/mysql/utils.rb +40 -0
- data/lib/og/adapter/postgresql.rb +231 -0
- data/lib/og/adapter/postgresql/override.rb +117 -0
- data/lib/og/adapter/postgresql/script.rb +15 -0
- data/lib/og/adapter/postgresql/utils.rb +35 -0
- data/lib/og/adapter/sqlite.rb +132 -0
- data/lib/og/adapter/sqlite/override.rb +33 -0
- data/lib/og/adapter/sqlite/script.rb +15 -0
- data/lib/og/collection.rb +35 -7
- data/lib/og/{evolution.rb → dump.rb} +4 -5
- data/lib/og/entity.rb +102 -173
- data/lib/og/entity/clone.rb +119 -0
- data/lib/og/errors.rb +0 -2
- data/lib/og/manager.rb +85 -37
- data/lib/og/relation.rb +52 -34
- data/lib/og/relation/belongs_to.rb +0 -2
- data/lib/og/relation/has_many.rb +27 -4
- data/lib/og/relation/joins_many.rb +41 -14
- data/lib/og/relation/many_to_many.rb +10 -0
- data/lib/og/relation/refers_to.rb +22 -5
- data/lib/og/store.rb +80 -86
- data/lib/og/store/sql.rb +710 -713
- data/lib/og/store/sql/evolution.rb +119 -0
- data/lib/og/store/sql/join.rb +155 -0
- data/lib/og/store/sql/utils.rb +149 -0
- data/lib/og/test/assertions.rb +1 -3
- data/lib/og/test/testcase.rb +0 -2
- data/lib/og/types.rb +2 -5
- data/lib/og/validation.rb +6 -9
- data/test/{og/mixin → glue}/tc_hierarchical.rb +3 -13
- data/test/glue/tc_og_paginate.rb +47 -0
- data/test/{og/mixin → glue}/tc_optimistic_locking.rb +2 -12
- data/test/{og/mixin → glue}/tc_orderable.rb +15 -23
- data/test/glue/tc_orderable2.rb +47 -0
- data/test/glue/tc_revisable.rb +3 -3
- data/test/{og/mixin → glue}/tc_taggable.rb +20 -10
- data/test/{og/mixin → glue}/tc_timestamped.rb +2 -12
- data/test/glue/tc_webfile.rb +36 -0
- data/test/og/CONFIG.rb +8 -11
- data/test/og/multi_validations_model.rb +14 -0
- data/test/og/store/tc_filesys.rb +3 -1
- data/test/og/store/tc_kirby.rb +16 -13
- data/test/og/store/tc_sti.rb +11 -11
- data/test/og/store/tc_sti2.rb +79 -0
- data/test/og/tc_build.rb +41 -0
- data/test/og/tc_cacheable.rb +3 -2
- data/test/og/tc_has_many.rb +96 -0
- data/test/og/tc_inheritance.rb +6 -4
- data/test/og/tc_joins_many.rb +93 -0
- data/test/og/tc_multi_validations.rb +5 -7
- data/test/og/tc_multiple.rb +7 -6
- data/test/og/tc_override.rb +13 -7
- data/test/og/tc_primary_key.rb +30 -0
- data/test/og/tc_relation.rb +8 -14
- data/test/og/tc_reldelete.rb +163 -0
- data/test/og/tc_reverse.rb +17 -14
- data/test/og/tc_scoped.rb +3 -11
- data/test/og/tc_setup.rb +13 -11
- data/test/og/tc_store.rb +21 -28
- data/test/og/tc_validation2.rb +2 -2
- data/test/og/tc_validation_loop.rb +17 -15
- metadata +109 -103
- data/INSTALL +0 -91
- data/ProjectInfo +0 -51
- data/README +0 -177
- data/doc/config.txt +0 -28
- data/examples/README +0 -23
- data/examples/mysql_to_psql.rb +0 -71
- data/examples/run.rb +0 -271
- data/lib/glue/tree.rb +0 -218
- data/lib/og/store/alpha/filesys.rb +0 -110
- data/lib/og/store/alpha/memory.rb +0 -295
- data/lib/og/store/alpha/sqlserver.rb +0 -256
- data/lib/og/store/kirby.rb +0 -490
- data/lib/og/store/mysql.rb +0 -415
- data/lib/og/store/psql.rb +0 -875
- data/lib/og/store/sqlite.rb +0 -348
- data/lib/og/store/sqlite2.rb +0 -241
- data/setup.rb +0 -1585
- data/test/og/tc_sti_find.rb +0 -35
@@ -0,0 +1,119 @@
|
|
1
|
+
module Og
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Implement Og's automatic schema evolution features.
|
5
|
+
# Add schema evolution related methods to SqlStore.
|
6
|
+
#++
|
7
|
+
|
8
|
+
module Evolution
|
9
|
+
|
10
|
+
#--
|
11
|
+
# Override if needed in the actual Adapter implementation.
|
12
|
+
#++
|
13
|
+
|
14
|
+
def add_sql_field(klass, a, anno)
|
15
|
+
Logger.info "Adding field '#{a}' to '#{klass.table}'"
|
16
|
+
query "ALTER TABLE #{klass.table} ADD COLUMN #{field_sql_for_attribute a, anno}"
|
17
|
+
end
|
18
|
+
alias add_sql_column add_sql_field
|
19
|
+
|
20
|
+
#--
|
21
|
+
# Override if needed in the actual Adapter implementation.
|
22
|
+
#++
|
23
|
+
|
24
|
+
def remove_sql_field(klass, a)
|
25
|
+
Logger.info "Removing field '#{a}' from '#{klass.table}'"
|
26
|
+
query "ALTER TABLE #{klass.table} DROP COLUMN #{a}"
|
27
|
+
end
|
28
|
+
alias add_sql_column add_sql_field
|
29
|
+
|
30
|
+
#--
|
31
|
+
# Override if needed in the actual Adapter implementation.
|
32
|
+
#++
|
33
|
+
|
34
|
+
def rename_sql_table(_old, _new)
|
35
|
+
Logger.info "Rename table '#{_old}' to '#{_new}'"
|
36
|
+
query "ALTER TABLE #{_old} RENAME #{_new}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Evolve the schema (table in sql stores) for the given
|
40
|
+
# class. Compares the fields in the database schema with
|
41
|
+
# the serializable attributes of the given class and tries
|
42
|
+
# to fix mismatches by adding are droping columns.
|
43
|
+
#
|
44
|
+
# === Evolution options
|
45
|
+
#
|
46
|
+
# * :evolve_schema => :add (only add, dont remove columns)
|
47
|
+
# * :evolve_schema => :full (add and delete columns)
|
48
|
+
# * :evolve_schema => :warn (only emit warnings, DEFAULT_
|
49
|
+
# * :evolve_schema => false (no evolution)
|
50
|
+
#
|
51
|
+
# === Example
|
52
|
+
#
|
53
|
+
# Og.setup(
|
54
|
+
# ..
|
55
|
+
# :evolve_schema => :full
|
56
|
+
# ..
|
57
|
+
# )
|
58
|
+
|
59
|
+
def evolve_schema(klass)
|
60
|
+
return unless @options[:evolve_schema]
|
61
|
+
|
62
|
+
sql_fields = create_field_map(klass).keys
|
63
|
+
attrs = klass.serializable_attributes
|
64
|
+
|
65
|
+
# Add new fields to the table.
|
66
|
+
|
67
|
+
for field in attrs
|
68
|
+
unless sql_fields.include? field
|
69
|
+
unless @options[:evolve_schema] == :warn
|
70
|
+
add_sql_field klass, field, klass.ann(field)
|
71
|
+
else
|
72
|
+
Logger.warn "Missing field '#{field}' on table '#{klass.table}'!"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Remove obsolete fields from the table.
|
78
|
+
|
79
|
+
for field in sql_fields
|
80
|
+
unless attrs.include? field
|
81
|
+
if @options[:evolve_schema] == :full
|
82
|
+
remove_sql_field klass, field
|
83
|
+
else
|
84
|
+
Logger.warn "Obsolete field '#{field}' found on table '#{klass.table}'!"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Renames the schema (table in sql stores) for the given
|
91
|
+
# class.
|
92
|
+
#
|
93
|
+
# === Input
|
94
|
+
#
|
95
|
+
# * new_schema = the new schema (Class or table name)
|
96
|
+
# * old_schema = the old schema (Class or table name)
|
97
|
+
#
|
98
|
+
# === Example
|
99
|
+
#
|
100
|
+
# store.rename_schema(TicketArticle, Ticket::Article)
|
101
|
+
|
102
|
+
def rename_schema(old_schema, new_schema)
|
103
|
+
if old_schema.is_a? Class
|
104
|
+
old_schema = table(old_schema)
|
105
|
+
end
|
106
|
+
|
107
|
+
if new_schema.is_a? Class
|
108
|
+
new_schema = table(new_schema)
|
109
|
+
end
|
110
|
+
|
111
|
+
rename_sql_table(old_schema, new_schema)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
SqlStore.send :include, Evolution
|
117
|
+
|
118
|
+
end
|
119
|
+
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module Og
|
2
|
+
|
3
|
+
# Add join related utility methods in SqlUtils.
|
4
|
+
|
5
|
+
module SqlUtils
|
6
|
+
|
7
|
+
def join_object_ordering(obj1, obj2)
|
8
|
+
if obj1.class.to_s <= obj2.class.to_s
|
9
|
+
return obj1, obj2
|
10
|
+
else
|
11
|
+
return obj2, obj1, true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def join_class_ordering(class1, class2)
|
16
|
+
if class1.to_s <= class2.to_s
|
17
|
+
return class1, class2
|
18
|
+
else
|
19
|
+
return class2, class1, true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_join_name(class1, class2, postfix = nil)
|
24
|
+
# Don't reorder arguments, as this is used in places that
|
25
|
+
# have already determined the order they want.
|
26
|
+
"#{Og.table_prefix}j_#{tableize(class1)}_#{tableize(class2)}#{postfix}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def join_table(class1, class2, postfix = nil)
|
30
|
+
first, second = join_class_ordering(class1, class2)
|
31
|
+
build_join_name(first, second, postfix)
|
32
|
+
end
|
33
|
+
|
34
|
+
def join_table_index(key)
|
35
|
+
"#{key}_idx"
|
36
|
+
end
|
37
|
+
|
38
|
+
def join_table_key(klass)
|
39
|
+
klass = klass.schema_inheritance_root_class if klass.schema_inheritance_child?
|
40
|
+
"#{klass.to_s.demodulize.underscore.downcase}_oid"
|
41
|
+
end
|
42
|
+
|
43
|
+
def join_table_keys(class1, class2)
|
44
|
+
if class1 == class2
|
45
|
+
# Fix for the self-join case.
|
46
|
+
return join_table_key(class1), "#{join_table_key(class2)}2"
|
47
|
+
else
|
48
|
+
return join_table_key(class1), join_table_key(class2)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def ordered_join_table_keys(class1, class2)
|
53
|
+
first, second = join_class_ordering(class1, class2)
|
54
|
+
return join_table_keys(first, second)
|
55
|
+
end
|
56
|
+
|
57
|
+
def join_table_info(relation, postfix = nil)
|
58
|
+
|
59
|
+
# some fixes for schema inheritance.
|
60
|
+
|
61
|
+
owner_class, target_class = relation.owner_class, relation.target_class
|
62
|
+
|
63
|
+
raise "Undefined owner_class in #{target_class}" unless owner_class
|
64
|
+
raise "Undefined target_class in #{owner_class}" unless target_class
|
65
|
+
|
66
|
+
owner_class = owner_class.schema_inheritance_root_class if owner_class.schema_inheritance_child?
|
67
|
+
target_class = target_class.schema_inheritance_root_class if target_class.schema_inheritance_child?
|
68
|
+
|
69
|
+
owner_key, target_key = join_table_keys(owner_class, target_class)
|
70
|
+
first, second, changed = join_class_ordering(owner_class, target_class)
|
71
|
+
|
72
|
+
if changed
|
73
|
+
first_key, second_key = target_key, owner_key
|
74
|
+
else
|
75
|
+
first_key, second_key = owner_key, target_key
|
76
|
+
end
|
77
|
+
|
78
|
+
table = (relation.table ?
|
79
|
+
relation.table :
|
80
|
+
join_table(owner_class, target_class, postfix)
|
81
|
+
)
|
82
|
+
|
83
|
+
return {
|
84
|
+
:table => table,
|
85
|
+
:owner_key => owner_key,
|
86
|
+
:owner_table => table(owner_class),
|
87
|
+
:target_key => target_key,
|
88
|
+
:target_table => table(target_class),
|
89
|
+
:first_table => table(first),
|
90
|
+
:first_key => first_key,
|
91
|
+
:first_index => join_table_index(first_key),
|
92
|
+
:second_table => table(second),
|
93
|
+
:second_key => second_key,
|
94
|
+
:second_index => join_table_index(second_key)
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
# Subclasses can override this if they need a different
|
99
|
+
# syntax.
|
100
|
+
|
101
|
+
def create_join_table_sql(join_table_info, suffix = 'NOT NULL', key_type = 'integer')
|
102
|
+
join_table = join_table_info[:table]
|
103
|
+
first_index = join_table_info[:first_index]
|
104
|
+
first_key = join_table_info[:first_key]
|
105
|
+
second_key = join_table_info[:second_key]
|
106
|
+
second_index = join_table_info[:second_index]
|
107
|
+
|
108
|
+
sql = []
|
109
|
+
|
110
|
+
sql << %{
|
111
|
+
CREATE TABLE #{join_table} (
|
112
|
+
#{first_key} integer NOT NULL,
|
113
|
+
#{second_key} integer NOT NULL,
|
114
|
+
PRIMARY KEY(#{first_key}, #{second_key})
|
115
|
+
)
|
116
|
+
}
|
117
|
+
|
118
|
+
# gmosx: not that useful?
|
119
|
+
# sql << "CREATE INDEX #{first_index} ON #{join_table} (#{first_key})"
|
120
|
+
# sql << "CREATE INDEX #{second_index} ON #{join_table} (#{second_key})"
|
121
|
+
|
122
|
+
return sql
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
# Extends SqlStore by adding join related methods.
|
128
|
+
|
129
|
+
class SqlStore < Store
|
130
|
+
|
131
|
+
# Relate two objects through an intermediate join table.
|
132
|
+
# Typically used in joins_many and many_to_many relations.
|
133
|
+
|
134
|
+
def join(obj1, obj2, table, options = nil)
|
135
|
+
first, second = join_object_ordering(obj1, obj2)
|
136
|
+
first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
|
137
|
+
if options
|
138
|
+
exec "INSERT INTO #{table} (#{first_key},#{second_key}, #{options.keys.join(',')}) VALUES (#{first.pk},#{second.pk}, #{options.values.map { |v| quote(v) }.join(',')})"
|
139
|
+
else
|
140
|
+
exec "INSERT INTO #{table} (#{first_key},#{second_key}) VALUES (#{first.pk}, #{second.pk})"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Unrelate two objects be removing their relation from the
|
145
|
+
# join table.
|
146
|
+
|
147
|
+
def unjoin(obj1, obj2, table)
|
148
|
+
first, second = join_object_ordering(obj1, obj2)
|
149
|
+
first_key, second_key = ordered_join_table_keys(obj1.class, obj2.class)
|
150
|
+
exec "DELETE FROM #{table} WHERE #{first_key}=#{first.pk} AND #{second_key}=#{second.pk}"
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Og
|
4
|
+
|
5
|
+
# A collection of useful SQL utilities.
|
6
|
+
|
7
|
+
module SqlUtils
|
8
|
+
|
9
|
+
# Escape an SQL string
|
10
|
+
|
11
|
+
def escape(str)
|
12
|
+
return nil unless str
|
13
|
+
return str.gsub(/'/, "''")
|
14
|
+
end
|
15
|
+
|
16
|
+
# Convert a ruby time to an sql timestamp.
|
17
|
+
#--
|
18
|
+
# TODO: Optimize this.
|
19
|
+
#++
|
20
|
+
|
21
|
+
def timestamp(time = Time.now)
|
22
|
+
return nil unless time
|
23
|
+
return time.strftime("%Y-%m-%d %H:%M:%S")
|
24
|
+
end
|
25
|
+
|
26
|
+
# Output YYY-mm-dd
|
27
|
+
#--
|
28
|
+
# TODO: Optimize this.
|
29
|
+
#++
|
30
|
+
|
31
|
+
def date(date)
|
32
|
+
return nil unless date
|
33
|
+
return "#{date.year}-#{date.month}-#{date.mday}"
|
34
|
+
end
|
35
|
+
|
36
|
+
#--
|
37
|
+
# TODO: implement me!
|
38
|
+
#++
|
39
|
+
|
40
|
+
def blob(val)
|
41
|
+
val
|
42
|
+
end
|
43
|
+
|
44
|
+
# Parse an integer.
|
45
|
+
|
46
|
+
def parse_int(int)
|
47
|
+
int = int.to_i if int
|
48
|
+
return int
|
49
|
+
end
|
50
|
+
|
51
|
+
# Parse a float.
|
52
|
+
|
53
|
+
def parse_float(fl)
|
54
|
+
fl = fl.to_f if fl
|
55
|
+
fl
|
56
|
+
end
|
57
|
+
|
58
|
+
# Parse sql datetime
|
59
|
+
#--
|
60
|
+
# TODO: Optimize this.
|
61
|
+
#++
|
62
|
+
|
63
|
+
def parse_timestamp(str)
|
64
|
+
return nil unless str
|
65
|
+
return Time.parse(str)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Input YYYY-mm-dd
|
69
|
+
#--
|
70
|
+
# TODO: Optimize this.
|
71
|
+
#++
|
72
|
+
|
73
|
+
def parse_date(str)
|
74
|
+
return nil unless str
|
75
|
+
return Date.strptime(str)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Parse a boolean
|
79
|
+
# true, 1, t => true
|
80
|
+
# other => false
|
81
|
+
|
82
|
+
def parse_boolean(str)
|
83
|
+
return true if (str=='true' || str=='t' || str=='1')
|
84
|
+
return false
|
85
|
+
end
|
86
|
+
|
87
|
+
#--
|
88
|
+
# TODO: implement me!!
|
89
|
+
#++
|
90
|
+
|
91
|
+
def parse_blob(val)
|
92
|
+
val
|
93
|
+
end
|
94
|
+
|
95
|
+
# Escape the various Ruby types.
|
96
|
+
|
97
|
+
def quote(vals)
|
98
|
+
vals = [vals] unless vals.is_a?(Array)
|
99
|
+
quoted = vals.inject('') do |s,val|
|
100
|
+
s += case val
|
101
|
+
when Fixnum, Integer, Float
|
102
|
+
val ? val.to_s : 'NULL'
|
103
|
+
when String
|
104
|
+
val ? "'#{escape(val)}'" : 'NULL'
|
105
|
+
when Time
|
106
|
+
val ? "'#{timestamp(val)}'" : 'NULL'
|
107
|
+
when Date
|
108
|
+
val ? "'#{date(val)}'" : 'NULL'
|
109
|
+
when TrueClass, FalseClass
|
110
|
+
val ? "'t'" : 'NULL'
|
111
|
+
else
|
112
|
+
# gmosx: keep the '' for nil symbols.
|
113
|
+
val ? escape(val.to_yaml) : ''
|
114
|
+
end + ','
|
115
|
+
end
|
116
|
+
quoted.chop!
|
117
|
+
vals.size > 1 ? "(#{quoted})" : quoted
|
118
|
+
end
|
119
|
+
|
120
|
+
# Escape the Array Ruby type.
|
121
|
+
|
122
|
+
def quote_array(val)
|
123
|
+
case val
|
124
|
+
when Array
|
125
|
+
val.collect{ |v| quotea(v) }.join(',')
|
126
|
+
else
|
127
|
+
quote(val)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
alias_method :quotea, :quote_array
|
131
|
+
|
132
|
+
# Apply table name conventions to a class name.
|
133
|
+
|
134
|
+
def tableize(klass)
|
135
|
+
"#{klass.to_s.gsub(/::/, "_").downcase}"
|
136
|
+
end
|
137
|
+
|
138
|
+
# Return the table name for the given class.
|
139
|
+
|
140
|
+
def table(klass)
|
141
|
+
#klass.ann.self[:sql_table] || klass.ann.self[:table] || "#{Og.table_prefix}#{tableize(klass)}"
|
142
|
+
(klass.ann.self[:sql_table] rescue nil) ||
|
143
|
+
(klass.ann.self[:table] rescue nil) ||
|
144
|
+
"#{Og.table_prefix}#{tableize(klass)}"
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
data/lib/og/test/assertions.rb
CHANGED
@@ -141,7 +141,7 @@ module Test::Unit::Assertions
|
|
141
141
|
assert_block(msg) { cookie.value == value }
|
142
142
|
end
|
143
143
|
|
144
|
-
# :section:
|
144
|
+
# :section: Nitro::Template related assertions.
|
145
145
|
|
146
146
|
# :section: Redirection assertions.
|
147
147
|
|
@@ -171,5 +171,3 @@ module Test::Unit::Assertions
|
|
171
171
|
end
|
172
172
|
|
173
173
|
end
|
174
|
-
|
175
|
-
# * George Moschovitis <gm@navel.gr>
|
data/lib/og/test/testcase.rb
CHANGED
data/lib/og/types.rb
CHANGED
@@ -2,11 +2,11 @@ module Og
|
|
2
2
|
|
3
3
|
# Some useful type macros to help when defining properties.
|
4
4
|
# You can easily code your own type macros. Just return the
|
5
|
-
# array that should be passed to the
|
5
|
+
# array that should be passed to the attr_xxx macros.
|
6
6
|
#
|
7
7
|
# === Example
|
8
8
|
#
|
9
|
-
#
|
9
|
+
# attr_accessor :name, VarChar(30)
|
10
10
|
|
11
11
|
def self.VarChar(size)
|
12
12
|
return String, :sql => "VARCHAR(#{size})"
|
@@ -17,6 +17,3 @@ NotNull = { :sql => 'NOT NULL' }.freeze
|
|
17
17
|
Null = { :sql => 'NULL' }.freeze
|
18
18
|
|
19
19
|
end
|
20
|
-
|
21
|
-
# * Michael Neumann <mneumann@ntecs.de>
|
22
|
-
# * George Moschovitis <gm@navel.gr>
|