sqlstmt 0.2.11 → 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e5516750b663f84d0ee52a37951a305e8c003914
4
- data.tar.gz: 2c80189865a625385c7da900c8cc8fe494a3860c
3
+ metadata.gz: 618fd088e704565e2bb73f6b7887db32cf60f7be
4
+ data.tar.gz: b632cba8862643ac1003cea40b7cf1b15f862900
5
5
  SHA512:
6
- metadata.gz: 07d9232c1d6473704cda766e7c12436a8677f2589939201b3dd8681801f66bf6ca72fb9c2b5b9c301c2a945d3d3792d430687a82e2d4755aa94d4d0a81d5f33d
7
- data.tar.gz: a08886bb3a3fdfbce82fed743ba2ec339c16d7acc33bb2b789025b24cc0bca0a58853da1ec50f6cc34f314e6c0d6ad10fdc6b839937e17f621e3fd76c47c0164
6
+ metadata.gz: ea60dddfea616466f657e597065ef6051cd091d7d123f06be08d12c93fc43e0f3e28d6cfebb2c0ff6358e28ddc5a234110b5fe8ac220e3c3b99789cb2733653d
7
+ data.tar.gz: fa8802b7ec27baee278410180119d9eae21cbc31d13cecd20e2f207eb6eaa7a7c5fd4b545a2122bfcda915de8153887de18bdce069c64a2582770f8da2e8a9b6
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007 Makani Mason, Kem Mason
1
+ Copyright (c) 2007 ATPSoft
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
@@ -4,8 +4,10 @@ require 'date'
4
4
  class String
5
5
  unless method_defined?(:to_sql)
6
6
  def to_sql
7
- str = gsub('\\', '\\\\').gsub('\'', '\\\'').gsub("'", "\\'")
8
- "'#{str}'"
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'
@@ -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
- straight_join_str = @data.straight_join ? 'STRAIGHT_JOIN ' : ''
16
- distinct_str = @data.distinct ? 'DISTINCT ' : ''
17
- select_str = @data.gets.join(',')
18
- outfile_str = @data.outfile ? " INTO OUTFILE #{@data.outfile}" : ''
19
- return "SELECT #{straight_join_str}#{distinct_str}#{select_str}#{build_from_clause}#{outfile_str}"
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
- limit_clause = simple_clause('LIMIT', @data.limit)
24
- return "UPDATE #{build_table_list}#{build_join_clause} SET #{build_set_clause}#{build_where_clause}#{limit_clause}"
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
- keyword = @data.replace ? 'REPLACE' : 'INSERT'
29
- value_list = @data.set_values.join(',')
30
- ignore_str = @data.ignore ? 'IGNORE ' : ''
31
- start_str = "#{keyword} #{ignore_str}INTO #{@data.into} "
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
- start_str += "(#{field_list}) "
59
+ parts << "(#{field_list})"
35
60
  end
36
-
37
- distinct_str = @data.distinct ? 'DISTINCT ' : ''
38
- return "#{start_str}SELECT #{distinct_str}#{value_list}#{build_from_clause}"
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
- if @data.tables_to_delete.empty?
43
- table_clause = ''
44
- else
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
- return "DELETE#{table_clause}#{build_from_clause}"
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
- join_clause = build_join_clause
52
- group_clause = simple_clause('GROUP BY', @data.group_by)
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
- group_clause += ' WITH ROLLUP'
99
+ parts << 'WITH ROLLUP'
100
+ end
101
+ if !@data.havings.empty?
102
+ parts << "HAVING #{@data.havings.join(' AND ')}"
55
103
  end
