sql_tree 0.1.1 → 0.2.0

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.
data/.gitignore CHANGED
@@ -1,3 +1,6 @@
1
1
  /pkg
2
2
  /tmp
3
3
  /doc
4
+ .bundle
5
+ Gemfile.lock
6
+ *.rbc
@@ -0,0 +1,8 @@
1
+ infinity_test do
2
+
3
+ use :rubies => %w(1.8.7 1.9.2 ree jruby rbx), :test_framework => :rspec
4
+
5
+ before(:each_ruby) do |environment|
6
+ environment.system('bundle install')
7
+ end
8
+ end
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :gemcutter
2
+ gemspec
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Willem van Bergen
1
+ Copyright (c) 2009-2010 Willem van Bergen and contributors
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -12,9 +12,11 @@ yet stable and not all SQL constructs are implemented yet.
12
12
 
13
13
  == Installation
14
14
 
15
- The SQLTree library is distributed as a gem on Gemcutter.org. To install:
15
+ The SQLTree library is distributed as a gem. To install:
16
16
 
17
- gem install sql_tree --source http://gemcutter.org
17
+ gem install sql_tree
18
+
19
+ Or, add <tt>gem 'sql_tree'</tt> to your project's Gemfile and run <tt>bundle install</tt>.
18
20
 
19
21
  == Usage
20
22
 
@@ -33,5 +35,5 @@ This library is written by Willem van Bergen and is MIT licensed (see the
33
35
  LICENSE file).
34
36
 
35
37
  * Full RDoc API documentation can be found at http://rdoc.info/projects/wvanbergen/sql_tree
36
- * See the project wiki at http://wiki.github.com/wvanbergen/sql_tree for more
38
+ * See the project wiki at http://github.com/wvanbergen/sql_tree/wiki for more
37
39
  information about using this library.
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- Dir['tasks/*.rake'].each { |file| load(file) }
2
-
1
+ Dir['tasks/*.rb'].each { |file| load(file) }
3
2
  GithubGem::RakeTasks.new(:gem)
4
-
5
3
  task :default => [:spec]
@@ -5,6 +5,8 @@
5
5
  # necessary files for the gem to function properly.
6
6
  module SQLTree
7
7
 
8
+ VERSION = "0.2.0"
9
+
8
10
  class << self
9
11
  # The character to quote variable names with.
10
12
  attr_accessor :identifier_quote_char
@@ -13,38 +15,14 @@ module SQLTree
13
15
  # Set default quote characters
14
16
  self.identifier_quote_char = '"'
15
17
 
16
- # Loads constants in the SQLTree namespace using self.load_default_class_file(base, const)
17
- # <tt>const</tt>:: The constant that is not yet loaded in the SQLTree namespace. This should be passed as a string or symbol.
18
- def self.const_missing(const)
19
- load_default_class_file(SQLTree, const)
20
- end
21
-
22
- # Loads constants that reside in the SQLTree tree using the constant name
23
- # and its base constant to determine the filename.
24
- # <tt>base</tt>:: The base constant to load the constant from. This should be Foo when the constant Foo::Bar is being loaded.
25
- # <tt>const</tt>:: The constant to load from the base constant as a string or symbol. This should be 'Bar' or :Bar when the constant Foo::Bar is being loaded.
26
- def self.load_default_class_file(base, const)
27
- require "#{to_underscore("#{base.name}::#{const}")}"
28
- base.const_get(const) if base.const_defined?(const)
29
- end
30
-
31
18
  # The <tt>[]</tt> method is a shorthand for the <tt>SQLTree::Parser.parse</tt>
32
19
  # method to parse an SQL query and return a SQL syntax tree.
33
20
  def self.[](query, options = {})
34
21
  SQLTree::Parser.parse(query)
35
22
  end
36
-
37
- # Convert a string/symbol in camelcase (SQLTree::Parser) to underscores (sql_tree/parser)
38
- # This function can be used to load the file (using require) in which the given constant is defined.
39
- # <tt>str</tt>:: The string to convert in the following format: <tt>ModuleName::ClassName</tt>
40
- def self.to_underscore(str)
41
- str.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
42
- end
43
-
44
- # Convert a string/symbol in underscores (<tt>sql_tree/parser</tt>) to camelcase
45
- # (<tt>SqlTree::Parser</tt>). This can be used to find the class that is defined in a given filename.
46
- # <tt>str</tt>:: The string to convert in the following format: <tt>module_name/class_name</tt>
47
- def self.to_camelcase(str)
48
- str.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
49
- end
50
23
  end
24
+
25
+ require 'sql_tree/token'
26
+ require 'sql_tree/tokenizer'
27
+ require 'sql_tree/node'
28
+ require 'sql_tree/parser'
@@ -1,11 +1,5 @@
1
1
  module SQLTree::Node
