OSDN Git Service

Initial commit
[hdboo/hdboo.git] / lib / hdboo / sqlparser.y
1 class Hdboo::SQL::Parser
2
3 token
4   IDENTIFIER
5   PARAMETER_NAME
6   NUMERIC
7   CHARACTER_STRING
8   INTEGER
9   FLOAT
10   PROCEDURE_CODE
11
12 rule
13   sql
14     : module_annotation sql_command_list
15         { result = nd(:SQL, val)}
16
17   module_annotation
18     : annotation_specification
19         { result = nd(:MODULE_ANNOTATION, val[0].children, val) }
20
21   sql_command_list
22     :
23         { result = nd(:SQL_COMMAND_LIST, [], val) }
24     | sql_command
25         { result = nd(:SQL_COMMAND_LIST, [val[0]], val) }
26     | sql_command ';' sql_command_list
27         { update_pos(val[2], val)
28           result = val[2].unshift(val[0]) }
29     | sql_command '$$' sql_command_list
30         { update_pos(val[2], val)
31           result = val[2].unshift(val[0]) }
32     | sql_command '//' sql_command_list
33         { update_pos(val[2], val)
34           result = val[2].unshift(val[0]) }
35       
36   sql_command
37     : table_definition
38     | view_definition
39     | procedure_definition
40     | insert_statement
41     | alter_delimiter
42   
43   qualified_name
44     : IDENTIFIER                     
45     | qualified_name '.' IDENTIFIER 
46         { result = nd(:IDENTIFIER, val[0].value + val[1].value + val[2].value, val)}
47   
48   table_definition
49     : 'CREATE' 'TABLE' IDENTIFIER '('
50       annotation_specification table_element_list
51       ')' table_option_list
52         { result = table(val[2], val[5], val[4], val) }
53   
54   view_definition
55     : 'CREATE' 'VIEW' IDENTIFIER annotation_specification
56       'AS' query_specification
57         { result = nd(:CREATE_VIEW, [val[2], val[5], val[3]], val) }
58   
59   table_option_list
60     :
61         { result = nd(:TABLE_OPTION_LIST, [], val) }
62     | table_option table_option_list
63         { update_pos(val[1], val)
64           result = val[1].unshift(val[0]) }
65   
66   table_option
67     : 'ENGINE' '=' engine_type
68         { result = nd(:ENGINE, val[2], val)}
69     
70   engine_type
71     : 'MYISAM'
72     | 'INNODB'
73     | 'HEAP'
74
75   table_element_list
76     : table_element
77         { result = nd(:TABLE_ELEMENT_LIST, [val[0]], val) }
78     | table_element ',' table_element_list
79         { update_pos(val[2], val)
80           result = val[2].unshift(val[0]) }
81           
82   table_element
83     : column_definition
84     | constraint_definition
85     
86   column_definition
87     : IDENTIFIER annotation_specification data_type column_option_list
88         { result = column(val[0], val[2], val[3], val[1], val) }
89     
90   column_option_list
91     :
92         { result = nd(:COLUMN_OPTION_LIST, [], val) }
93     | column_option column_option_list
94         { update_pos(val[0], val)
95           result = val[1].unshift(val[0]) }
96     
97   column_option
98     : 'NOT' 'NULL'       { result = nd(:NOT_NULL, val) }
99     | 'NULL'             { result = nd(:NULL, val) }
100     | 'DEFAULT' literal  { result = nd(:DEFAULT, val[1], val) }
101     | 'DEFAULT' 'NULL'   { result = nd(:DEFAULT, nil, val) }
102     | 'AUTO_INCREMENT'   { result = nd(:AUTO_INCREMENT, val) }
103   
104   constraint_definition
105     : primary_key
106     | foreign_key
107     | unique
108     | index
109     | fulltext_index
110     
111   primary_key
112     : constraint_name_definition 'PRIMARY' 'KEY' '(' constrained_column_list ')'
113         { result = constraint(:PrimaryKey, val[0].value, val[4].to_a, val) }
114   
115   foreign_key 
116     : constraint_name_definition 'FOREIGN' 'KEY' '(' constrained_column_list ')'
117       references_specification
118         { referenced_table, referenced_columns = *(val[6].value)
119           result = constraint(:ReferencesConstraint,
120                      val[0].value, val[4].to_a,
121                      referenced_table, referenced_columns, val) }
122                      
123   references_specification
124     : 'REFERENCES' IDENTIFIER triggered_action
125         { result = nd(:REFERENCES_SPECIFICATION, [val[1].value, []], val) }
126     | 'REFERENCES' IDENTIFIER '(' constrained_column_list ')' triggered_action
127         { result = nd(:REFERENCES_SPECIFICATION, [val[1].value, val[4].value], val) }
128     
129   triggered_action
130     :                         { result = nd(:TRIGGERED_ACTION, val) }
131     | 'ON' 'DELETE' 'CASCADE' { result = nd(:TRIGGERED_ACTION, val) }
132   
133   unique
134     : constraint_name_definition 'UNIQUE' '(' constrained_column_list ')'
135         { result = constraint(:Unique, val[0].value, val[3].to_a, val) }
136   
137   constraint_name_definition 
138     :                          { result = nd(:CONSTRAINT_NAME, '-', val) }
139     | 'CONSTRAINT' IDENTIFIER  { result = nd(:CONSTRAINT_NAME, val[1].value, val) }
140   
141   index
142     : 'INDEX' index_name_definition '(' constrained_column_list ')'
143         { result = index(val[1].value, val[3].value, val) }
144   
145   fulltext_index
146     : 'FULLTEXT' index_name_definition using '(' constrained_column_list ')'
147         { result = index(val[1].value, val[4].value, val) }
148   
149   using
150     : 'USING' 'SECTIONALIZE'
151         { result = nd(:USING, val)}
152
153   index_name_definition
154     :            { result = nd(:CONSTRAINT_NAME, '<NO_NAME>', val) }
155     | IDENTIFIER { result = nd(:CONSTRAINT_NAME, val[0].value, val) }
156   
157   constrained_column_list
158     : IDENTIFIER
159         { result = nd(:CONSTRAINED_COLUMN_LIST, [val[0]], val) }
160     | IDENTIFIER ',' constrained_column_list
161         { update_pos(val[2], val)
162           result = val[2].unshift(val[0]) }
163
164   procedure_definition
165     : 'CREATE' 'PROCEDURE' IDENTIFIER '(' parameter_list ')'
166       annotation_specification
167       'BEGIN' PROCEDURE_CODE 'END'
168         { result = procedure(val[2], val[4], val[6], val) }
169   
170   parameter_list
171     : 
172         { result = nd(:PARAMETER_LIST, [], val)}
173     | parameter
174         { result = nd(:PARAMETER_LIST, [val[0]], val) }
175     | parameter ',' annotation_specification parameter_list
176         { val[0].value.annotations = val[2].to_a
177           update_pos(val[3], val)
178           result = val[3].unshift(val[0]) }
179           
180   parameter
181     : parameter_direction parameter_name data_type annotation_specification
182         { result = parameter(val[1], val[2], val[3], val) }
183         
184   parameter_name
185     : IDENTIFIER
186     | PARAMETER_NAME
187   
188   parameter_direction
189     :
190     | 'IN'
191     | 'OUT'
192     | 'INOUT'
193   
194   annotation_specification
195     : 
196         { result = nd(:ANNTATION_LIST, [], val) }
197     | annotation_specification '--' annotation_list
198         { update_pos(val[0], val)
199           val[0].value.concat(val[2].value)
200           result = val[0] }
201
202   annotation_list
203     : annotation
204         { result = nd(:ANNTATION_LIST, [val[0]], val) }
205     | annotation_list annotation
206         { update_pos(val[0], val)
207           result = val[0].push(val[1]) }
208       
209   annotation
210     : '@' IDENTIFIER
211         { result = annotation(val[1], [], val) }
212     | '@' IDENTIFIER '(' annotation_argument_list ')'
213         { result = annotation(val[1], val[3], val) }
214
215   annotation_argument_list
216     :
217         { result = nd(:ANNOTATION_ARGUMENT_LIST, [], val) }
218     | annotation_argument
219         { result = nd(:ANNOTATION_ARGUMENT_LIST, [val[0]], val) }
220     | annotation_argument ',' annotation_argument_list
221         { result = update_pos(val[2], val).unshift(val[0]) }
222     
223   annotation_argument
224     : literal
225     | qualified_name
226     | data_type 
227     
228   insert_statement
229     : 'INSERT' 'INTO' IDENTIFIER insert_columns_and_source
230         { result = nd(:INSERT, val) }
231     
232   insert_columns_and_source
233     : from_constructor
234   
235   from_constructor
236     : 'VALUES' column_value_expression_list
237       { result = update_pos(val[1], val) }
238     
239   column_value_expression_list
240     : column_value_expression
241         { result = nd(:COLUMN_VALUE_EXPRESSION_LIST, [], val) }
242     | column_value_expression ',' column_value_expression_list
243         { result = update_pos(val[2], val).unshift(val[0]) }
244     
245   column_value_expression
246     : '(' value_expression_list ')'
247         { result = update_pos(val[1], val) }
248   
249   value_expression_list
250     :
251         { result = nd(:VALUE_EXPRESSION_LIST, [], val) }
252     | value_expression
253         { result = nd(:VALUE_EXPRESSION_LIST, [val[0]], val) }
254     | value_expression ',' value_expression_list
255         { result = update_pos(val[2], val).unshift(val[0]) }
256     
257   value_expression
258     : literal
259     | qualified_name 
260
261   data_type
262     : 'INT'                      { result = datatype(:INTEGER,   nil,    val) }
263     | 'INTEGER'                  { result = datatype(:INTEGER,   nil,    val) }
264     | 'TINYINT'                  { result = datatype(:TINYINT,   nil,    val) }
265     | 'SMALLINT'                 { result = datatype(:SMALLINT,  nil,    val) }
266     | 'INTEGER'  '(' NUMERIC ')' { result = datatype(:INTEGER,   val[2], val) }
267     | 'TINYINT'  '(' NUMERIC ')' { result = datatype(:TINYINT,   val[2], val) }
268     | 'SMALLINT' '(' NUMERIC ')' { result = datatype(:SMALLINT,  val[2], val) }    
269     | 'CHAR'     '(' NUMERIC ')' { result = datatype(:CHAR,      val[2], val) }
270     | 'VARCHAR'  '(' NUMERIC ')' { result = datatype(:VARCHAR,   val[2], val) }
271     | 'TEXT'                     { result = datatype(:TEXT,      nil,    val) }
272     | 'BIT'                      { result = datatype(:BIT,       nil,    val) }
273     | 'DATETIME'                 { result = datatype(:DATETIME,  nil,    val) }
274     | 'TIMESTAMP'                { result = datatype(:TIMESTAMP, nil,    val) }
275
276   literal
277     : CHARACTER_STRING
278     | number
279     
280   number
281     : NUMERIC
282     | INTEGER
283     | FLOAT
284     
285   alter_delimiter
286     : 'DELIMITER'
287     
288   query_specification
289     : 'SELECT' select_list table_expression
290         { result = nd(:QUERY_SPECIFICATION, val) }
291   
292   select_list
293     : '*'
294     | select_sublist
295   
296   select_sublist
297     : IDENTIFIER
298     | IDENTIFIER ',' select_sublist
299     
300   table_expression
301     : from_clause where_clause
302        { result = nd(:TABLE_EXPRESSION, val) }
303     
304   from_clause
305     : 'FROM' IDENTIFIER
306         { result = nd(:FROM_CLAUSE, val[1], val)}
307     
308   where_clause
309     : 'WHERE' search_condition
310         { result = nd(:WHERE_CLAUSE, val) }
311    
312   search_condition
313     : boolean_value_expression
314     
315   boolean_value_expression
316     : boolean_term
317         { result = nd(:BOOLEAN_VALUE_EXPRESSION, val) }
318     | boolean_value_expression 'OR' boolean_term
319         { result = nd(:BOOLEAN_VALUE_EXPRESSION, val) }
320     
321   boolean_term
322     : boolean_facter
323         { result = nd(:BOOLEAN_TERM, val) }
324     | boolean_term 'AND' boolean_facter
325         { result = nd(:BOOLEAN_TERM, val) }
326   
327   boolean_facter
328     : boolean_test
329         { result = nd(:BOOLEAN_FACTER, val) }
330     | 'NOT' boolean_test
331         { result = nd(:BOOLEAN_FACTER, val) }
332   
333   boolean_test
334     : boolean_primary
335         { result = nd(:BOOLEAN_TEST, val) }
336     | boolean_primary 'IS' true_value
337         { result = nd(:BOOLEAN_TEST, val) }
338     | boolean_primary 'IS' 'NOT' true_value
339         { result = nd(:BOOLEAN_TEST, val) }
340     
341   true_value
342     : 'TRUE'
343     | 'FALSE'
344   
345   boolean_primary
346     : predicate
347     
348   predicate
349     : comparison_predicate
350     
351   comparison_predicate
352     : row_value_expression comp_op row_value_expression
353         { result = nd(:PREDICATE, val) }
354         
355   row_value_expression
356     : IDENTIFIER
357     | literal
358     
359   comp_op
360     : '='        { result = nd(:COMP_OP, '>=', val) }
361     | '<'        { result = nd(:COMP_OP, '>=', val) }
362     | '<' '='    { result = nd(:COMP_OP, '>=', val) }
363     | '>'        { result = nd(:COMP_OP, '>=', val) }
364     | '>' '='    { result = nd(:COMP_OP, '>=', val) }
365 end
366
367 ----header
368 require 'strscan'
369
370 ----inner
371 def constraint(type, *args)
372   all_elements = args.pop
373   nd(:CONSTRAINT, SQL::Table::Constraint.new(type, args), all_elements)
374 end
375
376 def index(name, columns, all_elements)
377   nd(:INDEX, SQL::Table::Index.new(name, columns), all_elements)
378 end
379
380 def procedure(name, parameters, annotations, all_elements)
381   nd(:CREATE_PROCEDURE,
382      SQL::Procedure.new(
383        name.value,
384        parameters.to_a,
385        annotations.to_a
386      ),
387      all_elements)
388 end
389
390 def parameter(name, type, annotations, all_elements)
391   nd(:PARAMETER,
392      SQL::Procedure::Parameter.new(
393        name.value,
394        type.value,
395        annotations.to_a
396      ),
397      all_elements)
398 end 
399
400 def table(name, elements, annotations, all_elements)
401   nd(:CREATE_TABLE,
402      SQL::Table.new(name.value, elements.to_a, annotations.to_a),
403      all_elements)
404 end
405
406 def column(name, type, column_options, annotations, all_elements)
407   nd(:COLUMN_DEFINITION,
408      SQL::Table::Column.new(
409        name.value, type.value, column_options.to_a, annotations.to_a
410      ),
411      all_elements)
412 end
413
414 def annotation(type, args, all_elements)
415   type = type.value
416   args = args.to_a
417   nd(:ANNOTATION, SQL::Annotation.new(type, args), all_elements)
418 end
419
420 def datatype(type, length, val)
421   nd(:DATA_TYPE, SQL::DataType.new(type, length), val)
422 end
423
424 def nd(type, value, all_elements=value)
425   node = Node.new(type, value, @source)
426   update_pos(node, all_elements)
427   if value.respond_to?(:source=)
428     value.source = node.source
429   end
430   node
431 end
432
433 def update_pos(node, all_elements)
434   all_elements = all_elements.compact
435   node.start = all_elements.collect{|val| val.start}.compact[0]
436   node.last  = all_elements.collect{|val| val.last }.compact[-1]
437   node
438 end
439
440 class Node
441   attr_reader   :type, :value
442   attr_accessor :start, :last
443   
444   def initialize(type, value, source)
445     @type   = type.to_sym
446     @value  = value
447     @source = source
448   end
449   
450   def push(elem)
451     raise Exception if leaf?
452     @value << elem
453     self
454   end
455   
456   def unshift(elem)
457     raise Exception if leaf?
458     @value.unshift(elem)
459     self
460   end
461   
462   def [](index)
463     if leaf?
464       return index = 0 ? @value : nil
465     end
466     if index.is_a?(Integer)
467       return @value[index]
468     end
469     @value.detect{|val| val.type == index.to_sym}
470   end
471
472   def leaf?
473     !@value.is_a?(Array)
474   end
475   
476   def name
477     self[:IDENTIFIER].value
478   end
479   
480   def children
481     leaf? ? [] : @value
482   end
483   
484   def find(node_type)
485     node_type = node_type.to_sym
486     return self if type == node_type
487     if leaf?
488       return nil
489     end
490     result = nil
491     children.each do |child_node|
492       found = child_node.find(node_type)
493       if found
494         result = found
495         break
496       end
497     end
498     result
499   end
500   
501   def source
502     start_row = @start[0]
503     start_col = @start[1]
504     last_row  = @last[0]
505     last_col  = @last[1]
506
507     result = @source[start_row..last_row]
508     if result.length == 1
509       result[0] = result[0][start_col..last_col]
510     else
511       result[0]  = result[0][start_col..-1]
512       result[-1] = result[-1][0..last_col]
513     end
514     result.join("\n")
515   end
516   
517   def to_a
518     if leaf?
519       return [@value]
520     end
521     @value.collect {|child| child.value}
522   end
523
524   def to_s
525     "#{type}: at line #{start[0]+1}, column #{start[1]+1}\nsource> #{source}"
526   end
527   
528   def inspect
529     to_s
530   end
531 end
532
533 class ASTWalker
534   def walk(ast)
535     walk_node(ast)
536     self
537   end
538   
539   def initialize
540     @listeners = Hash.new {|hash, key| hash[key] = []}
541   end
542   
543   def visit(note_type, &block)
544     node_type = note_type.to_sym
545     @listeners[node_type] << block
546     self
547   end
548   
549   private
550   def walk_node(node)
551     if @listeners.has_key?(node.type)
552       @listeners[node.type].each do |listener|
553         listener.call(node)
554       end
555     end
556     node.children.each do |child_node|
557 p node if child_node.nil?
558       walk_node(child_node)
559     end
560   end
561 end
562
563 # token
564 PUNCTUATION = /([;\.,()=@*<>\[\]]|\$\$|\/\/|--)/
565 ONE_LINE_COMMENT = /--\s[^@]*$/
566 KEYWORD_LIST = [
567   #general term
568   'NOT',
569   'NULL',
570   'DEFAULT',
571   'AS',
572
573   # sql command
574   'CREATE',
575   'TABLE',
576   'VIEW',
577   'PROCEDURE',
578   'INSERT',
579   'INTO',
580   'VALUES',
581   'DELIMITER',
582   
583   # table option
584   'ENGINE',
585   'MYISAM',
586   'INNODB',
587   'HEAP',
588   
589   # column option
590   'AUTO_INCREMENT',
591   
592   # data type
593   'INT',
594   'CHAR',
595   'VARCHAR',
596   'TEXT',
597   'INTEGER',
598   'TINYINT',
599   'SMALLINT',
600   'BIT',
601   'DATETIME',
602   'TIMESTAMP',
603   
604   # procedure / function
605   'BEGIN',
606   'END',
607   'IN',
608   'OUT',
609   'INOUT',
610   
611   # constraint
612   'CONSTRAINT',
613   'PRIMARY',
614   'FOREIGN',
615   'REFERENCES',
616   'UNIQUE',
617   'INDEX',
618   'KEY',
619   'FULLTEXT',
620   'USING',
621   'SECTIONALIZE',
622   
623   #query
624   'SELECT',
625   'FROM',
626   'WHERE',
627   
628   #dml
629   'DELETE',
630   'UPDATE',
631   
632   #boolean expression
633   'TRUE',
634   'FALSE',
635   'IS',
636   
637   #triggered action
638   'ON',
639   'CASCADE'
640 ]
641 KEYWORD = /(#{KEYWORD_LIST.collect{|k| '\b'+k+'\b'}.join('|')})/ix
642 IDENTIFIER = /([a-zA-Z][_0-9a-zA-Z]*)/
643
644 # NUMBER
645 NUMERIC = /([1-9][0-9]*)/
646 INTEGER = /([+|-]?[1-9][0-9]*|0)/
647 FLOAT   = /(
648             (?: #without exponent part
649               [+|-]?(?:[1-9][0-9]*|0)(?:\.[0-9]+)?
650             |   #with exponent part
651               [+|-]?[1-9]+(?:\.[0-9]+)?e[+|-]?[1-9][0-9]*
652             )
653           )/x
654
655 # STRING
656 NOT_PRINTABLE = '\x00-\x1F\x7F' #including space (\x20)
657 MUST_BE_ESCAPED = '\x22\x27\x5C' #quot, double-quot, back-slash
658 ESCAPE_TABLE = {
659   '\n'   => "\n",
660   '\b'   => "\b",
661   '\0'   => "\x00",
662   '\t'   => "\t",
663   "\\'"  => "'",
664   '\"'   => '"',
665   "\\\\" => "\\"
666
667 ESCAPED = ESCAPE_TABLE.keys.map{|k|k.gsub(/\\/, "\\\\\\\\")}.join('|')
668 CHARACTER_STRING = /(
669   #single-quoted string
670   ' 
671     (?:
672       [^#{MUST_BE_ESCAPED}#{NOT_PRINTABLE}]
673     |
674       #{ESCAPED}
675     |
676       ''
677     |
678       "
679     )*
680   '
681   |
682   #double-quoted string
683   "
684     (?:
685       [^#{MUST_BE_ESCAPED}#{NOT_PRINTABLE}]
686     |
687       #{ESCAPED}
688     |
689       ""
690     |
691       '
692     )*
693   "
694 )/x
695
696 # PROCEDURE
697 PARAMETER_NAME = /([_a-zA-Z][_0-9a-zA-Z]*)/
698
699 def parse(str)
700   @source = str.split(/\n/)
701   @lines  = @source.dup
702   @total_lines = @lines.length
703   next_line
704   do_parse
705 end
706
707 def next_line
708   @line = @lines.empty? \
709         ? nil \
710         : StringScanner.new(@lines.shift)
711 end
712
713 def current_line
714   @total_lines - (@lines.length + 1)
715 end
716
717 def pos
718   [current_line, @line.pos]
719 end
720
721 def next_token
722   token = _next_token
723   return nil unless token
724   token[1].start = @start_pos
725   token[1].last  = [pos[0], pos[1]-1]
726 #puts token
727   return token
728 end
729
730 def _next_token
731   return nil unless @line
732   @line.skip(/\s*/)
733   if @in_procedure_code
734     if token = @line.skip_until(/(?=\bEND(?!\s+(?:IF|CASE|WHILE|LOOP))\b)/i)
735       @in_procedure_code = false
736       return tkn(:PROCEDURE_CODE)
737     end
738     @line.terminate
739   end
740   if @line.eos?
741     next_line
742     return _next_token
743   end
744   @start_pos = pos
745   if token = @line.scan(ONE_LINE_COMMENT)
746     return _next_token
747   elsif token = @line.scan(PUNCTUATION)
748     return tkn(token)
749   elsif token = @line.scan(KEYWORD)
750     token = token.upcase
751     if token == 'BEGIN'
752       @in_procedure_code = true
753     end
754     return tkn(token)
755   elsif token = @line.scan(IDENTIFIER)
756     return tkn(:IDENTIFIER, token)
757   elsif token = @line.scan(PARAMETER_NAME)
758     return tkn(:PARAMETER_NAME, token)
759   elsif token = @line.scan(CHARACTER_STRING)
760     return tkn(
761       :CHARACTER_STRING,
762       token[1..-2].gsub(/#{ESCAPED}/x) {|s|ESCAPE_TABLE[s]}\
763                   .gsub(/""/, '"')\
764                   .gsub(/''/, "\'")
765     )
766   elsif token = @line.scan(NUMERIC)
767     return tkn(:NUMERIC, token.to_i)
768   elsif token = @line.scan(INTEGER)
769     return tkn(:INTEGER, token.to_i)
770   elsif token = @line.scan(FLOAT)
771     return tkn(:FLOAT, token.to_f)
772   else
773     return tkn(:UNEXPECTED_TOKEN, @line.scan(/.*/))
774   end
775 end
776
777 def tkn(type, value=type)
778   [type, Node.new(type, value, @source)]
779 end
780