1 class Hdboo::SQL::Parser
14 : module_annotation sql_command_list
15 { result = nd(:SQL, val)}
18 : annotation_specification
19 { result = nd(:MODULE_ANNOTATION, val[0].children, val) }
23 { result = nd(:SQL_COMMAND_LIST, [], val) }
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]) }
39 | procedure_definition
45 | qualified_name '.' IDENTIFIER
46 { result = nd(:IDENTIFIER, val[0].value + val[1].value + val[2].value, val)}
49 : 'CREATE' 'TABLE' IDENTIFIER '('
50 annotation_specification table_element_list
52 { result = table(val[2], val[5], val[4], val) }
55 : 'CREATE' 'VIEW' IDENTIFIER annotation_specification
56 'AS' query_specification
57 { result = nd(:CREATE_VIEW, [val[2], val[5], val[3]], val) }
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]) }
67 : 'ENGINE' '=' engine_type
68 { result = nd(:ENGINE, val[2], val)}
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]) }
84 | constraint_definition
87 : IDENTIFIER annotation_specification data_type column_option_list
88 { result = column(val[0], val[2], val[3], val[1], val) }
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]) }
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) }
104 constraint_definition
112 : constraint_name_definition 'PRIMARY' 'KEY' '(' constrained_column_list ')'
113 { result = constraint(:PrimaryKey, val[0].value, val[4].to_a, val) }
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) }
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) }
130 : { result = nd(:TRIGGERED_ACTION, val) }
131 | 'ON' 'DELETE' 'CASCADE' { result = nd(:TRIGGERED_ACTION, val) }
134 : constraint_name_definition 'UNIQUE' '(' constrained_column_list ')'
135 { result = constraint(:Unique, val[0].value, val[3].to_a, val) }
137 constraint_name_definition
138 : { result = nd(:CONSTRAINT_NAME, '-', val) }
139 | 'CONSTRAINT' IDENTIFIER { result = nd(:CONSTRAINT_NAME, val[1].value, val) }
142 : 'INDEX' index_name_definition '(' constrained_column_list ')'
143 { result = index(val[1].value, val[3].value, val) }
146 : 'FULLTEXT' index_name_definition using '(' constrained_column_list ')'
147 { result = index(val[1].value, val[4].value, val) }
150 : 'USING' 'SECTIONALIZE'
151 { result = nd(:USING, val)}
153 index_name_definition
154 : { result = nd(:CONSTRAINT_NAME, '<NO_NAME>', val) }
155 | IDENTIFIER { result = nd(:CONSTRAINT_NAME, val[0].value, val) }
157 constrained_column_list
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]) }
165 : 'CREATE' 'PROCEDURE' IDENTIFIER '(' parameter_list ')'
166 annotation_specification
167 'BEGIN' PROCEDURE_CODE 'END'
168 { result = procedure(val[2], val[4], val[6], val) }
172 { result = nd(:PARAMETER_LIST, [], val)}
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]) }
181 : parameter_direction parameter_name data_type annotation_specification
182 { result = parameter(val[1], val[2], val[3], val) }
194 annotation_specification
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)
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]) }
211 { result = annotation(val[1], [], val) }
212 | '@' IDENTIFIER '(' annotation_argument_list ')'
213 { result = annotation(val[1], val[3], val) }
215 annotation_argument_list
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]) }
229 : 'INSERT' 'INTO' IDENTIFIER insert_columns_and_source
230 { result = nd(:INSERT, val) }
232 insert_columns_and_source
236 : 'VALUES' column_value_expression_list
237 { result = update_pos(val[1], val) }
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]) }
245 column_value_expression
246 : '(' value_expression_list ')'
247 { result = update_pos(val[1], val) }
249 value_expression_list
251 { result = nd(:VALUE_EXPRESSION_LIST, [], val) }
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]) }
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) }
289 : 'SELECT' select_list table_expression
290 { result = nd(:QUERY_SPECIFICATION, val) }
298 | IDENTIFIER ',' select_sublist
301 : from_clause where_clause
302 { result = nd(:TABLE_EXPRESSION, val) }
306 { result = nd(:FROM_CLAUSE, val[1], val)}
309 : 'WHERE' search_condition
310 { result = nd(:WHERE_CLAUSE, val) }
313 : boolean_value_expression
315 boolean_value_expression
317 { result = nd(:BOOLEAN_VALUE_EXPRESSION, val) }
318 | boolean_value_expression 'OR' boolean_term
319 { result = nd(:BOOLEAN_VALUE_EXPRESSION, val) }
323 { result = nd(:BOOLEAN_TERM, val) }
324 | boolean_term 'AND' boolean_facter
325 { result = nd(:BOOLEAN_TERM, val) }
329 { result = nd(:BOOLEAN_FACTER, val) }
331 { result = nd(:BOOLEAN_FACTER, val) }
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) }
349 : comparison_predicate
352 : row_value_expression comp_op row_value_expression
353 { result = nd(:PREDICATE, val) }
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) }
371 def constraint(type, *args)
372 all_elements = args.pop
373 nd(:CONSTRAINT, SQL::Table::Constraint.new(type, args), all_elements)
376 def index(name, columns, all_elements)
377 nd(:INDEX, SQL::Table::Index.new(name, columns), all_elements)
380 def procedure(name, parameters, annotations, all_elements)
381 nd(:CREATE_PROCEDURE,
390 def parameter(name, type, annotations, all_elements)
392 SQL::Procedure::Parameter.new(
400 def table(name, elements, annotations, all_elements)
402 SQL::Table.new(name.value, elements.to_a, annotations.to_a),
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
414 def annotation(type, args, all_elements)
417 nd(:ANNOTATION, SQL::Annotation.new(type, args), all_elements)
420 def datatype(type, length, val)
421 nd(:DATA_TYPE, SQL::DataType.new(type, length), val)
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
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]
441 attr_reader :type, :value
442 attr_accessor :start, :last
444 def initialize(type, value, source)
451 raise Exception if leaf?
457 raise Exception if leaf?
464 return index = 0 ? @value : nil
466 if index.is_a?(Integer)
469 @value.detect{|val| val.type == index.to_sym}
477 self[:IDENTIFIER].value
485 node_type = node_type.to_sym
486 return self if type == node_type
491 children.each do |child_node|
492 found = child_node.find(node_type)
502 start_row = @start[0]
503 start_col = @start[1]
507 result = @source[start_row..last_row]
508 if result.length == 1
509 result[0] = result[0][start_col..last_col]
511 result[0] = result[0][start_col..-1]
512 result[-1] = result[-1][0..last_col]
521 @value.collect {|child| child.value}
525 "#{type}: at line #{start[0]+1}, column #{start[1]+1}\nsource> #{source}"
540 @listeners = Hash.new {|hash, key| hash[key] = []}
543 def visit(note_type, &block)
544 node_type = note_type.to_sym
545 @listeners[node_type] << block
551 if @listeners.has_key?(node.type)
552 @listeners[node.type].each do |listener|
556 node.children.each do |child_node|
557 p node if child_node.nil?
558 walk_node(child_node)
564 PUNCTUATION = /([;\.,()=@*<>\[\]]|\$\$|\/\/|--)/
565 ONE_LINE_COMMENT = /--\s[^@]*$/
604 # procedure / function
641 KEYWORD = /(#{KEYWORD_LIST.collect{|k| '\b'+k+'\b'}.join('|')})/ix
642 IDENTIFIER = /([a-zA-Z][_0-9a-zA-Z]*)/
645 NUMERIC = /([1-9][0-9]*)/
646 INTEGER = /([+|-]?[1-9][0-9]*|0)/
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]*
656 NOT_PRINTABLE = '\x00-\x1F\x7F' #including space (\x20)
657 MUST_BE_ESCAPED = '\x22\x27\x5C' #quot, double-quot, back-slash
667 ESCAPED = ESCAPE_TABLE.keys.map{|k|k.gsub(/\\/, "\\\\\\\\")}.join('|')
668 CHARACTER_STRING = /(
669 #single-quoted string
672 [^#{MUST_BE_ESCAPED}#{NOT_PRINTABLE}]
682 #double-quoted string
685 [^#{MUST_BE_ESCAPED}#{NOT_PRINTABLE}]
697 PARAMETER_NAME = /([_a-zA-Z][_0-9a-zA-Z]*)/
700 @source = str.split(/\n/)
702 @total_lines = @lines.length
708 @line = @lines.empty? \
710 : StringScanner.new(@lines.shift)
714 @total_lines - (@lines.length + 1)
718 [current_line, @line.pos]
723 return nil unless token
724 token[1].start = @start_pos
725 token[1].last = [pos[0], pos[1]-1]
731 return nil unless @line
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)
745 if token = @line.scan(ONE_LINE_COMMENT)
747 elsif token = @line.scan(PUNCTUATION)
749 elsif token = @line.scan(KEYWORD)
752 @in_procedure_code = true
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)
762 token[1..-2].gsub(/#{ESCAPED}/x) {|s|ESCAPE_TABLE[s]}\
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)
773 return tkn(:UNEXPECTED_TOKEN, @line.scan(/.*/))
777 def tkn(type, value=type)
778 [type, Node.new(type, value, @source)]