sql_tree 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ module SQLTree::Node
2
+
3
+ class SelectExpression < Base
4
+
5
+ attr_accessor :expression, :variable
6
+
7
+ def initialize(expression, variable = nil)
8
+ @expression = expression
9
+ @variable = variable
10
+ end
11
+
12
+ def to_sql
13
+ sql = @expression.to_sql
14
+ sql << " AS " << quote_var(@variable) if @variable
15
+ return sql
16
+ end
17
+
18
+ def self.parse(tokens)
19
+ if tokens.peek == SQLTree::Token::MULTIPLY
20
+ tokens.consume(SQLTree::Token::MULTIPLY)
21
+ return SQLTree::Node::ALL_FIELDS
22
+ else
23
+ expression = SQLTree::Node::Expression.parse(tokens)
24
+ expr = SQLTree::Node::SelectExpression.new(expression)
25
+ if tokens.peek == SQLTree::Token::AS
26
+ tokens.consume(SQLTree::Token::AS)
27
+ expr.variable = SQLTree::Node::Variable.parse(tokens).name
28
+ end
29
+ return expr
30
+ end
31
+ end
32
+
33
+ def ==(other)
34
+ other.expression == self.expression && other.variable == self.variable
35
+ end
36
+ end
37
+
38
+ class AllFieldsExpression < Expression
39
+ def to_sql
40
+ '*'
41
+ end
42
+ end
43
+
44
+ ALL_FIELDS = AllFieldsExpression.new
45
+ end
@@ -0,0 +1,78 @@
1
+ module SQLTree::Node
2
+
3
+ class SelectQuery < Base
4
+
5
+ attr_accessor :distinct, :select, :from, :where, :group_by, :having, :order_by, :limit
6
+
7
+ def initialize
8
+ @distinct = false
9
+ @select = []
10
+ end
11
+
12
+ def to_sql
13
+ raise "At least one SELECT expression is required" if self.select.empty?
14
+ sql = (self.distinct) ? "SELECT DISTINCT " : "SELECT "
15
+ sql << select.map { |s| s.to_sql }.join(', ')
16
+ sql << " FROM " << from.map { |f| f.to_sql }.join(', ')
17
+ sql << " WHERE " << where.to_sql if where
18
+ return sql
19
+ end
20
+
21
+ # Uses the provided initialized parser to parse a SELECT query.
22
+ def self.parse(tokens)
23
+ select_node = self.new
24
+ tokens.consume(SQLTree::Token::SELECT)
25
+
26
+ if tokens.peek == SQLTree::Token::DISTINCT
27
+ tokens.consume(SQLTree::Token::DISTINCT)
28
+ select_node.distinct = true
29
+ end
30
+
31
+ select_node.select = self.parse_select_clause(tokens)
32
+ select_node.from = self.parse_from_clause(tokens) if tokens.peek == SQLTree::Token::FROM
33
+ select_node.where = self.parse_where_clause(tokens) if tokens.peek == SQLTree::Token::WHERE
34
+
35
+ return select_node
36
+ end
37
+
38
+ def self.parse_select_clause(tokens)
39
+ expressions = [SQLTree::Node::SelectExpression.parse(tokens)]
40
+ while tokens.peek == SQLTree::Token::COMMA
41
+ tokens.consume(SQLTree::Token::COMMA)
42
+ expressions << SQLTree::Node::SelectExpression.parse(tokens)
43
+ end
44
+ return expressions
45
+ end
46
+
47
+ def self.parse_from_clause(tokens)
48
+ tokens.consume(SQLTree::Token::FROM)
49
+ sources = [SQLTree::Node::Source.parse(tokens)]
50
+ while tokens.peek == SQLTree::Token::COMMA
51
+ tokens.consume(SQLTree::Token::COMMA)
52
+ sources << SQLTree::Node::Source.parse(tokens)
53
+ end
54
+ return sources
55
+ end
56
+
57
+ def self.parse_where_clause(tokens)
58
+ tokens.consume(SQLTree::Token::WHERE)
59
+ Expression.parse(tokens)
60
+ end
61
+
62
+ def self.parse_group_clause(tokens)
63
+ # TODO: implement me
64
+ end
65
+
66
+ def self.parse_having_clause(tokens)
67
+ # TODO: implement me
68
+ end
69
+
70
+ def self.parse_order_clause(tokens)
71
+ # TODO: implement me
72
+ end
73
+
74
+ def self.parse_limit_clause(tokens)
75
+ # TODO: implement me
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,37 @@
1
+ module SQLTree::Node
2
+
3
+ class Source < Base
4
+
5
+ attr_accessor :table_reference, :joins
6
+
7
+ def initialize(table_reference, joins = [])
8
+ @table_reference, @joins = table_reference, joins
9
+ end
10
+
11
+ def table
12
+ table_reference.table
13
+ end
14
+
15
+ def table_alias
16
+ table_reference.table_alias
17
+ end
18
+
19
+ def to_sql
20
+ sql = table_reference.to_sql
21
+ sql << ' ' << joins.map { |j| j.to_sql }.join(' ') if joins.any?
22
+ return sql
23
+ end
24
+
25
+ def ==(other)
26
+ other.table_reference = self.table_reference && other.joins == self.joins
27
+ end
28
+
29
+ def self.parse(tokens)
30
+ source = self.new(SQLTree::Node::TableReference.parse(tokens))
31
+ while tokens.peek && tokens.peek.join?
32
+ source.joins << Join.parse(tokens)
33
+ end
34
+ return source
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ module SQLTree::Node
2
+
3
+ class TableReference < Base
4
+
5
+ attr_accessor :table, :table_alias
6
+
7
+ def initialize(table, table_alias = nil)
8
+ @table, @table_alias = table, table_alias
9
+ end
10
+
11
+ def to_sql
12
+ sql = quote_var(table)
13
+ sql << " AS " << quote_var(table_alias) if table_alias
14
+ return sql
15
+ end
16
+
17
+ def ==(other)
18
+ other.table = self.table && other.table_alias == self.table_alias
19
+ end
20
+
21
+ def self.parse(tokens)
22
+ if SQLTree::Token::Variable === tokens.next
23
+ table_reference = self.new(tokens.current.literal)
24
+ if tokens.peek == SQLTree::Token::AS || SQLTree::Token::Variable === tokens.peek
25
+ tokens.consume(SQLTree::Token::AS) if tokens.peek == SQLTree::Token::AS
26
+ table_reference.table_alias = SQLTree::Node::Variable.parse(tokens).name
27
+ end
28
+ return table_reference
29
+ else
30
+ raise SQLTree::Parser::UnexpectedToken.new(tokens.current)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ module SQLTree::Node
2
+
3
+ class Value < Base
4
+ attr_accessor :value
5
+
6
+ def initialize(value)
7
+ @value = value
8
+ end
9
+
10
+ def to_sql
11
+ case value
12
+ when nil then 'NULL'
13
+ when String then quote_str(@value)
14
+ else @value.to_s
15
+ end
16
+ end
17
+
18
+ def to_tree
19
+ @value
20
+ end
21
+
22
+ def ==(other)
23
+ other.kind_of?(self.class) && other.value == self.value
24
+ end
25
+
26
+ def self.parse(tokens)
27
+ case tokens.next
28
+ when SQLTree::Token::String, SQLTree::Token::Number
29
+ SQLTree::Node::Value.new(tokens.current.literal)
30
+ when SQLTree::Token::NULL
31
+ SQLTree::Node::Value.new(nil)
32
+ else
33
+ raise SQLTree::Parser::UnexpectedToken.new(tokens.current, :literal)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ module SQLTree::Node
2
+
3
+ class Variable < Base
4
+
5
+ attr_accessor :name
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+
11
+ def to_sql
12
+ quote_var(@name)
13
+ end
14
+
15
+ def to_tree
16
+ @name.to_sym
17
+ end
18
+
19
+ def ==(other)
20
+ other.name == self.name
21
+ end
22
+
23
+ def self.parse(tokens)
24
+ if SQLTree::Token::Variable === tokens.peek
25
+ if tokens.peek(2) == SQLTree::Token::DOT
26
+ SQLTree::Node::Field.parse(tokens)
27
+ else
28
+ self.new(tokens.next.literal)
29
+ end
30
+ else
31
+ raise SQLTree::Parser::UnexpectedToken.new(tokens.peek, :variable)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,63 @@
1
+ class SQLTree::Parser
2
+
3
+ class UnexpectedToken < StandardError
4
+
5
+ attr_reader :expected_token, :actual_token
6
+
7
+ def initialize(actual_token, expected_token = nil)
8
+ @expected_token, @actual_token = expected_token, actual_token
9
+ message = "Unexpected token: found #{actual_token.inspect}"
10
+ message << ", but expected #{expected_token.inspect}" if expected_token
11
+ message << '!'
12
+ super(message)
13
+ end
14
+ end
15
+
16
+ def self.parse(sql_string, options = {})
17
+ self.new(sql_string, options).parse!
18
+ end
19
+
20
+ attr_reader :options
21
+
22
+ def initialize(tokens, options)
23
+ if tokens.kind_of?(String)
24
+ @tokens = SQLTree::Tokenizer.new.tokenize(tokens)
25
+ else
26
+ @tokens = tokens
27
+ end
28
+ @options = options
29
+ end
30
+
31
+ def current
32
+ @current_token
33
+ end
34
+
35
+ def next
36
+ @current_token = @tokens.shift
37
+ end
38
+
39
+ def consume(*checks)
40
+ checks.each do |check|
41
+ raise UnexpectedToken.new(self.current, check) unless check == self.next
42
+ end
43
+ end
44
+
45
+ def peek(distance = 1)
46
+ @tokens[distance - 1]
47
+ end
48
+
49
+ def peek_tokens(amount)
50
+ @tokens[0, amount]
51
+ end
52
+
53
+ def debug
54
+ puts @tokens.inspect
55
+ end
56
+
57
+ def parse!
58
+ case self.peek
59
+ when SQLTree::Token::SELECT then SQLTree::Node::SelectQuery.parse(self)
60
+ else raise UnexpectedToken.new(self.peek)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,131 @@
1
+ # The <tt>SQLTree::Token</tt> class is the base class for every token
2
+ # in the SQL language. Actual tokens are represented by a subclass.
3
+ #
4
+ # Tokens are produced by the <tt>SQLTree::Tokenizer</tt> from a string
5
+ # and are consumed by the <tt>SQLTree::Parser</tt> to construct an
6
+ # abstract syntax tree for the query that is being parsed.
7
+ class SQLTree::Token
8
+
9
+ # For some tokens, the encountered literal value is important
10
+ # during the parsing phase (e.g. strings and variable names).
11
+ # Therefore, the literal value encountered that represented the
12
+ # token in the original SQL query string is stored.
13
+ attr_accessor :literal
14
+
15
+ # Creates a token instance with a given literal representation.
16
+ #
17
+ # <tt>literal<tt>:: The literal string value that was encountered
18
+ # while tokenizing.
19
+ def initialize(literal)
20
+ @literal = literal
21
+ end
22
+
23
+ # Compares two tokens. Tokens are considered equal when they are
24
+ # instances of the same class, i.e. do literal is not used.
25
+ def ==(other)
26
+ other.class == self.class
27
+ end
28
+
29
+ def inspect # :nodoc:
30
+ literal
31
+ end
32
+
33
+ def join?
34
+ [SQLTree::Token::JOIN, SQLTree::Token::LEFT, SQLTree::Token::RIGHT,
35
+ SQLTree::Token::INNER, SQLTree::Token::OUTER, SQLTree::Token::NATURAL,
36
+ SQLTree::Token::FULL].include?(self)
37
+ end
38
+
39
+ ###################################################################
40
+ # DYNAMIC TOKEN TYPES
41
+ ###################################################################
42
+
43
+ # The <tt>SQLTree::Token::Value</tt> class is the base class for
44
+ # every dynamic token. A dynamic token is a token for which the
45
+ # literal value used remains impoirtant during parsing.
46
+ class Value < SQLTree::Token
47
+
48
+ def inspect # :nodoc:
49
+ "#<#{self.class.name.split('::').last}:#{literal.inspect}>"
50
+ end
51
+
52
+ # Compares two tokens. For values, the literal encountered value
53
+ # of the token is also taken into account besides the class.
54
+ def ==(other)
55
+ other.class == self.class && @literal == other.literal
56
+ end
57
+ end
58
+
59
+ # The <tt>SQLTree::Token::Variable</tt> class represents SQL
60
+ # variables. The variable name is stored in the literal as string,
61
+ # without quotes if they were present.
62
+ class Variable < SQLTree::Token::Value
63
+ end
64
+
65
+ # The <tt>SQLTree::Token::String</tt> class represents strings.
66
+ # The actual string is stored in the literal as string without quotes.
67
+ class String < SQLTree::Token::Value
68
+ end
69
+
70
+ # The <tt>SQLTree::Token::Keyword</tt> class represents numbers.
71
+ # The actual number is stored as an integer or float in the token's
72
+ # literal.
73
+ class Number < SQLTree::Token::Value
74
+ end
75
+
76
+ ###################################################################
77
+ # STATIC TOKEN TYPES
78
+ ###################################################################
79
+
80
+ # The <tt>SQLTree::Token::Keyword</tt> class represents reserved SQL
81
+ # keywords. These keywords are used to structure the query. Keywords
82
+ # are static, i.e. the literal value is not important during the
83
+ # parsing process.
84
+ class Keyword < SQLTree::Token
85
+ def inspect # :nodoc:
86
+ ":#{literal.gsub(/ /, '_').downcase}"
87
+ end
88
+ end
89
+
90
+ # The <tt>SQLTree::Token::Operator</tt> class represents logical and
91
+ # arithmetic operators in SQL. These tokens are static, i.e. the literal
92
+ # value is not important during the parsing process.
93
+ class Operator < SQLTree::Token
94
+ def inspect # :nodoc:
95
+ OPERATORS_HASH[literal].inspect
96
+ end
97
+ end
98
+
99
+ ###################################################################
100
+ # STATIC TOKEN CONSTANTS
101
+ ###################################################################
102
+
103
+ # Create some static token classes and a single instance of them
104
+ LPAREN = Class.new(SQLTree::Token).new('(')
105
+ RPAREN = Class.new(SQLTree::Token).new(')')
106
+ DOT = Class.new(SQLTree::Token).new('.')
107
+ COMMA = Class.new(SQLTree::Token).new(',')
108
+
109
+ # A list of all the SQL reserverd keywords.
110
+ KEYWORDS = %w{SELECT FROM WHERE GOUP HAVING ORDER DISTINCT LEFT RIGHT INNER FULL OUTER NATURAL JOIN USING
111
+ AND OR NOT AS ON IS NULL BY LIKE ILIKE BETWEEN IN}
112
+
113
+ # Create a token for all the reserved keywords in SQL
114
+ KEYWORDS.each { |kw| const_set(kw, Class.new(SQLTree::Token::Keyword).new(kw)) }
115
+
116
+ # A list of keywords that aways occur in fixed combinations. Register these as separate keywords.
117
+ KEYWORD_COMBINATIONS = [%w{IS NOT}, %w{NOT LIKE}, %w{NOT BETWEEN}, %w{NOT ILIKE}]
118
+ KEYWORD_COMBINATIONS.each { |kw| const_set(kw.join('_'), Class.new(SQLTree::Token::Keyword).new(kw.join(' '))) }
119
+
120
+ ARITHMETHIC_OPERATORS_HASH = { '+' => :plus, '-' => :minus, '*' => :multiply, '/' => :divide, '%' => :modulo }
121
+ COMPARISON_OPERATORS_HASH = { '=' => :eq, '!=' => :ne, '<>' => :ne, '>' => :gt, '<' => :lt, '>=' => :gte, '<=' => :lte }
122
+
123
+ # Register a token class and single instance for every token.
124
+ OPERATORS_HASH = ARITHMETHIC_OPERATORS_HASH.merge(COMPARISON_OPERATORS_HASH)
125
+ OPERATORS_HASH.each_pair do |literal, symbol|
126
+ self.const_set(symbol.to_s.upcase, Class.new(SQLTree::Token::Operator).new(literal)) unless self.const_defined?(symbol.to_s.upcase)
127
+ end
128
+
129
+ COMPARISON_OPERATORS = COMPARISON_OPERATORS_HASH.map { |(literal, symbol)| const_get(symbol.to_s.upcase) } +
130
+ [SQLTree::Token::IN, SQLTree::Token::IS, SQLTree::Token::BETWEEN, SQLTree::Token::LIKE, SQLTree::Token::ILIKE, SQLTree::Token::NOT]
131
+ end