abstract-sql 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ *.swp
2
+ *~
3
+ *.gem
4
+ .bundle
5
+ Gemfile.lock
6
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use --create ruby-1.9.2@abstract-sql
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in abstract-sql.gemspec
4
+ gemspec
@@ -0,0 +1,23 @@
1
+ # Abstract SQL
2
+
3
+ You ca use this project to transform a SQL string into a Perl SQL::Abstract statement format (as a Hash)
4
+
5
+ ## Usage
6
+
7
+ All you need to do is to add this line in your Gemfile
8
+
9
+ gem 'abstract-sql'
10
+
11
+ Then use it like this :
12
+
13
+ statement = "(id = 1 AND label like '%webo%') OR (id !=1 and label like '%api%')"
14
+ abstract = SQL::Abstract.new
15
+ abstract.parse statement
16
+ #=> {
17
+ :"-or"=>
18
+ [{:"-and"=>[{:"-="=>{:id=>1}}, {:"-like"=>{:label=>"%webo%"}}]},
19
+ {:"-and"=>[{:"-!="=>{:id=>1}}, {:"-like"=>{:label=>"%api%"}}]}
20
+ ]
21
+ }
22
+
23
+
@@ -0,0 +1,24 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/test_*.rb'
7
+ test.verbose = true
8
+ test.options = '--verbose --use-color'
9
+ end
10
+
11
+ begin
12
+ require 'rcov/rcovtask'
13
+ Rcov::RcovTask.new do |test|
14
+ test.libs << 'test'
15
+ test.pattern = 'test/**/test_*.rb'
16
+ test.verbose = true
17
+ test.rcov_opts += ["--exclude /gems/"]
18
+ end
19
+ rescue LoadError
20
+ puts "Install rcov gem : `gem install rcov`"
21
+ end
22
+
23
+ desc "Default task"
24
+ task :default => "test"
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "abstract-sql/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "abstract-sql"
7
+ s.version = Abstract::Sql::VERSION
8
+ s.authors = ["Hallelujah"]
9
+ s.email = ["hery@rails-royce.org"]
10
+ s.homepage = "https://github.com/hallelujah/sql-abstract"
11
+ s.summary = %q{Transform a SQL statement to Perl SQL::Abstract JSON format}
12
+ s.description = %q{It reverse an SQL statement to a Perl SQL::Abstract JSON format.}
13
+
14
+
15
+ s.add_dependency 'parslet'
16
+ s.add_development_dependency 'test-unit', '>= 2.1.0'
17
+ s.add_development_dependency 'rcov'
18
+ s.add_development_dependency 'rake'
19
+
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,23 @@
1
+ require "abstract-sql/version"
2
+ require "abstract-sql/parser/statement"
3
+ require "abstract-sql/transform/statement"
4
+
5
+ module SQL
6
+ class Abstract
7
+
8
+ attr_reader :parser, :transformer
9
+
10
+ def initialize
11
+ @parser = Parser::Statement.new
12
+ @transformer = Transform::Statement.new
13
+ end
14
+
15
+ def parse(sql)
16
+ tree = @parser.parse(sql)
17
+ while tree != (t = @transformer.apply(tree) )
18
+ tree = t
19
+ end
20
+ t
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ require 'parslet'
2
+ module SQL
3
+ module Parser
4
+ class Statement < Parslet::Parser
5
+ # At least one space character (space, tab, new line, carriage return)
6
+ rule(:spaces) { match('\s').repeat(1) }
7
+ rule(:lparen){ spaces? >> str('(') >> spaces?}
8
+ rule(:rparen){ spaces? >> str(')') >> spaces?}
9
+ rule(:spaces?){ spaces.maybe}
10
+ rule(:digit) { match('[0-9]') }
11
+ rule(:int){ digit.repeat(1).as(:int) }
12
+ rule(:float) do
13
+ (str('-').maybe >> (
14
+ str('0') | (match('[1-9]') >> digit.repeat)
15
+ ) >> (
16
+ str('.') >> digit.repeat(1)
17
+ ) >> (
18
+ match('[eE]') >> (str('+') | str('-')).maybe >> digit.repeat(1)
19
+ ).maybe
20
+ ).as(:float)
21
+ end
22
+
23
+ rule(:string) do
24
+ (str('"') >> (str('\\') >> any | str('"').absent? >> any).repeat.as(:string) >> str('"')) |
25
+ (str('\'') >> (str('\\') >> any | str('\'').absent? >> any ).repeat.as(:string) >> str('\''))
26
+ end
27
+
28
+ rule(:boolean_operator) do
29
+ str('and') | str('or') | str('AND') | str('OR')
30
+ end
31
+
32
+ rule(:operator) do
33
+ (str('!=') | str('like') | str('LIKE') | str('=') | str('<=') | str('>=') | str('<') | str('>')).as(:operator)
34
+ end
35
+
36
+ rule(:column) do
37
+ match('[-_0-9a-zA-Z]').repeat.as(:column)
38
+ end
39
+
40
+ rule(:value) do
41
+ spaces? >> (val | column) >> spaces?
42
+ end
43
+
44
+ rule(:val) do
45
+ (string | int | float).as(:val)
46
+ end
47
+
48
+ rule(:entity) do
49
+ (value.as(:lvalue) >> operator >> value.as(:rvalue))
50
+ end
51
+
52
+ rule(:statement) do
53
+ (lparen >> (top | statement | entity) >> rparen) | entity
54
+ end
55
+
56
+ rule(:top) do
57
+ statement.as(:lstatement) >> (boolean_operator.as(:boolean_operator) >> top.as(:rstatement)) | statement.as(:lstatement)
58
+ end
59
+
60
+ root(:top)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,51 @@
1
+ require 'parslet'
2
+ module SQL
3
+ module Transform
4
+ class Statement < Parslet::Transform
5
+ rule(:val => simple(:x)){ x }
6
+ rule(:val =>{:float => simple(:x)}){ String(x).to_f }
7
+ rule(:val =>{:int => simple(:x)}){ String(x).to_i }
8
+ rule(:val =>{:string => simple(:x)}){ String(x) }
9
+ # rule(:boolean_operator => simple(:x)){{ :boolean_operator => String(x).downcase.to_sym }}
10
+ rule(:lvalue => {:column => simple(:x) }, :operator => simple(:op), :rvalue => {:column => simple(:z)}){ {operator(op) => { String(x).to_sym => String(z).to_sym } } }
11
+ rule(:lvalue => {:column => simple(:x) }, :operator => simple(:op), :rvalue => simple(:z) ){ {operator(op) => { String(x).to_sym => z } } }
12
+ rule(:lvalue => simple(:x) , :operator => simple(:op), :rvalue => {:column => simple(:z)}){ {operator(op) => { String(z).to_sym => x } } }
13
+
14
+ # Statement right or left without right leaf
15
+ rule(:lstatement => { :lstatement => subtree(:y) }){ {:lstatement => y }}
16
+ rule(:rstatement => { :lstatement => subtree(:y) }){ {:rstatement => y}}
17
+
18
+ rule({:lstatement=>{:lstatement=> subtree(:l)},
19
+ :boolean_operator => simple(:op),
20
+ :rstatement=>{:lstatement => subtree(:r)}}) do {:lstatement => l, :boolean_operator => op, :rstatement => r} end
21
+
22
+ # Statement right or left without right leaf and a boolean operator
23
+ rule(:lstatement => { :lstatement => subtree(:y) }){ {:lstatement => y }}
24
+ rule(:rstatement => { :lstatement => subtree(:y) }){ {:rstatement => y}}
25
+
26
+ rule({:lstatement => subtree(:l),
27
+ :boolean_operator=> simple(:op),
28
+ :rstatement=>{:lstatement=>subtree(:r)}}) do {:lstatement => l, :boolean_operator => operator(op), :rstatement => r} end
29
+
30
+ rule({:rstatement => subtree(:r),
31
+ :boolean_operator=> simple(:op),
32
+ :lstatement=>{:lstatement=>subtree(:l)}}) do {:lstatement => l, :boolean_operator => operator(op), :rstatement => r} end
33
+
34
+
35
+ rule(:boolean_operator => simple(:op), :lstatement => subtree(:l), :rstatement => subtree(:r) ) { {operator(op) => [l, r] } }
36
+
37
+
38
+ end
39
+ end
40
+ end
41
+
42
+ class Parslet::Pattern::Context
43
+ def operator(op)
44
+ if op.is_a?(Symbol)
45
+ op
46
+ else
47
+ ( "-" + String(op) ).downcase.to_sym
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,5 @@
1
+ module Abstract
2
+ module Sql
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ Bundler.setup
5
+ require 'test/unit'
6
+
7
+
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
10
+ require 'abstract-sql'
11
+ class Test::Unit::TestCase
12
+ end
@@ -0,0 +1,42 @@
1
+ require 'helper'
2
+
3
+ class TestAbstract < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @abstract = SQL::Abstract.new
7
+ end
8
+
9
+ test "parse a simple condition" do
10
+ assert_equal( {:"-and" => [{:"-=" => { :id => 1}}, {:"-like" => {:label => '%webo%'}}]}, @abstract.parse("id = 1 and label like '%webo%'"))
11
+ assert_equal( {:"-and" => [{:"-=" => { :id => 1}}, {:"-like" => {:label => '%webo%'}}]}, @abstract.parse("((id = 1 )) and label like '%webo%'"))
12
+ end
13
+
14
+ test "parse a complex condition" do
15
+ assert_equal(
16
+ {
17
+ :"-or" => [
18
+ {:"-and" => [{:"-=" => { :id => 1}}, {:"-like" => {:label => '%webo%'}}]},
19
+ {:"-and" => [{:"-!=" => { :id => 1}}, {:"-like" => {:label => '%api%'}}]}
20
+ ]
21
+ },
22
+ @abstract.parse("(id = 1 AND label like '%webo%') OR (id !=1 and label like '%api%')")
23
+ )
24
+ end
25
+
26
+ test "raise error when bad formatted sql" do
27
+ assert_raise Parslet::ParseFailed do
28
+ # No AND
29
+ @abstract.parse("(id = 1 label like '%webo%')")
30
+ end
31
+ assert_raise Parslet::ParseFailed do
32
+ @abstract.parse("label liike '%webo%'")
33
+ end
34
+ assert_raise Parslet::UnconsumedInput do
35
+ @abstract.parse("id = 1 ET label like '%webo%'")
36
+ end
37
+ assert_raise Parslet::UnconsumedInput do
38
+ @abstract.parse("(id = 1 AND label like '%webo%') OR (id !=1 and label like '%api%'")
39
+ end
40
+ end
41
+
42
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: abstract-sql
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Hallelujah
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-01 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: parslet
16
+ requirement: &9785380 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *9785380
25
+ - !ruby/object:Gem::Dependency
26
+ name: test-unit
27
+ requirement: &9784880 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 2.1.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *9784880
36
+ - !ruby/object:Gem::Dependency
37
+ name: rcov
38
+ requirement: &9784460 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *9784460
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &9784000 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *9784000
58
+ description: It reverse an SQL statement to a Perl SQL::Abstract JSON format.
59
+ email:
60
+ - hery@rails-royce.org
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - .gitignore
66
+ - .rvmrc
67
+ - Gemfile
68
+ - README.markdown
69
+ - Rakefile
70
+ - abstract-sql.gemspec
71
+ - lib/abstract-sql.rb
72
+ - lib/abstract-sql/parser/statement.rb
73
+ - lib/abstract-sql/transform/statement.rb
74
+ - lib/abstract-sql/version.rb
75
+ - test/helper.rb
76
+ - test/test_abstract.rb
77
+ homepage: https://github.com/hallelujah/sql-abstract
78
+ licenses: []
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 1.8.6
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: Transform a SQL statement to Perl SQL::Abstract JSON format
101
+ test_files:
102
+ - test/helper.rb
103
+ - test/test_abstract.rb