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,7 @@
1
+ module Momomoto
2
+ module Datatype
3
+ class Real < Numeric
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Momomoto
2
+ module Datatype
3
+ class Smallint < Integer
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ module Momomoto
2
+ module Datatype
3
+ class Text < Base
4
+
5
+ def escape( input )
6
+ if input.nil? || input.empty?
7
+ "NULL"
8
+ else
9
+ "'" + Database.escape_string( input.to_s ) + "'"
10
+ end
11
+ end
12
+
13
+ def self.operator_sign( op )
14
+ case op
15
+ when :like then 'LIKE'
16
+ when :ilike then 'ILIKE'
17
+ else
18
+ super( op )
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+
2
+ require 'date'
3
+
4
+ module Momomoto
5
+ module Datatype
6
+ class Time_with_time_zone < Time_without_time_zone
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,30 @@
1
+
2
+ require 'time'
3
+
4
+ module Momomoto
5
+ module Datatype
6
+ class Time_without_time_zone < Base
7
+
8
+ def escape( value )
9
+ case value
10
+ when nil then 'NULL'
11
+ when String then "'#{Database.escape_string(value)}'"
12
+ else "'#{Database.escape_string(value.strftime('%H:%M:%S'))}'"
13
+ end
14
+ end
15
+
16
+ def filter_set( value )
17
+ case value
18
+ when nil, '' then nil
19
+ when ::Time then value
20
+ when String then ::Time.parse( value )
21
+ else raise Error
22
+ end
23
+ rescue => e
24
+ raise ConversionError, 'Error while parsing time'
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,7 @@
1
+ module Momomoto
2
+ module Datatype
3
+ class Timestamp_with_time_zone < Timestamp_without_time_zone
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+
2
+ require 'date'
3
+
4
+ module Momomoto
5
+ module Datatype
6
+ class Timestamp_without_time_zone < Base
7
+
8
+ def escape( value )
9
+ case value
10
+ when nil then 'NULL'
11
+ when String then "'#{Database.escape_string(value)}'"
12
+ else "'#{Database.escape_string(value.strftime('%Y-%m-%d %H:%M:%S'))}'"
13
+ end
14
+ end
15
+
16
+ def filter_set( value )
17
+ case value
18
+ when nil, '' then nil
19
+ when ::Time then value
20
+ when String then ::Time.parse( value )
21
+ else raise Error
22
+ end
23
+ rescue => e
24
+ raise ConversionError, "Error while parsing Timestamp"
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+
2
+ module Momomoto
3
+ module Information_schema
4
+ class Columns < Momomoto::Table
5
+ primary_keys( [] )
6
+ schema_name( "information_schema" )
7
+ columns( { :table_catalog => Momomoto::Datatype::Text.new,
8
+ :table_schema => Momomoto::Datatype::Text.new,
9
+ :table_name => Momomoto::Datatype::Text.new,
10
+ :column_name => Momomoto::Datatype::Text.new,
11
+ :ordinal_position => Momomoto::Datatype::Integer.new,
12
+ :column_default => Momomoto::Datatype::Text.new,
13
+ :is_nullable => Momomoto::Datatype::Text.new,
14
+ :data_type => Momomoto::Datatype::Text.new,
15
+ :character_maximum_length => Momomoto::Datatype::Integer.new,
16
+ :character_octet_length => Momomoto::Datatype::Integer.new,
17
+ :numeric_precision => Momomoto::Datatype::Integer.new,
18
+ :numeric_precision_radix => Momomoto::Datatype::Integer.new,
19
+ :numeric_scale => Momomoto::Datatype::Integer.new,
20
+ :datetime_precision => Momomoto::Datatype::Integer.new,
21
+ :interval_type => Momomoto::Datatype::Character_varying.new,
22
+ :interval_precision => Momomoto::Datatype::Character_varying.new,
23
+ :character_set_catalog => Momomoto::Datatype::Character_varying.new,
24
+ :character_set_schema => Momomoto::Datatype::Character_varying.new,
25
+ :character_set_name => Momomoto::Datatype::Character_varying.new,
26
+ :collation_catalog => Momomoto::Datatype::Character_varying.new,
27
+ :collation_schema => Momomoto::Datatype::Character_varying.new,
28
+ :collation_name => Momomoto::Datatype::Character_varying.new,
29
+ :domain_catalog => Momomoto::Datatype::Character_varying.new,
30
+ :domain_schema => Momomoto::Datatype::Character_varying.new,
31
+ :domain_name => Momomoto::Datatype::Character_varying.new,
32
+ :udt_catalog => Momomoto::Datatype::Character_varying.new,
33
+ :udt_schema => Momomoto::Datatype::Character_varying.new,
34
+ :udt_name => Momomoto::Datatype::Character_varying.new,
35
+ :scope_catalog => Momomoto::Datatype::Character_varying.new,
36
+ :scope_schema => Momomoto::Datatype::Character_varying.new,
37
+ :scope_name => Momomoto::Datatype::Character_varying.new,
38
+ :maximum_cardinality => Momomoto::Datatype::Integer.new,
39
+ :dtd_identifier => Momomoto::Datatype::Character_varying.new,
40
+ :is_self_referencing => Momomoto::Datatype::Character_varying.new
41
+ } )
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,14 @@
1
+
2
+ module Momomoto
3
+ module Information_schema
4
+ class Fetch_procedure_columns < Momomoto::Procedure
5
+ schema_name( "momomoto" )
6
+ parameters( :procedure_name => Momomoto::Datatype::Text.new )
7
+ columns( {
8
+ :column_name => Momomoto::Datatype::Text.new,
9
+ :data_type => Momomoto::Datatype::Text.new
10
+ } )
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,14 @@
1
+
2
+ module Momomoto
3
+ module Information_schema
4
+ class Fetch_procedure_parameters < Momomoto::Procedure
5
+ schema_name( "momomoto" )
6
+ parameters( :procedure_name => Momomoto::Datatype::Text.new )
7
+ columns( {
8
+ :parameter_name => Momomoto::Datatype::Text.new,
9
+ :data_type => Momomoto::Datatype::Text.new
10
+ } )
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,19 @@
1
+
2
+ module Momomoto
3
+ module Information_schema
4
+ class Key_column_usage < Momomoto::Table
5
+ primary_keys( [] )
6
+ schema_name( "information_schema" )
7
+ columns( { :constraint_catalog => Momomoto::Datatype::Character_varying.new,
8
+ :constraint_schema => Momomoto::Datatype::Character_varying.new,
9
+ :constraint_name => Momomoto::Datatype::Character_varying.new,
10
+ :table_catalog => Momomoto::Datatype::Character_varying.new,
11
+ :table_schema => Momomoto::Datatype::Character_varying.new,
12
+ :table_name => Momomoto::Datatype::Character_varying.new,
13
+ :column_name => Momomoto::Datatype::Character_varying.new,
14
+ :ordinal_position => Momomoto::Datatype::Integer.new
15
+ } )
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,12 @@
1
+
2
+ module Momomoto
3
+
4
+ module Information_schema
5
+
6
+ class Routines < Momomoto::Table
7
+
8
+ end
9
+
10
+ end
11
+
12
+ end
@@ -0,0 +1,19 @@
1
+
2
+ module Momomoto
3
+ module Information_schema
4
+ class Table_constraints < Momomoto::Table
5
+ primary_keys( [] )
6
+ schema_name( "information_schema" )
7
+ columns( { :constraint_catalog => Momomoto::Datatype::Text.new,
8
+ :constraint_schema => Momomoto::Datatype::Text.new,
9
+ :constraint_name => Momomoto::Datatype::Text.new,
10
+ :table_catalog => Momomoto::Datatype::Text.new,
11
+ :table_schema => Momomoto::Datatype::Text.new,
12
+ :table_name => Momomoto::Datatype::Text.new,
13
+ :constraint_type => Momomoto::Datatype::Text.new,
14
+ :is_deferrable => Momomoto::Datatype::Text.new,
15
+ :initially_deferred => Momomoto::Datatype::Text.new
16
+ } )
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,66 @@
1
+
2
+ module Momomoto
3
+
4
+ # this class implements the join functionality it is an abstract class
5
+ # it must not be used directly but you should inherit from this class
6
+ class Join < Base
7
+
8
+ attr_reader :base_table, :join_rules
9
+
10
+ # constructor of the join class
11
+ # base_table is the base table of the join
12
+ # join_rules is an array of Hashes consisting of the table to join as key
13
+ # and an symbol or an array of symbols on which fields to join the table
14
+ # Example: Momomoto::Join.new(Event, {Event_Person=>:event_id},{Person=>:person_id})
15
+ # results in SELECT * FROM event INNER JOIN event_person USING (event_id) INNER JOIN Person USING(person_id)
16
+ # if you leave out the eclipit braces to mark the hash you may get
17
+ # unexpected results because of the undefined order of hashs
18
+ def initialize( base_table, *join_rules )
19
+ @base_table = base_table
20
+ @join_rules = join_rules
21
+
22
+ metaclass.instance_eval do
23
+ define_method( base_table.table_name ) do base_table end
24
+ end
25
+ join_rules.each do | rule |
26
+ rule.keys.each do | table, join_columns |
27
+ metaclass.instance_eval do
28
+ define_method( table.table_name ) do table end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def metaclass # :nodoc:
35
+ class << self; self; end
36
+ end
37
+
38
+ def class_variable_set( variable, value ) # :nodoc:
39
+ self.class.send( :class_variable_set, variable, value )
40
+ end
41
+
42
+ def select( conditions = {}, options = {} )
43
+ sql = "SELECT " + base_table.columns.keys.map{ | field | "#{base_table.table_name}.\"#{field}\"" }.join( "," )
44
+ join_rules.each do | rules |
45
+ rules.each do | table, fields |
46
+ sql += ','
47
+ sql += table.columns.keys.map{ | field | "#{table.table_name}.\"#{field}\"" }.join( ',' )
48
+ end
49
+ end
50
+ sql += " FROM "
51
+ sql += base_table.full_name
52
+ join_rules.each do | rules |
53
+ rules.each do | table_name, fields |
54
+ fields = fields.class === Array ? fields : [fields]
55
+ sql += " INNER JOIN #{table_name} USING(#{fields.join(', ')})"
56
+ end
57
+ end
58
+ sql += self.class.compile_where( conditions )
59
+ @data = []
60
+ self.class.database.execute( sql )
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+
@@ -0,0 +1,10 @@
1
+
2
+ require 'momomoto/base'
3
+ require 'momomoto/table'
4
+ require 'momomoto/join'
5
+ require 'momomoto/procedure'
6
+ require 'momomoto/order'
7
+ require 'momomoto/row'
8
+ require 'momomoto/datatype'
9
+ require 'momomoto/database'
10
+
@@ -0,0 +1,56 @@
1
+
2
+ module Momomoto
3
+
4
+ class Order
5
+
6
+ attr_accessor :fields
7
+
8
+ def initialize( *fields )
9
+ @fields = fields.flatten
10
+ end
11
+
12
+ def to_sql( columns )
13
+ sql = []
14
+ fields.each do | field |
15
+ if field.kind_of?( Momomoto::Order )
16
+ sql << function( field.to_sql( columns ) )
17
+ else
18
+ raise Momomoto::Error, "Unknown field #{field} in order" if not columns.keys.member?( field.to_sym )
19
+ sql << function( field )
20
+ end
21
+ end
22
+ sql.join(',')
23
+ end
24
+
25
+ class Lower < Order
26
+ def initialize( *fields )
27
+ fields = fields.flatten
28
+ fields.each do | field |
29
+ raise Error, "Asc and Desc are only allowed as toplevel order elements" if field.kind_of?( Asc ) or field.kind_of?( Desc )
30
+ end
31
+ @fields = fields
32
+ end
33
+
34
+ def function( argument )
35
+ argument = [ argument ] if not argument.kind_of?( Array )
36
+ argument = argument.map{|a| "lower(#{a})"}
37
+ argument.join(',')
38
+ end
39
+ end
40
+
41
+ class Asc < Order
42
+ def function( argument )
43
+ "#{argument} asc"
44
+ end
45
+ end
46
+
47
+ class Desc < Order
48
+ def function( argument )
49
+ "#{argument} desc"
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
@@ -0,0 +1,129 @@
1
+
2
+ module Momomoto
3
+
4
+ # this class implements access to stored procedures
5
+ # it must not be used directly but you should inherit from this class
6
+ class Procedure < Base
7
+
8
+ class << self
9
+
10
+ def initialize_procedure # :nodoc:
11
+
12
+ unless class_variables.member?( '@@procedure_name' )
13
+ procedure_name( construct_procedure_name( self.name ) )
14
+ end
15
+
16
+ unless class_variables.member?( '@@schema_name' )
17
+ schema_name( construct_schema_name( self.name ) )
18
+ end
19
+
20
+ unless class_variables.member?( '@@parameters' )
21
+ parameters( database.fetch_procedure_parameters( procedure_name ) )
22
+ raise CriticalError if not parameters
23
+ end
24
+
25
+ unless class_variables.member?( '@@columns' )
26
+ columns( database.fetch_procedure_columns( procedure_name ) )
27
+ raise CriticalError if not columns
28
+ end
29
+
30
+ const_set( :Row, Class.new( Momomoto::Row ) ) if not const_defined?( :Row )
31
+ initialize_row( const_get( :Row ), self )
32
+
33
+ # mark class as initialized
34
+ class_variable_set( :@@initialized, true)
35
+
36
+ end
37
+
38
+ # guesses the procedure name of the procedure this class works on
39
+ def construct_procedure_name( classname ) # :nodoc:
40
+ classname.split('::').last.downcase.gsub(/[^a-z_0-9]/, '')
41
+ end
42
+
43
+ # set the procedure name
44
+ def procedure_name=( procedure_name )
45
+ class_variable_set( :@@procedure_name, procedure_name )
46
+ end
47
+
48
+ # get the procedure name
49
+ def procedure_name( procedure_name = nil )
50
+ return self.procedure_name=( procedure_name ) if procedure_name
51
+ begin
52
+ class_variable_get( :@@procedure_name )
53
+ rescue NameError
54
+ construct_procedure_name( self.name )
55
+ end
56
+ end
57
+
58
+ # get the full name of the procedure including schema if set
59
+ def full_name # :nodoc:
60
+ "#{ schema_name ? schema_name + '.' : ''}#{procedure_name}"
61
+ end
62
+
63
+ # set the parameters this procedures accepts
64
+ # example: parameters = {:param1=>Momomoto::Datatype::Text.new}
65
+ # example: parameters = {:param1=>Momomoto::Datatype::Text.new}
66
+ def parameters=( *p )
67
+ p = p.flatten
68
+ class_variable_set( :@@parameters, p )
69
+ end
70
+
71
+ # get the parameters this procedure accepts
72
+ def parameters( *p )
73
+ return self.send( :parameters=, *p ) if not p.empty?
74
+ begin
75
+ class_variable_get( :@@parameters )
76
+ rescue NameError
77
+ initialize_procedure
78
+ class_variable_get( :@@parameters )
79
+ end
80
+ end
81
+
82
+ # get the columns of the resultset this procedure returns
83
+ def columns=( columns )
84
+ class_variable_set( :@@columns, columns)
85
+ end
86
+
87
+ # get the columns of the resultset this procedure returns
88
+ def columns( c = nil )
89
+ return self.columns=( c ) if c
90
+ begin
91
+ class_variable_get( :@@columns )
92
+ rescue NameError
93
+ initialize_procedure
94
+ class_variable_get( :@@columns )
95
+ end
96
+ end
97
+
98
+ def compile_parameter( params ) # :nodoc:
99
+ args = []
100
+ parameters.each do | parameter |
101
+ field_name, datatype = parameter.to_a.first
102
+ raise Error, "parameter #{field_name} not specified" if not params.member?( field_name )
103
+ args << datatype.escape( params[field_name] )
104
+ end
105
+ args.join(',')
106
+ end
107
+
108
+ # execute the stored procedure
109
+ def call( params = {}, conditions = {}, options = {} )
110
+ initialize_procedure unless class_variables.member?('@@initialized')
111
+ sql = "SELECT #{columns.keys.join(',')} FROM "
112
+ sql += "#{full_name}(#{compile_parameter(params)})"
113
+ sql += compile_where( conditions )
114
+ sql += compile_order( options[:order] ) if options[:order]
115
+ sql += compile_limit( options[:limit] ) if options[:limit]
116
+ sql += ';'
117
+ data = []
118
+ database.execute( sql ).each do | row |
119
+ data << const_get(:Row).new( row )
120
+ end
121
+ data
122
+ end
123
+
124
+ end
125
+
126
+ end
127
+
128
+ end
129
+