2
2
 
3
- # Auto-loades files for Node subclasses that reside in the
4
- # node directory, based on the classname.
5
- def self.const_missing(const)
6
- SQLTree.load_default_class_file(SQLTree::Node, const)
7
- end
8
-
9
3
  # The SQLTree::Node::Base class is the superclass for all node
10
4
  # types that are used to represent SQL queries.
11
5
  #
@@ -105,4 +99,22 @@ module SQLTree::Node
105
99
  self.parse(parser)
106
100
  end
107
101
  end
108
- end
102
+ end
103
+
104
+ require 'sql_tree/node/expression'
105
+
106
+ require 'sql_tree/node/select_query'
107
+ require 'sql_tree/node/select_declaration'
108
+ require 'sql_tree/node/table_reference'
109
+ require 'sql_tree/node/source'
110
+ require 'sql_tree/node/join'
111
+ require 'sql_tree/node/ordering'
112
+
113
+ require 'sql_tree/node/insert_query'
114
+ require 'sql_tree/node/update_query'
115
+ require 'sql_tree/node/delete_query'
116
+ require 'sql_tree/node/set_query'
117
+
118
+ require 'sql_tree/node/begin_statement'
119
+ require 'sql_tree/node/commit_statement'
120
+ require 'sql_tree/node/rollback_statement'
@@ -0,0 +1,12 @@
1
+ module SQLTree::Node
2
+ class BeginStatement < Base
3
+ def to_sql(options = {})
4
+ "BEGIN"
5
+ end
6
+
7
+ def self.parse(tokens)
8
+ tokens.consume(SQLTree::Token::BEGIN)
9
+ return self.new
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module SQLTree::Node
2
+ class CommitStatement < Base
3
+ def to_sql(options = {})
4
+ "COMMIT"
5
+ end
6
+
7
+ def self.parse(tokens)
8
+ tokens.consume(SQLTree::Token::COMMIT)
9
+ return self.new
10
+ end
11
+ end
12
+ end
@@ -49,6 +49,11 @@ module SQLTree::Node
49
49
  else
50
50
  Variable.parse(tokens)
51
51
  end
52
+ elsif SQLTree::Token::STRING_ESCAPE == tokens.peek
53
+ tokens.consume(SQLTree::Token::STRING_ESCAPE)
54
+ Value.parse(tokens)
55
+ elsif SQLTree::Token::INTERVAL === tokens.peek
56
+ IntervalValue.parse(tokens)
52
57
  else
53
58
  Value.parse(tokens)
54
59
  end
@@ -333,7 +338,35 @@ module SQLTree::Node
333
338
  return function_call
334
339
  end
335
340
  end
336
-
341
+
342
+
343
+ # Represents a postgresql INTERVAL value. Example: interval '2 days'.
344
+ #
345
+ # The value is the literal text of the interval (e.g. "2 days").
346
+ class IntervalValue < SQLTree::Node::Expression
347
+ # The actual value this node represents.
348
+ leaf :value
349
+
350
+ def initialize(value) # :nodoc:
351
+ @value = value
352
+ end
353
+
354
+ # Generates an SQL representation for this value.
355
+ def to_sql(options = {})
356
+ "interval " + quote_str(@value)
357
+ end
358
+
359
+ def self.parse(tokens)
360
+ tokens.consume(SQLTree::Token::INTERVAL)
361
+ if SQLTree::Token::String === tokens.peek
362
+ self.new(tokens.next.literal)
363
+ else
364
+ raise SQLTree::Parser::UnexpectedToken.new(tokens.current, :literal)
365
+ end
366
+ end
367
+ end
368
+
369
+
337
370
  # Represents alitreal value in an SQL expression. This node is a leaf node
338
371
  # and thus has no child nodes.
339
372
  #
@@ -345,14 +378,14 @@ module SQLTree::Node
345
378
  # * an integer or decimal value, which is represented by an appropriate
346
379
  # <tt>Numeric</tt> instance.
347
380
  class Value < SQLTree::Node::Expression
348
-
381
+
349
382
  # The actual value this node represents.
350
383
  leaf :value
351
384
 
352
385
  def initialize(value) # :nodoc:
353
386
  @value = value
354
387
  end
355
-
388
+
356
389
  # Generates an SQL representation for this value.
357
390
  #
358
391
  # This method supports nil, string, numeric, date and time values.
@@ -361,11 +394,11 @@ module SQLTree::Node
361
394
  # within an SQL query
362
395
  def to_sql(options = {})
363
396
  case value
