momomoto 0.1.0

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