OSDN Git Service

Initial commit
[hdboo/hdboo.git] / lib / hdboo / sql.rb
1 require 'time'
2 require 'hdboo/base'
3 require 'hdboo/database'
4 require 'hdboo/sqlparser'
5
6 module ::Kernel
7   def require_sql(module_name, destructive=false, reload=false)
8     unless module_name =~ /([_\w]+(?:\/[_\w]+)*)@([_a-z]+)/
9       raise NameError, module_name
10     end
11     module_name = $1
12     db_name = $2
13     Hdboo.resource("#{module_name}.sql", reload) do |sql_script|
14       raise Errno::ENOENT, module_name unless sql_script
15       Hdboo::SQL.sql_eval(db_name, sql_script, destructive)
16       if Hdboo.resource_exist?("#{module_name}.rb")
17         reload ? load(module_name+'.rb')\
18                : require(module_name)
19       end
20       sql_script
21     end
22     true
23   end
24   
25   def require_sql!(module_name)
26     require_sql(module_name, true, false)
27   end
28   
29   def load_sql(module_name)
30     require_sql(module_name, false, true)
31   end
32   
33   def load_sql!(module_name)
34     require_sql(module_name, true, true)
35   end
36 end
37
38 module Hdboo
39 module SQL
40   
41   def self.connect(database_name)
42     Database[database_name]
43   end
44   
45   def self.query(sql_script, &block)
46     Database.query(sql_script, &block)
47   end
48   
49   def self.sql_eval!(database_name, sql_script)
50     sql_eval(database_name, sql_script, true)
51     self
52   end
53   
54   def self.sql_eval(database_name, sql_script, destructive=false)
55     sql_ast    = Parser.new.parse(sql_script)
56     sql_module = nil
57     
58     Parser::ASTWalker.new\
59       .visit(:MODULE_ANNOTATION) do |node|
60         sql_module = node.children.find do |child|
61                       child.value.is_a?(Module)
62                      end.value
63         node.children.each do |child|
64           if child.value.is_a?(Include)
65             sql_module.elements << child.value.sql_element
66           end
67         end
68       end\
69       .visit(:CREATE_TABLE) do |node|
70         sql_module.elements << node.value
71       end\
72       .visit(:CREATE_VIEW) do |view|
73         sql_module.elements << node.value
74       end\
75       .visit(:CREATE_PROCEDURE) do |node|
76         sql_module.elements << node.value
77       end\
78     .walk(sql_ast)
79     
80     db = Database[database_name]
81     db.execute(sql_module.ddl) if destructive
82     db.transaction {sql_module.eval(destructive)}
83     self
84   end
85   
86   module Annotation
87     #static methods
88     def self.new(type, args)
89       type = type.to_sym
90       clazz = nil
91       annotation_libraries.each do |library|
92         if library.const_defined?(type)
93           clazz = library.const_get(type)
94           break if clazz < Annotation
95           clazz = nil
96         end
97       end
98
99       unless clazz
100         raise 'unknown annotation type: ' + type.to_s
101       end
102       begin
103         clazz.new(*args)
104       rescue
105         raise "annotation #{type.to_s} can not be initialized.: #{$!.message}"
106       end
107     end
108     
109     def self.annotation_libraries
110       unless @annotation_libraries
111         @annotation_libraries = [
112           ::Hdboo::SQL,
113           ::Hdboo::Validator,
114           ::Hdboo::SQL::Table,
115           ::Hdboo::SQL::Procedure,
116           ::Hdboo::SQL::Procedure::Intercepter,
117           ::Hdboo::SQL::Procedure::Parameter
118         ]
119         ::Hdboo::Validator.constants.each do |name|
120           validator = ::Hdboo::Validator.const_get(name)
121           if validator < ::Hdboo::Validator
122             validator.send(:include, Annotation)
123           end
124         end
125       end
126       @annotation_libraries
127     end
128   
129     #instance methods
130     attr_accessor :annotation_context
131   end
132   
133   module Element
134     attr_accessor :sql_module, :source
135     def eval
136       raise "#{self.class.name}#eval() must be implemented."
137     end
138     
139     def create 
140       raise "#{self.class.name}#create() must be implemented."
141     end
142     
143     def drop
144       raise "#{self.class.name}#drop() must be implemented."
145     end
146   end
147   
148   class Module
149     include Annotation
150     #static methods
151     @instances = {}
152     def self.[](name)
153       @instances[name.to_sym]
154     end
155     
156     def self.register(instance)
157       @instances[instance.fqn] = instance
158     end
159     
160     #instance methods   
161     attr_reader :fqn, :schema, :namespace, :elements
162     def initialize(fqn, schema='')
163       @fqn = fqn
164       @schema = schema
165       @elements = []
166     end
167  
168     def [](symbol)
169       namespace.const_get(symbol.to_sym)
170     end
171     
172     def namespace
173       return @namespace if @namespace
174       unless fqn =~ /\w+(.\w+)*/
175         raise SyntaxError, "invalid namespace format: #{fqn}"
176       end
177       script = ''
178       segments = fqn.split(/\./)
179       segments.reverse.each_with_index do |name, depth|
180         root = (depth == segments.length-1) ? '::' : ''
181         script = [ "module #{root}#{name}",
182                      script,
183                    'end'
184                  ].join("\n")
185       end
186       Kernel.eval(script)
187       @rb_module = Kernel.eval('::' + segments.join('::'))
188       @rb_module
189     end
190     
191     def eval(destructive=false)
192       elements.each do |element|
193         element.sql_module = self
194         element.eval(destructive)
195       end
196     end
197     
198     def ddl
199       drop + "\n" + create
200     end
201     
202     def create
203       statements = elements.collect {|each| each.create}\
204                            .join("\n//\n")
205       "DELIMITER // \n#{statements}\n//"
206     end
207     
208     def drop
209       statements = elements.collect {|each| each.drop}\
210                            .reverse.join("\n//\n")
211       ['DELIMITER //',
212        'SET FOREIGN_KEY_CHECKS = 0;',
213        statements,
214        'SET FOREIGN_KEY_CHECKS = 1;'].join("\n//\n") + "\n//"
215     end
216   end
217   
218   class Include
219     include Annotation
220     
221     attr_reader :package_name
222     def initialize(package_name)
223       @package_name = package_name.to_sym
224     end
225     
226     def sql_element
227       unless Include.const_get(package_name)
228         raise "unkown sql package name: #{package_name}"
229       end
230       clazz = Include.const_get(package_name)
231       clazz.new
232     end
233     
234     #inner classes
235     class Pivot
236       include Element
237       def eval(destructive)
238         #nothing to do.
239       end
240       
241       def create
242         return [
243           'CREATE TABLE pivot (pos INTEGER) ENGINE = MYISAM',
244           (<<-END_SQL
245           INSERT INTO pivot
246           VALUES
247             (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),
248             (11),(12),(13),(14),(15),(16),(17),(18),(19),(20),
249             (21),(22),(23),(24),(25),(26),(27),(28),(29),(30),
250             (31),(32)
251           ;
252           END_SQL
253           ),
254           
255           (<<-END_SQL
256           CREATE FUNCTION CSV_GET(
257             csv VARCHAR(1024),
258             pos INTEGER
259           ) RETURNS VARCHAR (1024) DETERMINISTIC
260           BEGIN
261             RETURN SUBSTRING_INDEX(SUBSTRING_INDEX(csv, ',', pos), ',', -1);
262           END
263           END_SQL
264           ),
265           
266           (<<-END_SQL
267           CREATE FUNCTION CSV_LENGTH(
268             csv VARCHAR(1024)
269           ) RETURNS INTEGER DETERMINISTIC
270           BEGIN
271             IF csv IS NULL THEN
272               RETURN 1;
273             END IF;
274             RETURN LENGTH(csv) - LENGTH(REPLACE(csv, ',', '')) + 1;
275           END
276           END_SQL
277           ) 
278         ]
279       end
280       
281       def drop
282         return [
283           'DROP TABLE IF EXISTS pivot CASCADE',
284           'DROP FUNCTION IF EXISTS CSV_GET',
285           'DROP FUNCTION IF EXISTS CSV_LENGTH',
286         ]
287       end
288     end
289   end
290   
291   class ResultSet < Array
292     def as(clazz)
293       replace(collect do |row|
294         row.as(clazz)
295       end)
296     end
297   end
298   
299   class Table
300     include Element
301     
302     #static methods
303     def self.[](name)
304       @instances[name.to_sym]      
305     end
306     def self.register(table)
307       @instances[table.name] = table      
308     end
309     @instances = {}
310     
311     #instance methods
312     attr_reader :name, :elements, :annotations, :clazz
313     def initialize(name, elements, annotations=[])
314       @name        = name.to_sym
315       @elements    = elements.to_a
316       @annotations = annotations.to_a
317       annotations.each {|each| each.annotation_context = self}
318     end
319     
320     def columns
321       unless @columns
322         @columns = elements.select {|each| each.is_a?(Column)}
323       end
324       @columns
325     end
326     
327     def constraints
328       unless @constraints
329         @constraints = elements.select {|each| each.is_a?(Constraint)}
330       end
331       @constraints
332     end
333     
334     def unique_constraints
335       constraints.select{|any| any.is_a?(Table::Constraint::Unique)}
336     end
337     
338     def create
339       [ source ]
340     end
341     
342     def drop
343       ["DROP TABLE IF EXISTS #{name} CASCADE"]
344     end
345     
346     def eval(destructive)
347       Table.register(self)
348       @clazz = sql_module.namespace.const_defined?(name)\
349              ? augment_class\
350              : define_class
351       if classified_by
352         classified_by.define_subclasses
353       end 
354       @clazz
355     end
356     
357     def define_class
358       clazz = Class.new(Record)
359       define_accessors(clazz)
360       define_class_accessors(clazz)
361       sql_module.namespace.const_set(name, clazz)
362       clazz      
363     end
364     
365     def augment_class
366       clazz = sql_module.namespace.const_get(name)
367       define_accessors(clazz)
368       define_class_accessors(clazz)
369       clazz
370     end
371     
372     def define_accessors(clazz)
373       metadata = self
374       clazz.module_eval do
375         metadata.columns.each do |column|
376           define_method(column.name) do
377             self[column.name]
378           end
379           setter_name = (column.name.to_s + '=').to_sym
380           define_method(setter_name) do |val|
381             column.validate(val)
382             self[column.name] = val
383             self
384           end
385           if column.name.to_s =~ /^is_(.+)$/
386             define_method($1 + '?') do
387               result = send(column.name)
388               return (result == 0) ? false : result
389             end
390           end
391         end
392       end
393     end
394     
395     def define_class_accessors(clazz)
396       metadata = self
397       Hdboo.singleton_eval(clazz) do
398         define_method(:metadata) do
399           metadata
400         end
401       end
402     end
403     
404     def classified_by
405       unless @classified_by
406         @classified_by = annotations.find {|each| each.is_a? ClassifiedBy}
407       end
408       @classified_by
409     end
410     
411     class Column
412       attr_reader :name, :type, :options, :annotations
413       def initialize(name, type, options=[], annotations=[])
414         @name = name.to_sym
415         @type = type
416         @options = options.to_a
417         @annotations = annotations.to_a
418         annotations.each {|each| each.annotation_context = self}
419       end
420       
421       def validators
422         unless @validators
423           validators = annotations.concat(options).push(@type)\
424                          .select{|each| each.is_a?(Validator)}
425           @validators = Validator::ValidatorList.new(validators)
426         end
427         @validators
428       end
429       
430       def validate(val)
431         validators.validate(val, name)
432       end
433     end
434     
435     module Constraint
436       #static methods
437       def self.new(type, args)
438         type = type.to_sym
439         unless self.const_defined?(type)
440           raise "unknown constraint type: #{type.to_s}"
441         end
442         clazz = self.const_get(type)
443         clazz.new(*args)
444       end
445       
446       #instance methods
447       attr_reader :name, :columns
448       attr_accessor :annotations
449       
450       #inner classes
451       class Unique
452         include Constraint
453         def initialize(name, columns)
454           @name        = name.to_sym
455           @columns     = columns.to_a
456           @annotations = []
457           annotations.each {|each| each.annotation_context = self}
458         end
459       end
460       
461       class PrimaryKey < Unique
462         include Constraint
463       end
464       
465       class ReferencesConstraint
466         include Constraint
467         attr_reader :reference_table, :reference_columns
468         def initialize(name, columns, reference_table, reference_columns)
469           @name              = name.to_sym
470           @columns           = columns.to_a
471           @reference_table   = reference_table.to_sym
472           @reference_columns = reference_columns.to_a
473           @annotations       = []
474           annotations.each {|each| each.annotation_context = self}
475         end
476       end
477     end
478     
479     class Index
480       attr_reader :name, :columns, :annotations
481       def initialize(name, columns)
482         @name        = name.to_sym
483         @columns     = columns
484         @annotations = []
485         annotations.each {|each| each.annotation_context = self}
486       end
487     end
488     
489     class ClassifiedBy
490       include Annotation
491       attr_reader :column_name
492       def initialize(column_name)
493         @column_name = column_name.to_sym
494       end
495       
496       def annotation_context=(table)
497         unless table.columns.any? {|any| any.name == column_name}
498           raise "undefined column: #{table.name}.#{@column_name}."
499         end
500         @annotation_context = table
501       end
502       
503       def define_subclasses
504         table = annotation_context
505         subtype_names.each do |subtype_name|
506           namespace = table.sql_module.namespace
507           next if namespace.const_defined?(subtype_name)
508           subclass = Class.new(table.clazz)
509           namespace.const_set(subtype_name, subclass)
510         end
511       end
512       
513       def subtype_names
514         power_type.clazz.constants.reject{|any| any == 'Enumerable'}
515       end
516       
517       def power_type
518         table = annotation_context
519         power_type_name = table.constraints.find do |constraint|
520              constraint.is_a?(Constraint::ReferencesConstraint)\
521           && constraint.columns[0].to_sym == column_name 
522         end.reference_table
523         Table[power_type_name]
524       end
525     end
526   end
527   
528   class View
529     include Element
530     attr_reader :name, :from_tables, :column_names, :annotations
531     def initialize(name, from_tables=[], column_names=[], annotations=[])
532       @name = name.to_sym
533       @from_tables = from_tables.to_a
534       @column_names = column_names.to_a
535       @annotations = annotations.to_a
536       annotations.each {|each| each.annotation_context = self}
537     end
538     
539     def eval
540       #TODO
541     end
542     
543     def create
544       [source]
545     end
546     
547     def drop
548       ["DROP VIEW IF EXISTS #{name} CASCADE"]      
549     end
550   end
551   
552   module DataType
553     include Validator
554     attr_reader :name, :length
555     
556     def self.new(type, length=nil)
557       clazz = TYPE_TO_CLASS_MAP[type.to_sym]
558       clazz.new(length)
559     end
560     
561     def initialize(length=nil)
562       @length = length
563     end
564     
565     def value_of(value)
566       raise "#{self.class.name}#value_of() must be implemented."
567     end
568     
569     class Integer
570       include DataType
571       def pass?(val)
572         if val.nil? || val.is_a?(TrueClass) || val.is_a?(FalseClass)
573           return true
574         end
575         if val.is_a?(::String) && (val =~ /([+|-]?[1-9][0-9]*|0)/)
576           val = val.to_i
577         end
578         unless val.is_a?(Numeric) && val.integer?
579           return false
580         end
581         length.nil? ? true\
582                     : val < 10^length
583       end
584       
585       def message(val)
586         length.nil?\
587           ? "must be integer, but was #{val}."\
588           : "must be integer of #{length} digits or less, but was #{val}."
589       end
590       
591       def value_of(val)
592         if val.is_a?(Hash)
593           val = val.values.first
594         elsif val.is_a?(Array)
595           val = val.first
596         end
597         val.to_i
598       end
599     end
600     
601     class Bool < Integer
602       include DataType
603       def initialize
604         @length = 1
605       end
606            
607       def value_of(val)
608         if val.is_a?(Hash)
609           val = val.values.first
610         elsif val.is_a?(Array)
611           val = val.first
612         end
613         val != 0 
614       end
615     end
616     
617     class String
618       include DataType
619       def pass?(val)
620         val.nil? || val.is_a?(::String)
621       end
622       
623       def message(val)
624         "must be String, but was #{val.class.name}."
625       end
626       def value_of(val)
627         if val.is_a?(Hash)
628           val = val.values.first
629         elsif val.is_a?(Array)
630           val = val.first
631         end
632         val.to_s
633       end
634     end
635     
636     class DateTime
637       include SQL::DataType
638       def pass?(val)
639         true
640         #TODO
641       end
642       
643       def message(val)
644         'datetime'
645         #TODO
646       end  
647     end
648     
649     TYPE_TO_CLASS_MAP = {
650       :INTEGER   => DataType::Integer,
651       :TINYINT   => DataType::Integer,
652       :SMALLINT  => DataType::Integer,
653       :BOOL      => DataType::Bool,
654       :CHAR      => DataType::String,
655       :VARCHAR   => DataType::String,
656       :TEXT      => DataType::String,
657       :DATETIME  => DataType::DateTime,
658       :TIMESTAMP => DataType::DateTime 
659     }    
660   end
661   
662   class Record < HashAsObject
663     include Validatable
664     
665     #static methods
666     def self.metadata
667       Table.new('-', [], [])
668     end
669     def metadata
670       self.class.metadata
671     end
672     
673     def self.identifier_keys
674       metadata.constraints\
675               .find {|some| some.is_a? Table::Constraint::PrimaryKey}\
676               .columns\
677               .map {|each| each.to_sym}
678     end
679     
680     def identifier
681       self.class.identifier_keys.map {|key| self[key]}
682     end
683     
684     def ==(other)
685       return super unless other.is_a? Record
686       return other == self if self.instance_of? Record
687       identifier == other.identifier
688     end
689         
690     def as(clazz)
691       metadata = clazz.metadata
692       if metadata.classified_by
693         class_name = self[metadata.classified_by.column_name]
694         clazz = metadata.sql_module.namespace.const_get(class_name)
695       end
696       clazz.new.replace(self)
697     end
698     
699     def validators_for(key)
700       result = []
701       key = key.to_sym
702       unless self.is_a?(Record)
703         result.concat(super)
704       end
705       column_for_key = metadata.columns.find{|any|any.name==key}
706       result.concat(column_for_key.validators) if column_for_key
707       result
708     end
709   end
710   
711   class Procedure
712     include Element
713     
714     #static methods
715     @instances = {}
716     def self.[](name)
717       @instances[name.to_sym]
718     end
719     
720     def self.register(procedure)
721       @instances[procedure.name.to_sym] = procedure
722     end
723     
724     #instance methods
725     attr_reader :name, :owner, :annotations, :parameters
726     def initialize(name, parameters, annotations)
727       @name = name
728       @parameters = parameters
729       parameters.each {|each| each.owner = self}
730       @annotations = annotations.to_a
731       annotations.each {|each| each.annotation_context = self}
732     end
733    
734     def create
735       [source]
736     end
737     
738     def drop
739       ["DROP PROCEDURE IF EXISTS #{name}"]
740     end
741     
742     def eval(destructive)
743       Procedure.register(self)
744       unless name =~ /^([A-Z][A-Za-z0-9]+)_([\w]+)$/
745         return nil
746       end
747       class_name  = $1
748       method_name = $2
749       clazz  = sql_module.namespace.const_get(class_name)
750       @owner = clazz.metadata
751
752       Hdboo.singleton_eval(clazz, <<-EOS)
753         def #{method_name} (*args, &block)
754           Hdboo::SQL::Procedure[:#{name}].call(*args, &block)
755         end
756       EOS
757       
758       clazz.module_eval(<<-EOS)
759         def #{method_name}(kargs={}, &block)
760           kargs = Hdboo.normalize_key(kargs)
761           kargs = self.to_hash.merge(kargs.to_hash)
762           Hdboo::SQL::Procedure[:#{name}].call(kargs, &block)
763         end
764         
765         def #{method_name}! (kargs={}, &block)
766           result = #{method_name}(kargs, &block)
767           new_status = result.is_a?(Array)\
768                      ? result[0]\
769                      : result
770           unless new_status
771             raise NoDataFound, '#{name}', caller
772           end
773           replace(new_status)
774         end
775       EOS
776       
777       if master_data_loader && destructive
778         master_data_loader.load
779       end
780       
781       if constants_definition
782         constants_definition.define_on(clazz)
783       end
784       self
785     end
786     
787     def master_data_loader
788       annotations.find {|any| any.is_a? MasterData}
789     end
790     
791     def constants_definition
792       annotations.find {|any| any.is_a? DefineConstants}
793     end
794     
795     def call(*args, &block)
796       kargs = nil
797       if args.length == 0
798         kargs = {}
799       elsif args.length == 1 && args.first.respond_to?(:to_hash)
800         kargs = Hdboo.normalize_key(args.first.to_hash)
801       else
802         kargs = {}
803         parameters.each_with_index do |param, i|
804           kargs[param.name.to_sym] = args[i]
805         end
806       end
807
808       intercepters.reverse.each do |intercepter|
809         intercepter.before(kargs, self)
810       end
811       
812       invocation = nil
813       intercepters.reverse.each do |intercepter|
814         wrapee = invocation
815         invocation = lambda do |kargs|
816           intercepter.around(wrapee, kargs, self, &block)
817         end 
818       end
819       
820       begin
821         result = invocation.call(kargs)
822       rescue
823         intercepters.each do |intercepter|
824           intercepter.after_error($!, self)
825         end
826       end
827       
828       intercepters.each do |intercepter|
829         result = intercepter.after(result, self)
830       end
831       result
832     end
833     
834     def before(kargs, procedure)
835       errors = Hash.new {|hash, key| hash[key] = []}
836       parameters.each do |parameter|
837         value = kargs[parameter.name]
838         if parameter.required? && value.nil?
839           errors[parameter.name] << 'required.'
840         else
841           parameter.validators.each do |validator|
842             unless validator.pass?(value)
843               errors[parameter.name] << validator.message(value)
844             end
845           end
846         end
847       end
848       unless errors.empty?
849         raise InvalidArgument, errors
850       end
851     end
852   
853     def around(invocation, kargs, procedure, &block)
854       args = parameters.collect {|parameter| kargs[parameter.name]}
855       Database.call_procedure(name, args, &block)
856     end
857   
858     def after(result, procedure)
859       result
860     end
861     
862     def after_error(error, procedure)
863       raise error
864     end
865         
866     def intercepters
867       unless @intercepters
868         @intercepters = @annotations\
869                           .select {|each| each.kind_of?(Intercepter)}\
870                           .unshift(self)\
871                           .reverse
872       end
873       @intercepters
874     end
875     
876     #inner classes
877     class Parameter
878       attr_reader :name, :type, :annotations
879       attr_accessor :owner
880       
881       def initialize(name, type, annotations)
882         @name        = name.sub(/^_+/, '').to_sym
883         @type        = type
884         @annotations = annotations
885         @optional    = nil
886         annotations.each {|each| each.annotation_context = self}
887       end
888       
889       def annotations= (annotations)
890         annotations.each {|each| each.annotation_context = self}
891         @annotations = annotations
892       end
893       
894       def validators
895         return @validators if @validators
896         @validators = annotations.map do |annotation|
897           if annotation.is_a?(ColumnValue)
898             annotation.validators
899           elsif annotation.is_a?(Validator)
900             annotation
901           else
902             nil
903           end
904         end.flatten.compact
905       end
906       
907       def optional?
908         if @optional.nil?
909           @optional = annotations.any? {|each| each.is_a?(Optional)}
910         end
911         @optional
912       end
913       
914       def required?
915         !optional?
916       end
917   
918       
919       class Optional
920         include Annotation
921         attr_reader :default_value
922         def intialize(default_value=nil)
923           @default_value = default_value
924         end
925       end
926       
927       class ColumnValue
928         include Annotation
929         attr_reader :table_name
930         def initialize(table_name=nil)
931           @table_name = table_name 
932         end
933         
934         def column_name
935           annotation_context.name
936         end
937         
938         def validators
939           procedure = annotation_context.owner
940           namespace = procedure.sql_module.namespace
941           table = table_name.nil?\
942                 ? procedure.owner\
943                 : namespace.const_get(table_name.to_sym).metadata
944           column = table.columns.find {|each| each.name == column_name}
945           @validators = column.validators
946         end
947       end
948     end
949   
950     class Intercepter
951       def before(kargs, procedure)
952       end
953       
954       def around(invocation, kargs, procedure, &block)
955         invocation.call(kargs, &block)
956       end
957       
958       def after(result, procedure)
959         result
960       end
961       
962       def after_error(error, procedure)
963         raise error
964       end
965       
966       class OnNoData < Intercepter
967         include Annotation
968         attr_reader :message_key, :message_text
969         def initialize(message_key, message_text)
970           @message_key  = message_key
971           @message_text = message_text
972         end
973         
974         def after(result, procedure)
975           if result.nil? || (result.is_a?(Array) && result.empty?)
976             raise NoDataFound.new({@message_key => [@message_text]})
977           end
978           result
979         end
980       end
981       
982       class OnExists < Intercepter
983         include Annotation
984         attr_reader :message_key, :message_text
985         def initialize(message_key, message_text)
986           @message_key  = message_key
987           @message_text = message_text
988         end
989         
990         def after(result, procedure)
991           unless result.nil? || (result.is_a?(Array) && result.empty?)
992             raise DataConflict.new({@message_key => [@message_text]})
993           end
994           result
995         end
996       end
997       
998       class OnConflict < Intercepter
999         include Annotation
1000         attr_reader :constraint_name, :message_key, :message_text
1001         def initialize(constraint_name, message_key, message_text)
1002           @constraint_name = constraint_name
1003           @message_key     = message_key
1004           @message_text    = message_text
1005         end
1006         
1007         def after_error(error, procedure)
1008           return unless error.sqlstate == '23000'
1009           table = procedure.owner
1010           if error.message =~ /Duplicate entry '(.*?)' for key '(.+?)'/
1011             #seq_num = $1.to_i - 1
1012             #violated_constraint = table.unique_constraints[seq_num]
1013             #if  violated_constraint.name == constraint_name.to_sym
1014             
1015             if constraint_name == $2
1016               raise DataConflict, {@message_key => [@message_text]}
1017             end
1018           elsif error.message =~ /foreign key constraint fails/
1019             if error.message.include?(constraint_name.to_s)
1020               raise DataConflict, {@message_key => [@message_text]}
1021             end
1022           end
1023           #raise error
1024         end  
1025       end
1026       
1027       class Returns < Intercepter
1028         include Annotation
1029         attr_reader :return_type
1030         
1031         def initialize(return_type=Record)
1032           if return_type.is_a?(DataType)
1033             @return_type = return_type
1034           elsif return_type.is_a?(String)
1035             @return_type = return_type.to_sym == :Bool\
1036                          ? DataType::Bool.new\
1037                          : return_type.to_sym
1038           else
1039             @return_type = return_type
1040           end
1041         end
1042         
1043         def after(result, procedure)
1044           if return_type.is_a?(Symbol)
1045             type = procedure.sql_module.namespace.const_get(return_type)
1046             raise "unknown type: #{return_type.to_s}" if type.nil?
1047             @return_type = type
1048           end
1049           return nil if result.nil?
1050           unless result.is_a?(Array)
1051             result = RecordSet.new([result])
1052           end
1053           result.replace(result.collect do |record|
1054             return_type.is_a?(DataType)\
1055               ? return_type.value_of(record)\
1056               : record.as(return_type)
1057           end)
1058           result
1059         end
1060       end
1061       
1062       class ReturnsFirst < Returns
1063         def after(result, procedure)
1064           super.first
1065         end        
1066       end
1067     end
1068     
1069     class MasterData
1070       include Annotation
1071       def load
1072         procedure = annotation_context
1073         procedure.call
1074       end
1075     end
1076     
1077     class DefineConstants
1078       include Annotation
1079       attr_reader :key_column_name
1080       def initialize(key_column_name)
1081         @key_column_name = key_column_name.to_sym
1082       end
1083       
1084       def define_on(clazz)
1085         procedure = annotation_context
1086         procedure.call.each do |record|
1087           constant_key = record[key_column_name]
1088           unless clazz.const_defined?(constant_key)
1089             clazz.const_set(constant_key, record)
1090           end
1091         end
1092       end
1093     end
1094   end
1095 end
1096 end