momomoto 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|