364
- when nil then 'NULL'
365
- when String then quote_str(@value)
366
- when Numeric then @value.to_s
367
- when Date then @value.strftime("'%Y-%m-%d'")
368
- when DateTime, Time then @value.strftime("'%Y-%m-%d %H:%M:%S'")
397
+ when nil; 'NULL'
398
+ when String; quote_str(@value)
399
+ when Numeric; @value.to_s
400
+ when Date; @value.strftime("'%Y-%m-%d'")
401
+ when DateTime, Time; @value.strftime("'%Y-%m-%d %H:%M:%S'")
369
402
  else raise "Don't know how te represent this value in SQL!"
370
403
  end
371
404
  end
@@ -389,7 +422,7 @@ module SQLTree::Node
389
422
  end
390
423
  end
391
424
  end
392
-
425
+
393
426
  # Represents a variable within an SQL expression. This is a leaf node, so it
394
427
  # does not have any child nodes. A variale can point to a field of a table or
395
428
  # to another expression that was declared elsewhere.
@@ -3,15 +3,18 @@ module SQLTree::Node
3
3
  class Join < Base
4
4
 
5
5
  leaf :join_type
6
+ leaf :is_outer
6
7
  child :table_reference
7
8
  child :join_expression
8
9
 
9
10
  def initialize(values = {})
11
+ self.is_outer = false
10
12
  values.each { |key, value| self.send(:"#{key}=", value) }
11
13
  end
12
14
 
13
15
  def to_sql(options = {})
14
16
  join_sql = join_type ? "#{join_type.to_s.upcase} " : ""
17
+ join_sql << "OUTER " if is_outer
15
18
  join_sql << "JOIN #{table_reference.to_sql(options)} "
16
19
  join_sql << "ON #{join_expression.to_sql(options)}"
17
20
  join_sql
@@ -35,6 +38,11 @@ module SQLTree::Node
35
38
  join.join_type = tokens.next.literal.downcase.to_sym
36
39
  end
37
40
 
41
+ if [:right, :left].include?(join.join_type) && tokens.peek.class == SQLTree::Token::OUTER
42
+ join.is_outer = true
43
+ tokens.consume(SQLTree::Token::OUTER)
44
+ end
45
+
38
46
  tokens.consume(SQLTree::Token::JOIN)
39
47
  join.table_reference = SQLTree::Node::TableReference.parse(tokens)
40
48
  tokens.consume(SQLTree::Token::ON)
@@ -0,0 +1,12 @@
1
+ module SQLTree::Node
2
+ class RollbackStatement < Base
3
+ def to_sql(options = {})
4
+ "ROLLBACK"
5
+ end
6
+
7
+ def self.parse(tokens)
8
+ tokens.consume(SQLTree::Token::ROLLBACK)
9
+ return self.new
10
+ end
11
+ end
12
+ end
@@ -1,5 +1,17 @@
1
1
  module SQLTree::Node
2
2
 
3
+ class SelectExpression < Base
4
+
5
+ def self.parse(tokens)
6
+ case tokens.peek
7
+ when SQLTree::Token::COUNT
8
+ SQLTree::Node::CountAggregrate.parse(tokens)
9
+ else
10
+ SQLTree::Node::Expression.parse(tokens)
11
+ end
12
+ end
13
+ end
14
+
3
15
  class SelectDeclaration < Base
4
16
 
5
17
  child :expression
@@ -30,8 +42,7 @@ module SQLTree::Node
30
42
 
31
43
  else
32
44
 
33
- expression = SQLTree::Node::Expression.parse(tokens)
34
- expr = self.new(:expression => expression)
45
+ expr = self.new(:expression => SQLTree::Node::SelectExpression.parse(tokens))
35
46
  if SQLTree::Token::AS === tokens.peek
36
47
  tokens.consume(SQLTree::Token::AS)
37
48
  if SQLTree::Token::Identifier === tokens.peek
@@ -44,6 +55,61 @@ module SQLTree::Node
44
55
  end
45
56
  end
46
57
  end
