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.
Files changed (65) hide show
  1. data/LICENSE +340 -0
  2. data/Rakefile +38 -0
  3. data/lib/momomoto.rb +5 -0
  4. data/lib/momomoto/base.rb +162 -0
  5. data/lib/momomoto/database.rb +179 -0
  6. data/lib/momomoto/datatype.rb +20 -0
  7. data/lib/momomoto/datatype/base.rb +78 -0
  8. data/lib/momomoto/datatype/bigint.rb +9 -0
  9. data/lib/momomoto/datatype/boolean.rb +23 -0
  10. data/lib/momomoto/datatype/bytea.rb +13 -0
  11. data/lib/momomoto/datatype/character.rb +7 -0
  12. data/lib/momomoto/datatype/character_varying.rb +7 -0
  13. data/lib/momomoto/datatype/date.rb +22 -0
  14. data/lib/momomoto/datatype/inet.rb +14 -0
  15. data/lib/momomoto/datatype/integer.rb +16 -0
  16. data/lib/momomoto/datatype/interval.rb +30 -0
  17. data/lib/momomoto/datatype/numeric.rb +17 -0
  18. data/lib/momomoto/datatype/real.rb +7 -0
  19. data/lib/momomoto/datatype/smallint.rb +7 -0
  20. data/lib/momomoto/datatype/text.rb +24 -0
  21. data/lib/momomoto/datatype/time_with_time_zone.rb +10 -0
  22. data/lib/momomoto/datatype/time_without_time_zone.rb +30 -0
  23. data/lib/momomoto/datatype/timestamp_with_time_zone.rb +7 -0
  24. data/lib/momomoto/datatype/timestamp_without_time_zone.rb +29 -0
  25. data/lib/momomoto/information_schema/columns.rb +45 -0
  26. data/lib/momomoto/information_schema/fetch_procedure_columns.rb +14 -0
  27. data/lib/momomoto/information_schema/fetch_procedure_parameters.rb +14 -0
  28. data/lib/momomoto/information_schema/key_column_usage.rb +19 -0
  29. data/lib/momomoto/information_schema/routines.rb +12 -0
  30. data/lib/momomoto/information_schema/table_constraints.rb +19 -0
  31. data/lib/momomoto/join.rb +66 -0
  32. data/lib/momomoto/momomoto.rb +10 -0
  33. data/lib/momomoto/order.rb +56 -0
  34. data/lib/momomoto/procedure.rb +129 -0
  35. data/lib/momomoto/row.rb +63 -0
  36. data/lib/momomoto/table.rb +251 -0
  37. data/sql/install.sql +10 -0
  38. data/sql/procedures.sql +54 -0
  39. data/sql/types.sql +11 -0
  40. data/test/test_base.rb +17 -0
  41. data/test/test_bigint.rb +26 -0
  42. data/test/test_boolean.rb +30 -0
  43. data/test/test_bytea.rb +35 -0
  44. data/test/test_character.rb +27 -0
  45. data/test/test_character_varying.rb +17 -0
  46. data/test/test_database.rb +63 -0
  47. data/test/test_datatype.rb +62 -0
  48. data/test/test_date.rb +50 -0
  49. data/test/test_inet.rb +27 -0
  50. data/test/test_information_schema.rb +27 -0
  51. data/test/test_integer.rb +37 -0
  52. data/test/test_interval.rb +38 -0
  53. data/test/test_join.rb +19 -0
  54. data/test/test_numeric.rb +30 -0
  55. data/test/test_procedure.rb +75 -0
  56. data/test/test_real.rb +17 -0
  57. data/test/test_row.rb +47 -0
  58. data/test/test_smallint.rb +26 -0
  59. data/test/test_table.rb +233 -0
  60. data/test/test_text.rb +25 -0
  61. data/test/test_time_with_time_zone.rb +17 -0
  62. data/test/test_time_without_time_zone.rb +40 -0
  63. data/test/test_timestamp_with_time_zone.rb +17 -0
  64. data/test/test_timestamp_without_time_zone.rb +28 -0
  65. 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,9 @@
1
+ module Momomoto
2
+ module Datatype
3
+
4
+ class Bigint < Integer
5
+
6
+ end
7
+
8
+ end
9
+ end
@@ -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,13 @@
1
+
2
+ module Momomoto
3
+ module Datatype
4
+ class Bytea < Base
5
+
6
+ def escape( input )
7
+ input.nil? ? "NULL" : "'" + Database.escape_bytea( input ) + "'"
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,7 @@
1
+ module Momomoto
2
+ module Datatype
3
+ class Character < Text
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Momomoto
2
+ module Datatype
3
+ class Character_varying < Text
4
+
5
+ end
6
+ end
7
+ 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,14 @@
1
+ module Momomoto
2
+ module Datatype
3
+ class Inet < Text
4
+
5
+ def filter_set( value )
6
+ case value
7
+ when nil, '' then nil
8
+ else value
9
+ end
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ module Momomoto
2
+ module Datatype
3
+ class Integer < Base
4
+
5
+ def filter_set( value )
6
+ case value
7
+ when nil, '' then nil
8
+ else Integer( value )
9
+ end
10
+ rescue => e
11
+ raise ConversionError, e.to_s
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -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
@@ -0,0 +1,17 @@
1
+
2
+ module Momomoto
3
+ module Datatype
4
+ class Numeric < Base
5
+
6
+ def filter_set( value )
7
+ case value
8
+ when nil, '' then nil
9
+ else Float( value )
10
+ end
11
+ rescue => e
12
+ raise ConversionError, e.to_s
13
+ end
14
+
15
+ end
16
+ end
17
+ end