abstract-sql 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,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