56
- order_clause = simple_clause('ORDER BY', @data.order_by)
57
- limit_clause = simple_clause('LIMIT', @data.limit)
58
- having_clause = @data.havings.empty? ? '' : " HAVING #{@data.havings.join(' AND ')}"
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
- if @data.joins.empty?
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
@@ -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
@@ -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 test_includes_table
5
- sqlb = SqlStmt.new.select.table('target')
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
- def test_tables
20
- assert_equal('SELECT blah FROM target', SqlStmt.new.select.table('target').no_where.get('blah').to_sql)
21
- assert_equal('SELECT t.blah FROM target t', SqlStmt.new.select.table('target t').no_where.get('t.blah').to_sql)
22
- assert_equal('SELECT t.blah FROM target t USE INDEX (blee)', SqlStmt.new.select.table('target t', 'blee').no_where.get('t.blah').to_sql)
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
- def test_minimum_requirements
26
- assert_raises(SqlStmtError) { SqlStmt.new.select.table('target').to_s }
27
- assert_raises(SqlStmtError) { SqlStmt.new.type('select').table('target').to_s }
28
- assert_raises(SqlStmtError) { SqlStmt.new.select.table('target').no_where.to_s }
29
- assert_raises(SqlStmtError) { SqlStmt.new.select.table('target').optional_where.to_s }
30
- assert_equal('SELECT blah FROM target', SqlStmt.new.select.table('target').optional_where.get('blah').to_sql)
31
- end
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
- def test_stuff
34
- assert_equal('SELECT blah FROM source', SqlStmt.new.select.table('source').get('blah').no_where.to_s)
35
- assert_equal('SELECT DISTINCT blah FROM source', SqlStmt.new.select.table('source').get('blah').no_where.distinct.to_s)
36
- assert_equal('SELECT blah FROM source WHERE source_id = 1', SqlStmt.new.select.table('source').get('blah').where('source_id = 1').to_s)
37
- assert_equal('SELECT blah FROM source s', SqlStmt.new.select.table('source s').get('blah').no_where.to_s)
38
- assert_equal('SELECT blah FROM source s JOIN other o ON s.blah_id = o.blah_id', SqlStmt.new.select.table('source s').join('other o', 's.blah_id = o.blah_id').get('blah').no_where.to_s)
39
- assert_equal('SELECT blah FROM source s LEFT JOIN other o ON s.blah_id = o.blah_id', SqlStmt.new.select.table('source s').left_join('other o', 's.blah_id = o.blah_id').get('blah').no_where.to_s)
40
- assert_equal('SELECT blah,blee FROM source', SqlStmt.new.select.table('source').get('blah','blee').no_where.to_s)
41
- assert_equal('SELECT blah FROM source HAVING blee > 0', SqlStmt.new.select.table('source').get('blah').no_where.having('blee > 0').to_s)
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 test_group_by
45
- sqlb = SqlStmt.new.select.table('source').get('blah').no_where.group_by('blah')
46
- assert_equal('SELECT blah FROM source GROUP BY blah', sqlb.to_s)
47
- sqlb.with_rollup
48
- assert_equal('SELECT blah FROM source GROUP BY blah WITH ROLLUP', sqlb.to_s)
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 test_duplicate_joins
52
- sqlb = SqlStmt.new.select.table('source s').get('frog').no_where
53
- 4.times { sqlb.join('other o', 's.blah_id = o.blah_id') }
54
- assert_equal('SELECT frog FROM source s JOIN other o ON s.blah_id = o.blah_id', sqlb.to_s)
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 test_join_with_multiple_conditions
58
- %i(join left_join).each do |method|
59
- sqlb = SqlStmt.new.select.table('source s').get('frog').no_where.send(method, 'other o', 'z.blee_id = o.blee_id', 'z.other_field = o.other_field')
60
- method_sql = method.to_s.upcase.sub('_', ' ')
61
- assert_equal("SELECT frog FROM source s #{method_sql} other o ON z.blee_id = o.blee_id AND z.other_field = o.other_field", sqlb.to_s)
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 test_stuff
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("'2008-09-24'", Date.new(2008,9,24).to_sql)
9
- assert_equal("'2008-09-24 09:30:04'", DateTime.new(2008,9,24,9,30,4).to_sql)
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
- assert_equal('10.0', BigDecimal.new('10').to_sql)
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.11
4
+ version: 0.2.12
5
5
  platform: ruby
6
6
  authors:
7
- - Makani Mason
8
- - Kem Mason
7
+ - ATPSoft
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2018-08-13 00:00:00.000000000 Z
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.13
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