sql_tree 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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