sql_tree 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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