momomoto 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/LICENSE +340 -0
- data/Rakefile +38 -0
- data/lib/momomoto.rb +5 -0
- data/lib/momomoto/base.rb +162 -0
- data/lib/momomoto/database.rb +179 -0
- data/lib/momomoto/datatype.rb +20 -0
- data/lib/momomoto/datatype/base.rb +78 -0
- data/lib/momomoto/datatype/bigint.rb +9 -0
- data/lib/momomoto/datatype/boolean.rb +23 -0
- data/lib/momomoto/datatype/bytea.rb +13 -0
- data/lib/momomoto/datatype/character.rb +7 -0
- data/lib/momomoto/datatype/character_varying.rb +7 -0
- data/lib/momomoto/datatype/date.rb +22 -0
- data/lib/momomoto/datatype/inet.rb +14 -0
- data/lib/momomoto/datatype/integer.rb +16 -0
- data/lib/momomoto/datatype/interval.rb +30 -0
- data/lib/momomoto/datatype/numeric.rb +17 -0
- data/lib/momomoto/datatype/real.rb +7 -0
- data/lib/momomoto/datatype/smallint.rb +7 -0
- data/lib/momomoto/datatype/text.rb +24 -0
- data/lib/momomoto/datatype/time_with_time_zone.rb +10 -0
- data/lib/momomoto/datatype/time_without_time_zone.rb +30 -0
- data/lib/momomoto/datatype/timestamp_with_time_zone.rb +7 -0
- data/lib/momomoto/datatype/timestamp_without_time_zone.rb +29 -0
- data/lib/momomoto/information_schema/columns.rb +45 -0
- data/lib/momomoto/information_schema/fetch_procedure_columns.rb +14 -0
- data/lib/momomoto/information_schema/fetch_procedure_parameters.rb +14 -0
- data/lib/momomoto/information_schema/key_column_usage.rb +19 -0
- data/lib/momomoto/information_schema/routines.rb +12 -0
- data/lib/momomoto/information_schema/table_constraints.rb +19 -0
- data/lib/momomoto/join.rb +66 -0
- data/lib/momomoto/momomoto.rb +10 -0
- data/lib/momomoto/order.rb +56 -0
- data/lib/momomoto/procedure.rb +129 -0
- data/lib/momomoto/row.rb +63 -0
- data/lib/momomoto/table.rb +251 -0
- data/sql/install.sql +10 -0
- data/sql/procedures.sql +54 -0
- data/sql/types.sql +11 -0
- data/test/test_base.rb +17 -0
- data/test/test_bigint.rb +26 -0
- data/test/test_boolean.rb +30 -0
- data/test/test_bytea.rb +35 -0
- data/test/test_character.rb +27 -0
- data/test/test_character_varying.rb +17 -0
- data/test/test_database.rb +63 -0
- data/test/test_datatype.rb +62 -0
- data/test/test_date.rb +50 -0
- data/test/test_inet.rb +27 -0
- data/test/test_information_schema.rb +27 -0
- data/test/test_integer.rb +37 -0
- data/test/test_interval.rb +38 -0
- data/test/test_join.rb +19 -0
- data/test/test_numeric.rb +30 -0
- data/test/test_procedure.rb +75 -0
- data/test/test_real.rb +17 -0
- data/test/test_row.rb +47 -0
- data/test/test_smallint.rb +26 -0
- data/test/test_table.rb +233 -0
- data/test/test_text.rb +25 -0
- data/test/test_time_with_time_zone.rb +17 -0
- data/test/test_time_without_time_zone.rb +40 -0
- data/test/test_timestamp_with_time_zone.rb +17 -0
- data/test/test_timestamp_without_time_zone.rb +28 -0
- metadata +116 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'rubygems'
|
4
|
+
require_gem 'ruby-postgres', '>= 0.7.1.2006.04.06'
|
5
|
+
rescue LoadError
|
6
|
+
require 'postgres'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'singleton'
|
10
|
+
|
11
|
+
require 'momomoto/information_schema/columns'
|
12
|
+
require 'momomoto/information_schema/table_constraints'
|
13
|
+
require 'momomoto/information_schema/key_column_usage'
|
14
|
+
require 'momomoto/information_schema/routines'
|
15
|
+
require 'momomoto/information_schema/fetch_procedure_columns'
|
16
|
+
require 'momomoto/information_schema/fetch_procedure_parameters'
|
17
|
+
|
18
|
+
## Momomoto is a database abstraction layer
|
19
|
+
module Momomoto
|
20
|
+
|
21
|
+
## Momomoto Connection class
|
22
|
+
class Database
|
23
|
+
include Singleton
|
24
|
+
|
25
|
+
# establish connection to the database
|
26
|
+
# expects a hash with the following keys: host, port, database,
|
27
|
+
# username, password, pgoptions and pgtty
|
28
|
+
def config( config )
|
29
|
+
config ||= {}
|
30
|
+
# we also accept String keys in the config hash
|
31
|
+
config.each do | key, value |
|
32
|
+
config[key.to_sym] = value unless key.kind_of?( Symbol )
|
33
|
+
config[key.to_sym] = value.to_s if value.kind_of?(Symbol)
|
34
|
+
end
|
35
|
+
@config = config
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.config( conf )
|
39
|
+
instance.config( conf )
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize # :nodoc:
|
43
|
+
@config = {}
|
44
|
+
@connection = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def connect
|
48
|
+
@connection.close if @connection
|
49
|
+
@transaction_active = false
|
50
|
+
PGconn.translate_results = true
|
51
|
+
@connection = PGconn.connect( @config[:host], @config[:port], @config[:pgoptions],
|
52
|
+
@config[:pgtty], @config[:database], @config[:username],
|
53
|
+
@config[:password])
|
54
|
+
rescue => e
|
55
|
+
raise CriticalError, "Connection to database failed: #{e}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.connect
|
59
|
+
instance.connect
|
60
|
+
end
|
61
|
+
|
62
|
+
# terminate this connection
|
63
|
+
def disconnect
|
64
|
+
@connection.close
|
65
|
+
@connection = nil
|
66
|
+
@transaction_active = true
|
67
|
+
end
|
68
|
+
|
69
|
+
# execute a query
|
70
|
+
def execute( sql ) # :nodoc:
|
71
|
+
puts sql if Momomoto.debug
|
72
|
+
@connection.query( sql )
|
73
|
+
rescue => e
|
74
|
+
raise CriticalError, "#{e}: #{sql}"
|
75
|
+
end
|
76
|
+
|
77
|
+
# fetch columns which are primary key columns
|
78
|
+
# should work with any SQL2003 compliant DBMS
|
79
|
+
def fetch_primary_keys( table_name, schema_name = nil ) # :nodoc:
|
80
|
+
pkeys = []
|
81
|
+
conditions = {:table_name=>table_name, :constraint_type => 'PRIMARY KEY'}
|
82
|
+
conditions[:table_schema] = schema_name if schema_name
|
83
|
+
keys = Momomoto::Information_schema::Table_constraints.select( conditions )
|
84
|
+
if keys.length != 0
|
85
|
+
cols = Momomoto::Information_schema::Key_column_usage.select(
|
86
|
+
{ :table_name => keys[0].table_name,
|
87
|
+
:table_schema => keys[0].table_schema,
|
88
|
+
:constraint_name => keys[0].constraint_name,
|
89
|
+
:constraint_schema => keys[0].constraint_schema } )
|
90
|
+
cols.each do | key |
|
91
|
+
pkeys << key.column_name.to_sym
|
92
|
+
end
|
93
|
+
end
|
94
|
+
pkeys
|
95
|
+
end
|
96
|
+
|
97
|
+
# fetch column definitions from database
|
98
|
+
# should work with any SQL2003 compliant DBMS
|
99
|
+
def fetch_table_columns( table_name, schema_name = nil ) # :nodoc:
|
100
|
+
columns = {}
|
101
|
+
conditions = { :table_name => table_name }
|
102
|
+
conditions[:table_schema] = schema_name if schema_name
|
103
|
+
cols = Momomoto::Information_schema::Columns.select( conditions )
|
104
|
+
raise CriticalError, "Table without columns" if cols.length < 1
|
105
|
+
cols.each do | col |
|
106
|
+
columns[col.column_name.to_sym] = Momomoto::Datatype.const_get(col.data_type.gsub(' ','_').capitalize).new( col )
|
107
|
+
end
|
108
|
+
columns
|
109
|
+
end
|
110
|
+
|
111
|
+
# fetches the parameter of a stored procedure
|
112
|
+
def fetch_procedure_parameters( procedure_name, schema_name = nil ) # :nodoc:
|
113
|
+
p = []
|
114
|
+
conditions = { :procedure_name => procedure_name }
|
115
|
+
params = Momomoto::Information_schema::Fetch_procedure_parameters.call( conditions )
|
116
|
+
params.each do | param |
|
117
|
+
p << { param.parameter_name.to_sym => Momomoto::Datatype.const_get(param.data_type.gsub(' ','_').capitalize).new }
|
118
|
+
end
|
119
|
+
p
|
120
|
+
end
|
121
|
+
|
122
|
+
# fetches the resultset columns of a stored procedure
|
123
|
+
def fetch_procedure_columns( procedure_name, schema_name = nil ) # :nodoc:
|
124
|
+
c = {}
|
125
|
+
conditions = { :procedure_name => procedure_name }
|
126
|
+
cols = Momomoto::Information_schema::Fetch_procedure_columns.call( conditions )
|
127
|
+
cols.each do | col |
|
128
|
+
c[col.column_name.to_sym] = Momomoto::Datatype.const_get(col.data_type.gsub(' ','_').capitalize).new
|
129
|
+
end
|
130
|
+
c
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
# begin a transaction
|
135
|
+
def begin
|
136
|
+
execute( "BEGIN;" )
|
137
|
+
@transaction_active = true
|
138
|
+
end
|
139
|
+
|
140
|
+
# executes the block and commits the transaction if a block is given
|
141
|
+
# otherwise simply starts a new transaction
|
142
|
+
def transaction
|
143
|
+
raise Error, "Transaction active" if @transaction_active
|
144
|
+
self.begin
|
145
|
+
begin
|
146
|
+
yield
|
147
|
+
rescue => e
|
148
|
+
rollback
|
149
|
+
raise e
|
150
|
+
end
|
151
|
+
commit
|
152
|
+
end
|
153
|
+
|
154
|
+
# commit the current transaction
|
155
|
+
def commit
|
156
|
+
raise Error if not @transaction_active
|
157
|
+
execute( "COMMIT;" )
|
158
|
+
@transaction_active = false
|
159
|
+
end
|
160
|
+
|
161
|
+
# roll the transaction back
|
162
|
+
def rollback
|
163
|
+
raise Error if not @transaction_active
|
164
|
+
execute( "ROLLBACK;" )
|
165
|
+
@transaction_active = false
|
166
|
+
end
|
167
|
+
|
168
|
+
def self.escape_string( input )
|
169
|
+
PGconn.escape( input )
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.escape_bytea( input )
|
173
|
+
PGconn.escape_bytea( input )
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
require 'momomoto/datatype/base'
|
3
|
+
require 'momomoto/datatype/boolean'
|
4
|
+
require 'momomoto/datatype/integer'
|
5
|
+
require 'momomoto/datatype/smallint'
|
6
|
+
require 'momomoto/datatype/bigint'
|
7
|
+
require 'momomoto/datatype/text'
|
8
|
+
require 'momomoto/datatype/character'
|
9
|
+
require 'momomoto/datatype/character_varying'
|
10
|
+
require 'momomoto/datatype/numeric'
|
11
|
+
require 'momomoto/datatype/real'
|
12
|
+
require 'momomoto/datatype/bytea'
|
13
|
+
require 'momomoto/datatype/date'
|
14
|
+
require 'momomoto/datatype/time_without_time_zone'
|
15
|
+
require 'momomoto/datatype/interval'
|
16
|
+
require 'momomoto/datatype/time_with_time_zone'
|
17
|
+
require 'momomoto/datatype/timestamp_without_time_zone'
|
18
|
+
require 'momomoto/datatype/timestamp_with_time_zone'
|
19
|
+
require 'momomoto/datatype/inet'
|
20
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
module Momomoto
|
3
|
+
|
4
|
+
module Datatype
|
5
|
+
# base class for all datatypes
|
6
|
+
class Base
|
7
|
+
# get the default value for this column
|
8
|
+
# returns false if none exists
|
9
|
+
def default
|
10
|
+
@default
|
11
|
+
end
|
12
|
+
|
13
|
+
# is this column a not null column
|
14
|
+
def not_null?
|
15
|
+
@not_null
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize( row = nil )
|
19
|
+
@not_null = row.respond_to?(:is_nullable) && row.is_nullable == "NO"
|
20
|
+
@default = row.respond_to?( :column_default) && row.column_default
|
21
|
+
end
|
22
|
+
|
23
|
+
# values are filtered by this function when being set
|
24
|
+
def filter_set( value ) # :nodoc:
|
25
|
+
value
|
26
|
+
end
|
27
|
+
|
28
|
+
def escape( input )
|
29
|
+
input.nil? ? "NULL" : "'" + Database.escape_string( input.to_s ) + "'"
|
30
|
+
end
|
31
|
+
|
32
|
+
# this functions is used for compiling the where clause
|
33
|
+
def compile_rule( field_name, value ) # :nodoc:
|
34
|
+
case value
|
35
|
+
when nil then
|
36
|
+
raise Error, "nil values not allowed for #{field_name}"
|
37
|
+
when Array then
|
38
|
+
raise Error, "empty array conditions are not allowed for #{field_name}" if value.empty?
|
39
|
+
raise Error, "nil values not allowed in compile_rule for #{field_name}" if value.member?( nil )
|
40
|
+
field_name.to_s + ' IN (' + value.map{ | v | escape(filter_set(v)) }.join(',') + ')'
|
41
|
+
when Hash then
|
42
|
+
raise Error, "empty hash conditions are not allowed for #{field_name}" if value.empty?
|
43
|
+
rules = []
|
44
|
+
value.each do | op, v |
|
45
|
+
raise Error, "nil values not allowed in compile_rule for #{field_name}" if v == nil
|
46
|
+
v = [v] if not v.kind_of?( Array )
|
47
|
+
if op == :eq # use IN if comparing for equality
|
48
|
+
rules << compile_rule( field_name, v )
|
49
|
+
else
|
50
|
+
v.each do | v2 |
|
51
|
+
rules << field_name.to_s + ' ' + self.class.operator_sign(op) + ' ' + escape(filter_set(v2))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
rules.join( " AND " )
|
56
|
+
else
|
57
|
+
field_name.to_s + ' = ' + escape(filter_set(value))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.operator_sign( op )
|
62
|
+
case op
|
63
|
+
when :le then '<='
|
64
|
+
when :lt then '<'
|
65
|
+
when :ge then '>='
|
66
|
+
when :gt then '>'
|
67
|
+
when :eq then '='
|
68
|
+
when :ne then '<>'
|
69
|
+
else
|
70
|
+
raise CriticalError, "unsupported operator"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Momomoto
|
2
|
+
module Datatype
|
3
|
+
class Boolean < Base
|
4
|
+
|
5
|
+
def filter_set( value )
|
6
|
+
case value
|
7
|
+
when true, 1, 't', 'true', 'on' then true
|
8
|
+
when false, 0, 'f', 'false', 'off' then false
|
9
|
+
else not_null? ? false : nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def escape( input )
|
14
|
+
case input
|
15
|
+
when true, 1, 't', 'true', 'on' then "'t'"
|
16
|
+
when false, 0, 'f', 'false', 'off' then "'f'"
|
17
|
+
else "NULL"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module Momomoto
|
5
|
+
module Datatype
|
6
|
+
class Date < Base
|
7
|
+
|
8
|
+
def filter_set( value )
|
9
|
+
case value
|
10
|
+
when nil,'' then nil
|
11
|
+
when ::Date then value
|
12
|
+
when String then ::Date.parse( value, '%Y-%m-%d' )
|
13
|
+
else raise Error
|
14
|
+
end
|
15
|
+
rescue => e
|
16
|
+
raise ConversionError, "parse Error in Date #{e}"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Momomoto
|
2
|
+
module Datatype
|
3
|
+
class Interval < Time_without_time_zone
|
4
|
+
|
5
|
+
def escape( value )
|
6
|
+
case value
|
7
|
+
when nil then 'NULL'
|
8
|
+
when String then "'#{Database.escape_string(value)}'"
|
9
|
+
else "'#{Database.escape_string(value.strftime('%H:%M:%S'))}'"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def filter_get( value )
|
14
|
+
case value
|
15
|
+
when nil, '' then nil
|
16
|
+
when ::Time then value
|
17
|
+
when String then ::Time.parse( value )
|
18
|
+
else raise Error
|
19
|
+
end
|
20
|
+
rescue => e
|
21
|
+
raise ConversionError, 'Error while parsing time'
|
22
|
+
end
|
23
|
+
|
24
|
+
def filter_set( value )
|
25
|
+
filter_get( value )
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|