sqlstmt 0.2.11 → 0.2.12
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.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/lib/sqlstmt/core_to_sql.rb +4 -2
- data/lib/sqlstmt/data.rb +1 -1
- data/lib/sqlstmt/mysql/build.rb +75 -35
- data/lib/sqlstmt/sqlstmt.rb +13 -2
- data/test/insert_select_test.rb +6 -0
- data/test/select_test.rb +82 -50
- data/test/to_sql_test.rb +13 -5
- metadata +4 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 618fd088e704565e2bb73f6b7887db32cf60f7be
|
4
|
+
data.tar.gz: b632cba8862643ac1003cea40b7cf1b15f862900
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea60dddfea616466f657e597065ef6051cd091d7d123f06be08d12c93fc43e0f3e28d6cfebb2c0ff6358e28ddc5a234110b5fe8ac220e3c3b99789cb2733653d
|
7
|
+
data.tar.gz: fa8802b7ec27baee278410180119d9eae21cbc31d13cecd20e2f207eb6eaa7a7c5fd4b545a2122bfcda915de8153887de18bdce069c64a2582770f8da2e8a9b6
|
data/MIT-LICENSE
CHANGED
data/lib/sqlstmt/core_to_sql.rb
CHANGED
@@ -4,8 +4,10 @@ require 'date'
|
|
4
4
|
class String
|
5
5
|
unless method_defined?(:to_sql)
|
6
6
|
def to_sql
|
7
|
-
|
8
|
-
"'
|
7
|
+
str = gsub('\\') {'\\\\'}
|
8
|
+
str = str.gsub("'") {"\\'"}
|
9
|
+
str = str.gsub('"') {'\\"'}
|
10
|
+
return "'#{str}'"
|
9
11
|
end
|
10
12
|
end
|
11
13
|
end
|
data/lib/sqlstmt/data.rb
CHANGED
@@ -4,7 +4,7 @@ module SqlStmtLib
|
|
4
4
|
extend self
|
5
5
|
|
6
6
|
FLAG_KEYWORDS = %i(distinct ignore replace straight_join with_rollup).freeze
|
7
|
-
SINGLE_VALUE_KEYWORDS = %i(group_by into limit offset order_by outfile).freeze
|
7
|
+
SINGLE_VALUE_KEYWORDS = %i(group_by into limit offset order_by outfile on_duplicate ignore_duplicate).freeze
|
8
8
|
MULTI_VALUE_KEYWORDS = %i(get having where).freeze
|
9
9
|
|
10
10
|
# :str is the full original string specifying the table, like 'frog f' or 'frog AS f' or 'frog'
|
data/lib/sqlstmt/mysql/build.rb
CHANGED
@@ -11,52 +11,99 @@ class MysqlBuilder
|
|
11
11
|
return send(method_name)
|
12
12
|
end
|
13
13
|
|
14
|
+
def combine_parts(parts)
|
15
|
+
parts.each do |str|
|
16
|
+
str.strip!
|
17
|
+
end
|
18
|
+
parts.reject! do |str|
|
19
|
+
str.nil? || str.empty?
|
20
|
+
end
|
21
|
+
return parts.join(' ')
|
22
|
+
end
|
23
|
+
|
14
24
|
def build_stmt_select
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
25
|
+
parts = []
|
26
|
+
parts << shared_select(@data.gets)
|
27
|
+
if @data.outfile
|
28
|
+
parts << "INTO OUTFILE #{@data.outfile}"
|
29
|
+
end
|
30
|
+
return combine_parts(parts)
|
20
31
|
end
|
21
32
|
|
22
33
|
def build_stmt_update
|
23
|
-
|
24
|
-
|
34
|
+
parts = [
|
35
|
+
'UPDATE',
|
36
|
+
build_table_list,
|
37
|
+
build_join_clause,
|
38
|
+
'SET',
|
39
|
+
build_set_clause,
|
40
|
+
build_where_clause,
|
41
|
+
simple_clause('LIMIT', @data.limit),
|
42
|
+
]
|
43
|
+
return combine_parts(parts)
|
25
44
|
end
|
26
45
|
|
27
46
|
def build_stmt_insert
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
47
|
+
parts = []
|
48
|
+
if @data.replace
|
49
|
+
parts << 'REPLACE'
|
50
|
+
else
|
51
|
+
parts << 'INSERT'
|
52
|
+
end
|
53
|
+
if @data.ignore
|
54
|
+
parts << 'IGNORE'
|
55
|
+
end
|
56
|
+
parts << "INTO #{@data.into}"
|
32
57
|
if !@data.set_fields.empty?
|
33
58
|
field_list = @data.set_fields.join(',')
|
34
|
-
|
59
|
+
parts << "(#{field_list})"
|
35
60
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
61
|
+
parts << shared_select(@data.set_values)
|
62
|
+
if @data.on_duplicate
|
63
|
+
parts << "ON DUPLICATE KEY UPDATE #{@data.on_duplicate}"
|
64
|
+
elsif @data.ignore_duplicate
|
65
|
+
parts << "ON DUPLICATE KEY UPDATE #{@data.ignore_duplicate} = #{@data.ignore_duplicate}"
|
66
|
+
end
|
67
|
+
return combine_parts(parts)
|
39
68
|
end
|
40
69
|
|
41
70
|
def build_stmt_delete
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
table_clause = ' ' + @data.tables_to_delete.join(',')
|
71
|
+
parts = ['DELETE']
|
72
|
+
if !@data.tables_to_delete.empty?
|
73
|
+
parts << @data.tables_to_delete.join(',')
|
46
74
|
end
|
47
|
-
|
75
|
+
parts << build_from_clause
|
76
|
+
return combine_parts(parts)
|
77
|
+
end
|
78
|
+
|
79
|
+
def shared_select(fields)
|
80
|
+
parts = ['SELECT']
|
81
|
+
if @data.straight_join
|
82
|
+
parts << 'STRAIGHT_JOIN'
|
83
|
+
end
|
84
|
+
if @data.distinct
|
85
|
+
parts << 'DISTINCT'
|
86
|
+
end
|
87
|
+
parts << fields.join(',')
|
88
|
+
parts << build_from_clause
|
89
|
+
return combine_parts(parts)
|
48
90
|
end
|
49
91
|
|
50
92
|
def build_from_clause
|
51
|
-
|
52
|
-
|
93
|
+
parts = ['FROM']
|
94
|
+
parts << build_table_list
|
95
|
+
parts << build_join_clause
|
96
|
+
parts << build_where_clause
|
97
|
+
parts << simple_clause('GROUP BY', @data.group_by)
|
53
98
|
if @data.with_rollup
|
54
|
-
|
99
|
+
parts << 'WITH ROLLUP'
|
100
|
+
end
|
101
|
+
if !@data.havings.empty?
|
102
|
+
parts << "HAVING #{@data.havings.join(' AND ')}"
|
55
103
|
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
return " FROM #{build_table_list}#{join_clause}#{build_where_clause}#{group_clause}#{having_clause}#{order_clause}#{limit_clause}"
|
104
|
+
parts << simple_clause('ORDER BY', @data.order_by)
|
105
|
+
parts << simple_clause('LIMIT', @data.limit)
|
106
|
+
return combine_parts(parts)
|
60
107
|
end
|
61
108
|
|
62
109
|
def build_set_clause
|
@@ -87,14 +134,7 @@ class MysqlBuilder
|
|
87
134
|
end
|
88
135
|
|
89
136
|
def build_join_clause
|
90
|
-
|
91
|
-
return ''
|
92
|
-
else
|
93
|
-
# we call uniq here to be tolerant of a table being joined to multiple times in an identical fashion
|
94
|
-
# where the intention is not actually to include the table multiple times
|
95
|
-
# but I'm thinking we may want to reconsider, or at least warn when this happens so the source bug can be fixed
|
96
|
-
return ' ' + @data.joins.map {|join| join_to_str(join)}.uniq.join(' ')
|
97
|
-
end
|
137
|
+
return ' ' + @data.joins.map {|join| join_to_str(join)}.uniq.join(' ')
|
98
138
|
end
|
99
139
|
|
100
140
|
def build_where_clause
|
data/lib/sqlstmt/sqlstmt.rb
CHANGED
@@ -47,7 +47,7 @@ class SqlStmt
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def type(stmt_type)
|
50
|
-
if @data.stmt_type
|
50
|
+
if @data.stmt_type && @data.stmt_type != stmt_type
|
51
51
|
raise SqlStmtError, "statement type already set to #{@data.stmt_type}"
|
52
52
|
end
|
53
53
|
@data.stmt_type = stmt_type
|
@@ -79,13 +79,24 @@ class SqlStmt
|
|
79
79
|
return @data.table_ids.include?(table_to_find)
|
80
80
|
end
|
81
81
|
|
82
|
-
###### where
|
82
|
+
###### where - because the lack of a where clause can be so impactful,
|
83
|
+
###### the default behavior is to require one, but this behavior can be customized using the 3 methods below
|
84
|
+
###### note that each of these override the other calls, so whichever is one called last will take effect
|
83
85
|
|
86
|
+
# if no where clauses have been added, the statement will fail to build
|
87
|
+
# this is the default value, but in case it got changed, we can reset it back
|
88
|
+
def require_where
|
89
|
+
@data.where_behavior = :require
|
90
|
+
return self
|
91
|
+
end
|
92
|
+
|
93
|
+
# if any where clauses have been added, the statement will fail to build
|
84
94
|
def no_where
|
85
95
|
@data.where_behavior = :exclude
|
86
96
|
return self
|
87
97
|
end
|
88
98
|
|
99
|
+
# this disables a where clause requirement
|
89
100
|
def optional_where
|
90
101
|
@data.where_behavior = :optional
|
91
102
|
return self
|
data/test/insert_select_test.rb
CHANGED
@@ -16,6 +16,12 @@ class TestInsertSelect < Minitest::Test
|
|
16
16
|
assert_equal('INSERT INTO target SELECT * FROM source', SqlStmt.new.insert.into('target').table('source').set(nil, '*').no_where.to_s)
|
17
17
|
end
|
18
18
|
|
19
|
+
def test_ignore_duplicate
|
20
|
+
sqlt = SqlStmt.new.insert.into('target').table('source').set(nil, '*').no_where
|
21
|
+
assert_equal('INSERT INTO target SELECT * FROM source ON DUPLICATE KEY UPDATE idkey = idkey', sqlt.dup.ignore_duplicate('idkey').to_s)
|
22
|
+
assert_equal('INSERT INTO target SELECT * FROM source ON DUPLICATE KEY UPDATE idkey = idkey + 1', sqlt.dup.on_duplicate('idkey = idkey + 1').to_s)
|
23
|
+
end
|
24
|
+
|
19
25
|
def test_dup
|
20
26
|
shared_builder = SqlStmt.new.insert.into('target')
|
21
27
|
first_builder = shared_builder
|
data/test/select_test.rb
CHANGED
@@ -1,64 +1,96 @@
|
|
1
1
|
require_relative 'helper'
|
2
2
|
|
3
|
+
# adding this extra line so lines can match up perfectly for now with the swift implementation
|
3
4
|
class TestSelect < Minitest::Test
|
4
|
-
def
|
5
|
-
|
6
|
-
assert(sqlb.includes_table?('target'))
|
7
|
-
assert_equal(['target'], sqlb.data.table_ids.to_a)
|
8
|
-
|
9
|
-
sqlb = SqlStmt.new.select.table('target t')
|
10
|
-
assert(sqlb.includes_table?('target'))
|
11
|
-
assert(sqlb.includes_table?('t'))
|
12
|
-
assert(!sqlb.includes_table?('blah'))
|
13
|
-
|
14
|
-
sqlb = SqlStmt.new.select.table('target AS t')
|
15
|
-
assert(sqlb.includes_table?('target'))
|
16
|
-
assert(sqlb.includes_table?('t'))
|
17
|
-
end
|
5
|
+
def test_gradually
|
6
|
+
sqlt = SqlStmt.new
|
18
7
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
8
|
+
# keep these chained together, so we have at least one test of the return value of all the method calls
|
9
|
+
# in ruby this isn't particular tricky or unique, but still it's good to test
|
10
|
+
sqlt.select.type('select')
|
11
|
+
assert_raises(SqlStmtError) { sqlt.update() }
|
24
12
|
|
25
|
-
|
26
|
-
|
27
|
-
assert_raises(SqlStmtError) {
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
13
|
+
sqlt.table('target')
|
14
|
+
assert_equal(['target'], sqlt.data.table_ids.to_a())
|
15
|
+
assert_raises(SqlStmtError) { sqlt.to_s() }
|
16
|
+
|
17
|
+
sqlt.get('blah')
|
18
|
+
assert_raises(SqlStmtError) { sqlt.to_s() }
|
19
|
+
|
20
|
+
sqlt.no_where()
|
21
|
+
assert_equal('SELECT blah FROM target', sqlt.to_sql())
|
32
22
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
assert_equal('SELECT blah FROM
|
38
|
-
|
39
|
-
|
40
|
-
assert_equal('SELECT blah
|
41
|
-
|
23
|
+
sqlt.require_where()
|
24
|
+
assert_raises(SqlStmtError) { sqlt.to_s }
|
25
|
+
|
26
|
+
sqlt.optional_where()
|
27
|
+
assert_equal('SELECT blah FROM target', sqlt.to_sql())
|
28
|
+
|
29
|
+
sqlt.where('frog = 1')
|
30
|
+
assert_equal('SELECT blah FROM target WHERE frog = 1', sqlt.to_sql())
|
31
|
+
|
32
|
+
sqlt.join('other o', 'target.id = o.id')
|
33
|
+
assert_equal(['target', 'other', 'o'], sqlt.data.table_ids.to_a)
|
34
|
+
assert_equal('SELECT blah FROM target JOIN other o ON target.id = o.id WHERE frog = 1', sqlt.to_sql())
|
42
35
|
end
|
43
36
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
37
|
+
def test_simple_with_small_variations
|
38
|
+
tmpl = SqlStmt.new
|
39
|
+
tmpl.select().table('target t').get('blah').no_where()
|
40
|
+
assert_equal(['target', 't'], tmpl.data.table_ids.to_a())
|
41
|
+
|
42
|
+
sqlt = tmpl.dup()
|
43
|
+
sqlt.distinct
|
44
|
+
assert_equal('SELECT DISTINCT blah FROM target t', sqlt.to_sql())
|
45
|
+
|
46
|
+
sqlt = tmpl.dup()
|
47
|
+
sqlt.join('other o', 't.blah_id = o.blah_id', 't.blee_id = o.blee_id')
|
48
|
+
assert_equal('SELECT blah FROM target t JOIN other o ON t.blah_id = o.blah_id AND t.blee_id = o.blee_id', sqlt.to_sql())
|
49
|
+
|
50
|
+
sqlt = tmpl.dup()
|
51
|
+
sqlt.left_join('other o', 't.blah_id = o.blah_id')
|
52
|
+
assert_equal('SELECT blah FROM target t LEFT JOIN other o ON t.blah_id = o.blah_id', sqlt.to_sql())
|
53
|
+
|
54
|
+
sqlt = tmpl.dup()
|
55
|
+
sqlt.get('blee', 'bloo')
|
56
|
+
assert_equal('SELECT blah,blee,bloo FROM target t', sqlt.to_sql())
|
57
|
+
|
58
|
+
sqlt = tmpl.dup()
|
59
|
+
sqlt.having('blah > 0')
|
60
|
+
assert_equal('SELECT blah FROM target t HAVING blah > 0', sqlt.to_sql())
|
61
|
+
|
62
|
+
sqlt = tmpl.dup()
|
63
|
+
sqlt.group_by('blah')
|
64
|
+
assert_equal('SELECT blah FROM target t GROUP BY blah', sqlt.to_sql())
|
65
|
+
sqlt.with_rollup
|
66
|
+
assert_equal('SELECT blah FROM target t GROUP BY blah WITH ROLLUP', sqlt.to_sql())
|
49
67
|
end
|
50
68
|
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
|
69
|
+
def test_tables
|
70
|
+
sqlt = SqlStmt.new.select().table('target t', 'blee').no_where.get('t.blah')
|
71
|
+
assert_equal('SELECT t.blah FROM target t USE INDEX (blee)', sqlt.to_sql())
|
72
|
+
|
73
|
+
sqlt = SqlStmt.new.select().table('target')
|
74
|
+
assert(sqlt.includes_table?('target'))
|
75
|
+
assert(!sqlt.includes_table?('blah'))
|
76
|
+
|
77
|
+
sqlt = SqlStmt.new.select().table('target t')
|
78
|
+
assert(sqlt.includes_table?('target'))
|
79
|
+
assert(sqlt.includes_table?('t'))
|
80
|
+
|
81
|
+
sqlt = SqlStmt.new.select().table('target AS t')
|
82
|
+
assert(sqlt.includes_table?('target'))
|
83
|
+
assert(sqlt.includes_table?('t'))
|
84
|
+
|
85
|
+
sqlt.join('other o', 't.blah_id = o.blah_id')
|
86
|
+
assert(sqlt.includes_table?('other'))
|
87
|
+
assert(sqlt.includes_table?('o'))
|
55
88
|
end
|
56
89
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
90
|
+
def test_duplicate_joins
|
91
|
+
sqlt = SqlStmt.new.select().table('source s').get('frog').no_where
|
92
|
+
4.times { sqlt.join('other o', 's.blah_id = o.blah_id') }
|
93
|
+
assert_equal('SELECT frog FROM source s JOIN other o ON s.blah_id = o.blah_id', sqlt.to_s)
|
94
|
+
assert_equal('other o', sqlt.data.joins.first.table.str)
|
63
95
|
end
|
64
96
|
end
|
data/test/to_sql_test.rb
CHANGED
@@ -1,15 +1,23 @@
|
|
1
1
|
require_relative 'helper'
|
2
2
|
|
3
3
|
class Test_to_sql < Minitest::Test
|
4
|
-
def
|
5
|
-
assert_equal("'blah'", 'blah'.to_sql)
|
4
|
+
def test_to_sql
|
6
5
|
assert_equal('NULL', nil.to_sql)
|
6
|
+
|
7
|
+
assert_equal("'blah'", 'blah'.to_sql)
|
8
|
+
assert_equal("'b\\\\lah'", "b\\lah".to_sql())
|
9
|
+
assert_equal("'b\\'lah'", "b'lah".to_sql())
|
10
|
+
assert_equal("'b\\\"lah'", "b\"lah".to_sql())
|
11
|
+
|
7
12
|
assert_equal('3', 3.to_sql)
|
8
|
-
assert_equal(
|
9
|
-
|
13
|
+
assert_equal('10.0', BigDecimal.new('10').to_sql)
|
14
|
+
|
10
15
|
assert_equal('1', true.to_sql)
|
11
16
|
assert_equal('0', false.to_sql)
|
12
|
-
|
17
|
+
|
18
|
+
assert_equal("'2008-09-24'", Date.new(2008,9,24).to_sql)
|
19
|
+
assert_equal("'2008-09-24 09:30:04'", DateTime.new(2008,9,24,9,30,4).to_sql)
|
20
|
+
|
13
21
|
assert_equal("('a','b','c')", ['a', 'b', 'c'].to_sql)
|
14
22
|
end
|
15
23
|
end
|
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqlstmt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
- Kem Mason
|
7
|
+
- ATPSoft
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2019-09-10 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
13
|
description: Build SQL statements using method calls instead of strings. This is not
|
15
14
|
an ORM. It has only been used and tested with MySQL so far but the intention is
|
@@ -57,7 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
57
56
|
version: '0'
|
58
57
|
requirements: []
|
59
58
|
rubyforge_project:
|
60
|
-
rubygems_version: 2.6.
|
59
|
+
rubygems_version: 2.6.12
|
61
60
|
signing_key:
|
62
61
|
specification_version: 4
|
63
62
|
summary: build SQL statements using method calls instead of strings
|