58
+
59
+ class CountAggregrate < Base
60
+ leaf :distinct
61
+ child :expression
62
+
63
+ def to_sql(options = {})
64
+ sql = "COUNT("
65
+ sql << "DISTINCT " if distinct
66
+ sql << expression.to_sql(options)
67
+ sql << ')'
68
+ end
69
+
70
+ def self.parse(tokens)
71
+ count_aggregate = self.new(:distinct => false)
72
+
73
+ tokens.consume(SQLTree::Token::COUNT)
74
+ tokens.consume(SQLTree::Token::LPAREN)
75
+
76
+ # Handle DISTINCT
77
+ distinct_parens = false
78
+ if SQLTree::Token::DISTINCT === tokens.peek
79
+ tokens.consume(SQLTree::Token::DISTINCT)
80
+ count_aggregate.distinct = true
81
+ if SQLTree::Token::LPAREN === tokens.peek
82
+ tokens.consume(SQLTree::Token::LPAREN)
83
+ distinct_parens = true
84
+ end
85
+ end
86
+
87
+ if SQLTree::Token::MULTIPLY === tokens.peek
88
+
89
+ # "COUNT(*)"
90
+ tokens.consume(SQLTree::Token::MULTIPLY)
91
+ count_aggregate.expression = SQLTree::Node::ALL_FIELDS
92
+
93
+ elsif SQLTree::Token::Identifier === tokens.peek(1) &&
94
+ SQLTree::Token::DOT === tokens.peek(2) &&
95
+ SQLTree::Token::MULTIPLY === tokens.peek(3)
96
+
97
+ # "COUNT(table.*)"
98
+ table = tokens.next.literal
99
+ tokens.consume(SQLTree::Token::DOT, SQLTree::Token::MULTIPLY)
100
+ count_aggregate.expression = SQLTree::Node::AllFieldsDeclaration.new(table)
101
+
102
+ else
103
+
104
+ count_aggregate.expression = SQLTree::Node::Expression.parse(tokens)
105
+ end
106
+
107
+ tokens.consume(SQLTree::Token::RPAREN) if distinct_parens
108
+ tokens.consume(SQLTree::Token::RPAREN)
109
+
110
+ return count_aggregate
111
+ end
112
+ end
47
113
 
48
114
  class AllFieldsDeclaration < Base
49
115
 
@@ -0,0 +1,38 @@
1
+ module SQLTree::Node
2
+
3
+ # The <tt>DeleteQuery</tt> node represents an SQL DELETE query.
4
+ #
5
+ # This node has two children: <tt>table</tt> and <tt>where</tt>.
6
+ class SetQuery < Base
7
+
8
+ # The variable (<tt>SQLTree::Node::Field</tt>) that is being set.
9
+ child :variable
10
+
11
+ # The <tt>SQLTree::Node::Expression</tt> value that the variable is being
12
+ # set to.
13
+ child :value
14
+
15
+ # Initializes a new DeleteQuery instance.
16
+ def initialize(variable, value)
17
+ @variable, @value = variable, value
18
+ end
19
+
20
+ # Generates an SQL DELETE query from this node.
21
+ def to_sql(options = {})
22
+ sql = "SET #{variable.to_sql(options)}"
23
+ sql << " TO #{value.to_sql(options)}"
24
+ sql
25
+ end
26
+
27
+ # Parses a SET query from a stream of tokens.
28
+ # <tt>tokens</tt>:: The token stream to parse from, which is an instance
29
+ # of <tt> SQLTree::Parser</tt>.
30
+ def self.parse(tokens)
31
+ tokens.consume(SQLTree::Token::SET)
32
+ variable = SQLTree::Node::Expression::Field.parse(tokens)
33
+ tokens.consume(SQLTree::Token::TO)
34
+ value = SQLTree::Node::Expression::Value.parse(tokens)
35
+ return self.new(variable, value)
36
+ end
37
+ end
38
+ end
@@ -112,10 +112,14 @@ class SQLTree::Parser
112
112
  #
113
113
  def parse!
114
114
  case self.peek
115
- when SQLTree::Token::SELECT then SQLTree::Node::SelectQuery.parse(self)
116
- when SQLTree::Token::INSERT then SQLTree::Node::InsertQuery.parse(self)
117
- when SQLTree::Token::DELETE then SQLTree::Node::DeleteQuery.parse(self)
118
- when SQLTree::Token::UPDATE then SQLTree::Node::UpdateQuery.parse(self)
115
+ when SQLTree::Token::SELECT; SQLTree::Node::SelectQuery.parse(self)
116
+ when SQLTree::Token::INSERT; SQLTree::Node::InsertQuery.parse(self)
117
+ when SQLTree::Token::DELETE; SQLTree::Node::DeleteQuery.parse(self)
118
+ when SQLTree::Token::UPDATE; SQLTree::Node::UpdateQuery.parse(self)
119
+ when SQLTree::Token::BEGIN; SQLTree::Node::BeginStatement.parse(self)
120
+ when SQLTree::Token::COMMIT; SQLTree::Node::CommitStatement.parse(self)
121
+ when SQLTree::Token::ROLLBACK; SQLTree::Node::RollbackStatement.parse(self)
122
+ when SQLTree::Token::SET; SQLTree::Node::SetQuery.parse(self)
119
123
  else raise UnexpectedToken.new(self.peek)
120
124
  end
121
125
  end