quote-sql 0.0.6 → 0.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e308b3aef586983a61155c9ba3be6c39c2397fa5fbae069ab45522e51681caa
4
- data.tar.gz: 6a3945e1a4cfc16fed91c46216d1a10c5a69e85a6b3a0df10db45a9f162c07eb
3
+ metadata.gz: b1ae8afe082e0867130f3933c76e867ac3f16f53b816db61cd9a9cabdfeb3298
4
+ data.tar.gz: d5c1638521b04e8e62ff2dab8a27a27165f27f972ea4bc6e5f6e55e4680a8cb6
5
5
  SHA512:
6
- metadata.gz: 3ce751f52d51a6b7b989062673f9c849b5a8b44e5e4227f0f453290d41b20856b45f0d0bbddd6cea6f5256405b313d82367a27fdd2381dd2b37f27c3ac3442e6
7
- data.tar.gz: 2656c2cbacc7375fd22f2148d4a929fbba1822c97b61467bdf5794c7bcf78b75d5ba4a9bfb90a6f01b282479bad95c77d354ca2211a6f3561a6eaa60e9d90db4
6
+ metadata.gz: 886abf29ad27600528bd42dd88e4c13da41a1c7076b7bc9a69592d07f8d1653962eb9daaab68eb897238f5b4069658496919a254d01a53b5656cbff9c02bc958
7
+ data.tar.gz: '085c3fd76781bf95c658151398970fdc6de805767d495d4c23cc9e8cbd444cd61335b73162b99cc16d71eb1f8d268d8c7824244c5e2d6eafe47fe1bd20aaed0e'
data/README.md CHANGED
@@ -26,7 +26,7 @@ Best Martin
26
26
  `QuoteSql.new("SELECT %field").quote(field: "abc").to_sql`
27
27
  => SELECT 'abc'
28
28
 
29
- `QuoteSql.new("SELECT %field__text").quote(field__text: 9).to_sql`
29
+ `QuoteSql.new("SELECT %field::TEXT").quote(field: 9).to_sql`
30
30
  => SELECT 9::TEXT
31
31
 
32
32
  ### Rails models
@@ -54,15 +54,14 @@ Values are be ordered in sequence of columns. Missing value entries are substitu
54
54
  {lastname: "Schultz", firstname: "herbert"}
55
55
  ], constraint: :id).to_sql`
56
56
  => INSERT INTO "users" ("id", "firstname", "lastname", "created_at")
57
- VALUES (1, 'Albert', 'Müller', CURRENT_TIMESTAMP), (DEFAULT, 'herbert', 'Schultz', CURRENT_TIMESTAMP)
57
+ VALUES
58
+ (1, 'Albert', 'Müller', DEFAULT),
59
+ (DEFAULT, 'herbert', 'Schultz', DEFAULT)
58
60
  ON CONFLICT ("id") DO NOTHING
59
61
 
60
62
  ### Columns from a list
61
- `QuoteSql.new("SELECT %columns").quote(columns: [:a, "b.c", d: {e: field}]).to_sql`
62
- => SELECT "a","b"."c",jsonb_build_object('e', field) AS d
63
-
64
- `QuoteSql.new("SELECT %columns").quote(columns: [:a, "b.c", d: {e: field, nil: false}]).to_sql`
65
- => SELECT "a","b"."c",jsonb_strip_nulls(jsonb_build_object('e', 1)) AS d
63
+ `QuoteSql.new("SELECT %columns FROM %table").quote(table: "foo", columns: [:a, "b", "foo.c", {d: :e}]).to_sql`
64
+ => SELECT "foo"."a","b"."foo"."c", "foo"."e" AS d
66
65
 
67
66
  ## Executing
68
67
  ### Getting the results
@@ -72,6 +71,7 @@ Values are be ordered in sequence of columns. Missing value entries are substitu
72
71
  ### Binds
73
72
  You can use binds ($1, $2, ...) in the SQL and add arguments to the result call
74
73
  `QuoteSql.new('SELECT $1 AS a').result(1)`
74
+ => [{:a=>1}]
75
75
 
76
76
  #### using JSON
77
77
 
@@ -83,7 +83,7 @@ You can use binds ($1, $2, ...) in the SQL and add arguments to the result call
83
83
  Insert fom json
84
84
 
85
85
  v = {a: 1, b: "foo", c: true}
86
- QuoteSql.new("INSERT INTO table (%x_columns) SELECT * FROM %x_json").quote({:x_json=>1}).result(v.to_json)
86
+ QuoteSql.new("INSERT INTO table (%columns) SELECT * FROM %json").quote({:json=>1}).result(v.to_json)
87
87
 
88
88
 
89
89
 
@@ -111,10 +111,8 @@ All can be preceded by additional letters and underscore e.g. `%foo_bar_column`
111
111
  A database typecast is added to fields ending with double underscore and a valid db data type
112
112
  with optional array dimension
113
113
 
114
- - `%field__jsonb` => adds a `::JSONB` typecast to the field
115
- - `%number_to__text` => adds a `::TEXT` typecast to the field
116
- - `%array__text1` => adds a `::TEXT[]` (TO BE IMPLEMENTED)
117
- - `%array__text2` => adds a `::TEXT[][]` (TO BE IMPLEMENTED)
114
+ - `%field::jsonb` => treats the field as jsonb when casted
115
+ - `%array::text[]` => treats an array like a text array, default is JSONB
118
116
 
119
117
  ### Quoting
120
118
  - Any value of the standard mixins are quoted with these exceptions
@@ -19,12 +19,11 @@ class QuoteSql
19
19
  self.class.conn
20
20
  end
21
21
 
22
- def _exec_query(sql, binds = [], prepare: false, async: false)
23
- conn.exec_query(sql, "SQL", binds, prepare:, async:)
22
+ def _exec_query(sql, binds = [], **options)
23
+ conn.exec_query(sql, "SQL", binds, **options)
24
24
  end
25
25
 
26
- def _exec(sql, binds = [], prepare: false, async: false)
27
- options = { prepare:, async: }
26
+ def _exec(sql, binds = [], **options)
28
27
  result = _exec_query(sql, binds, **options)
29
28
  columns = result.columns.map(&:to_sym)
30
29
  result.cast_values.map do |row|
@@ -1,3 +1,4 @@
1
+ require 'niceql'
1
2
  class QuoteSql
2
3
  module Formater
3
4
  PG_FORMAT_BIN = `which pg_format`.chomp.presence
@@ -9,15 +10,14 @@ class QuoteSql
9
10
 
10
11
  def to_formatted_sql
11
12
  sql = respond_to?(:to_sql) ? to_sql : to_s
12
- Niceql::Prettifier.prettify_sql(sql)
13
+ Niceql::Prettifier.prettify_sql(sql.gsub(/(?<=[^%])%(?=\S)/, "%%"))
13
14
 
14
15
  # IO.popen(PG_FORMAT_BIN, "r+", err: "/dev/null") do |f|
15
16
  # f.write(sql)
16
17
  # f.close_write
17
18
  # f.read
18
19
  # end
19
- rescue
20
- sql
20
+
21
21
  end
22
22
 
23
23
  alias to_sqf to_formatted_sql
@@ -1,12 +1,12 @@
1
1
  class QuoteSql
2
2
  class Quoter
3
- def initialize(qsql, key, quotable)
3
+ def initialize(qsql, key, cast, quotable)
4
4
  @qsql = qsql
5
- @key, @quotable = key, quotable
5
+ @key, @cast, @quotable = key, cast, quotable
6
6
  @name = key.sub(/_[^_]+$/, '') if key["_"]
7
7
  end
8
8
 
9
- attr_reader :key, :quotable, :name
9
+ attr_reader :key, :quotable, :name, :cast
10
10
 
11
11
  def quotes
12
12
  @qsql.quotes
@@ -34,19 +34,19 @@ class QuoteSql
34
34
  @qsql.casts(name || self.name)
35
35
  end
36
36
 
37
- def ident_columns(name = nil)
38
- item = columns(name || self.name)
37
+ def ident_columns(name = self.name)
38
+ item = columns(name)
39
39
  unless item
40
- unless item = casts(name || self.name)&.keys
41
- if (table = self.table(name || self.name))&.respond_to? :column_names
40
+ unless item = casts(name)&.keys&.map(&:to_s)
41
+ if (table = self.table(name))&.respond_to? :column_names
42
42
  item = table.column_names
43
43
  else
44
- raise ArgumntError, "No columns, casts or table given for #{name}" unless table&.respond_to? :column_names
44
+ raise ArgumentError, "No columns, casts or table given for #{name}" unless table&.respond_to? :column_names
45
45
  end
46
46
  end
47
47
  end
48
48
  if item.is_a?(Array)
49
- if item.all? { _1.respond_to?(:name) }
49
+ if item.all? { not _1.is_a?(Symbol) and not _1.is_a?(String) and _1.respond_to?(:name) }
50
50
  item = item.map(&:name)
51
51
  end
52
52
  end
@@ -90,13 +90,13 @@ class QuoteSql
90
90
  when /(?:^|(.*)_)(ident)$/i
91
91
  _ident
92
92
  when /(?:^|(.*)_)constraints?$/i
93
- quotable
93
+ quotable.to_s
94
94
  when /(?:^|(.*)_)(raw|sql)$/i
95
- quotable
96
- when /^(.+)_json$/i
97
- data_json
95
+ quotable.to_s
96
+ when /(?:^|(.*)_)json$/i
97
+ json_recordset
98
98
  when /^(.+)_values$/i
99
- data_values
99
+ values
100
100
  when /values$/i
101
101
  insert_values
102
102
  else
@@ -106,31 +106,7 @@ class QuoteSql
106
106
 
107
107
  ###############
108
108
 
109
- private def value(values)
110
- # case values.class.to_s
111
- # when "QuoteSql::Raw", "Arel::Nodes::SqlLiteral" then rv = values
112
- # when "Array"
113
- # when "Hash"
114
- # columns = self.columns(name)&.flat_map { _1.is_a?(Hash) ? _1.values : _1 }
115
- # if columns.nil?
116
- # values = values.values
117
- # elsif columns.all? { _1.is_a? Symbol }
118
- # raise ArgumentError, "Columns just Symbols"
119
- # else
120
- # values = columns.map do |column|
121
- # if values.key?(column&.to_sym) or !defaults
122
- # values[column.to_sym]
123
- # elsif column[/^(created|updated)_at$/]
124
- # :current_timestamp
125
- # else
126
- # :default
127
- # end
128
- # end
129
- # end
130
- # else
131
- # raise ArgumentError, "value just Array, Hash, QuoteSql::Raw, Arel::Nodes::SqlLiteral"
132
- # end
133
-
109
+ private def _value(values)
134
110
  rv ||= values.map do |i|
135
111
  case i
136
112
  when :default, :current_timestamp
@@ -143,128 +119,155 @@ class QuoteSql
143
119
  Raw.sql "(#{rv.join(",")})"
144
120
  end
145
121
 
146
- def data_json(item = @quotable)
122
+ # def data_json(item = @quotable)
123
+ # casts = self.casts(name)
124
+ # columns = self.columns(name) || casts&.keys
125
+ # column_cast = columns&.map { "#{QuoteSql.quote_column_name(_1)} #{casts&.dig(_1) || "TEXT"}" }
126
+ # if item.is_a? Integer
127
+ # rv = "$#{item}"
128
+ # else
129
+ # item = [item].flatten.compact.as_json.map { _1.slice(*columns.map(&:to_s)) }
130
+ # rv = "'#{item.to_json.gsub(/'/, "''")}'"
131
+ # end
132
+ # Raw.sql "json_to_recordset(#{rv}) AS #{QuoteSql.quote_column_name name}(#{column_cast.join(',')})"
133
+ # end
134
+
135
+ def json_recordset(rows = @quotable)
136
+ case rows
137
+ when Array, Integer
138
+ when Hash
139
+ rows = [rows]
140
+ else
141
+ raise ArgumentError, "just Array<Hash> or Hash (for a single value)"
142
+ end
147
143
  casts = self.casts(name)
148
- columns = self.columns(name) || casts&.keys
149
- column_cast = columns&.map { "#{QuoteSql.quote_column_name(_1)} #{casts&.dig(_1) || "TEXT"}" }
150
- if item.is_a? Integer
151
- rv = "$#{item}"
144
+ columns = (self.columns(name) || casts&.keys)&.map(&:to_sym)
145
+ if rows.is_a? Integer
146
+ rv = "$#{rows}"
152
147
  else
153
- item = [item].flatten.compact.as_json.map { _1.slice(*columns.map(&:to_s)) }
154
- rv = "'#{item.to_json.gsub(/'/, "''")}'"
148
+ rows = rows.compact.map { _1.transform_keys(&:to_sym) }
149
+ raise ArgumentError, "all values need to be type Hash" if rows.any? { not _1.is_a?(Hash) }
150
+ columns ||= rows.flat_map { _1.keys.sort }.uniq.map(&:to_sym)
151
+ rv = "'#{rows.map{ _1.slice(*columns)}.to_json.gsub(/'/, "''")}'"
152
+ end
153
+ raise ArgumentError, "table or columns has to be present" if columns.blank?
154
+ column_cast = columns.map do |column|
155
+ "#{QuoteSql.quote_column_name column} #{casts&.dig(column, :sql_type) || "TEXT"}"
155
156
  end
156
- Raw.sql "json_to_recordset(#{rv}) AS #{QuoteSql.quote_column_name name}(#{column_cast.join(',')})"
157
+ Raw.sql "json_to_recordset(#{rv}) AS #{QuoteSql.quote_column_name(name || "json")}(#{column_cast.join(',')})"
157
158
  end
158
159
 
159
- def data_values(item = @quotable)
160
- item = Array(item).compact
161
- column_names = columns(name)
162
- if column_names.is_a? Hash
163
- types = column_names.values.map { "::#{_1.upcase}" if _1 }
164
- column_names = column_names.keys
160
+ def values(rows = @quotable)
161
+ if rows.class.to_s[/^(Arel::Nodes::SqlLiteral|QuoteSql::Raw)$/]
162
+ return Raw.sql((item[/^\s*\(/] and item[/\)\s*$/]) ? rows : "(#{rows})")
165
163
  end
166
- if item.all? { _1.is_a?(Hash) }
167
- column_names ||= item.flat_map { _1.keys.sort }.uniq
168
- item.map! { _1.fetch_values(*column_names) {} }
169
- end
170
- if item.all? { _1.is_a?(Array) }
171
- length, overflow = item.map { _1.length }.uniq
172
- raise ArgumentError, "all values need to have the same length" if overflow
173
- column_names ||= (1..length).map { "column#{_1}" }
174
- raise ArgumentError, "#{name}_columns and value lengths need to be the same" if column_names.length != length
175
- values = item.map { value(_1) }
164
+ case rows
165
+ when Array
166
+ when Hash
167
+ rows = [rows]
176
168
  else
177
- raise ArgumentError, "Either all type Hash or Array"
169
+ raise ArgumentError, "just raw or Array<Hash, Integer> or Hash (for a single value)"
178
170
  end
179
- if types.present?
180
- value = values[0][1..-2].split(/\s*,\s*/)
181
- types.each_with_index { value[_2] << _1 || "" }
182
- values[0] = "(" + value.join(",") + ")"
171
+ casts = self.casts(name)
172
+ columns = (self.columns(name) || casts&.keys)&.map(&:to_sym)
173
+ raise ArgumentError, "all values need to be type Hash" if rows.any? { not _1.is_a?(Hash) }
174
+ columns ||= rows.flat_map { _1.keys.sort }.uniq.map(&:to_sym)
175
+ values = rows.each_with_index.map do |row, i|
176
+ row.transform_keys(&:to_sym)
177
+ if i == 0 and casts.present?
178
+ columns.map{ "#{_quote(row[_1])}::#{casts&.dig(_1, :sql_type) || "TEXT"}" }
179
+ else
180
+ columns.map{ _quote(row[_1]) }
181
+ end.then { "(#{_1.join(",")})"}
183
182
  end
184
- # values[0] { _1 << types[_1] || ""}
185
- Raw.sql "(VALUES #{values.join(",")}) AS #{_ident name} (#{_ident column_names})"
186
- end
187
183
 
188
- def insert_values(item = @quotable)
189
- case item
190
- when Arel::Nodes::SqlLiteral
191
- item = Raw.sql("(#{item})") unless item[/^\s*\(/] and item[/\)\s*$/]
192
- return item
193
- when Array
194
- item.compact!
195
- column_names = (@qsql.quotes[:columns] || @qsql.quotes[:column_names]).dup
196
- types = []
197
- if column_names.is_a? Hash
198
- types = column_names.values.map { "::#{_1.upcase}" if _1 }
199
- column_names = column_names.keys
200
- elsif column_names.is_a? Array
201
- column_names = column_names.map do |column|
202
- types << column.respond_to?(:sql_type) ? "::#{column.sql_type}" : nil
203
- column.respond_to?(:name) ? column.name : column
204
- end
205
- end
184
+ Raw.sql "(VALUES #{values.join(",")}) AS #{QuoteSql.quote_column_name(name || "values")} (#{columns.map{QuoteSql.quote_column_name(_1)}.join(",")})"
185
+ end
206
186
 
207
- if item.all? { _1.is_a?(Hash) }
208
- column_names ||= item.flat_map { _1.keys.sort }.uniq
209
- item.map! { _1.fetch_values(*column_names) {} }
210
- end
211
187
 
212
- if item.all? { _1.is_a?(Array) }
213
- length, overflow = item.map { _1.length }.uniq
214
- raise ArgumentError, "all values need to have the same length" if overflow
215
- raise ArgumentError, "#{name}_columns and value lengths need to be the same" if column_names and column_names.length != length
216
- values = item.map { value(_1) }
217
- else
218
- raise ArgumentError, "Either all type Hash or Array"
219
- end
220
- if column_names.present?
221
- Raw.sql "(#{_ident column_names}) VALUES #{values.join(",")}"
222
- else
223
- Raw.sql "VALUES #{values.join(",")}"
224
- end
188
+ def insert_values(rows = @quotable)
189
+ if rows.class.to_s[/^(Arel::Nodes::SqlLiteral|QuoteSql::Raw)$/]
190
+ return Raw.sql((item[/^\s*\(/] and item[/\)\s*$/]) ? rows : "(#{rows})")
191
+ end
192
+ case rows
193
+ when Array
225
194
  when Hash
226
- value([item])
195
+ rows = [rows]
196
+ else
197
+ raise ArgumentError, "just raw or Array<Hash> or Hash (for a single value)"
227
198
  end
228
- end
229
199
 
230
- def cast
231
- if m = key.to_s[CASTS]
232
- m[2..].sub(CASTS) { _1.tr("_", " ") }
233
- end
200
+ rows = rows.compact.map { _1.transform_keys(&:to_sym) }
201
+ raise ArgumentError, "all values need to be type Hash" if rows.any? { not _1.is_a?(Hash) }
202
+ casts = self.casts(name)
203
+ columns = (self.columns(name) || casts&.keys || rows.flat_map { _1.keys.sort }.uniq).map(&:to_sym)
204
+ raise ArgumentError, "table or columns has to be present" if columns.blank?
205
+ columns -= (casts&.select { _2[:virtual] }&.keys || [])
206
+ values = rows.map { _value(_1.fetch_values(*columns) { :default }) }
207
+ Raw.sql("(#{columns.map { QuoteSql.quote_column_name _1 }.join(",")}) VALUES #{values.join(",")}")
234
208
  end
235
209
 
236
- def json?
237
- !!key[/(^|_)(jsonb?)$/]
210
+ def json?(cast = self.cast)
211
+ cast.to_s[/jsonb?$/i]
238
212
  end
239
213
 
240
- private def _quote(item = @quotable, cast = self.cast)
241
- rv = QuoteSql.quote(item)
242
- if cast
243
- rv << "::#{cast.upcase}"
244
- rv << "[]" * rv.depth if rv[/^ARRAY/]
245
- end
246
- Raw.sql rv
214
+ private def _quote(item = @quotable)
215
+ Raw.sql QuoteSql.quote(item)
247
216
  end
248
217
 
249
218
  private def _quote_column_name(name)
250
219
  Raw.sql name.scan(/(?:^|")?([^."]+)/).map { QuoteSql.quote_column_name _1 }.join(".")
251
220
  end
252
221
 
253
- def quote(item = @quotable)
254
- case item.class.to_s
255
- when "Arel::Nodes::SqlLiteral", "QuoteSql::Raw"
256
- return Raw.sql(item)
257
- when "Array"
258
- return _quote(item.to_json) if json?
259
- _quote(item)
260
- when "Hash"
261
- _quote(item.to_json, :jsonb)
262
- else
263
- return Raw.sql item.to_sql if item.respond_to? :to_sql
264
- _quote(item)
222
+ private def _quote_array(items)
223
+ rv = items.map do |i|
224
+ if i.is_a?(Array)
225
+ _quote_array(i)
226
+ elsif self.cast[/jsonb?/i]
227
+ _quote(i.to_json)
228
+ else
229
+ quote(i)
230
+ end
231
+ end
232
+ "[#{rv.join(",")}]"
233
+ end
234
+
235
+ def quote_hash(item)
236
+ item.compact! if item.delete(nil) == false
237
+ case self.cast
238
+ when /hstore/i
239
+ _quote(item.map { "#{_1}=>#{_2.nil? ? 'NULL' : _2}" }.join(","))
240
+ when NilClass, ""
241
+ "#{_quote(item.to_json)}::JSONB"
242
+ when /jsonb?/i
243
+ _quote(item.to_json)
265
244
  end
266
245
  end
267
246
 
247
+ def quote(item = @quotable, cast = nil)
248
+ Raw.sql case item.class.to_s
249
+ when "Arel::Nodes::SqlLiteral", "QuoteSql::Raw"
250
+ item
251
+ when "Array"
252
+ if json? or self.cast.blank?
253
+ rv = _quote(item.to_json)
254
+ self.cast.present? ? rv : "#{rv}::JSONB"
255
+ else
256
+ "ARRAY#{_quote_array(item)}"
257
+ end
258
+ when "Hash"
259
+ quote_hash(item)
260
+ else
261
+ if item.respond_to? :to_sql
262
+ item.to_sql
263
+ elsif json?
264
+ _quote(item.to_json)
265
+ else
266
+ _quote(item)
267
+ end
268
+ end
269
+ end
270
+
268
271
  def column_names(item = @quotable)
269
272
  if item.respond_to?(:column_names)
270
273
  item = item.column_names
@@ -1,5 +1,6 @@
1
1
  class QuoteSql::Test
2
2
  private
3
+
3
4
  def test_columns
4
5
  expected <<~SQL
5
6
  SELECT x, "a", "b", "c", "d"
@@ -49,22 +50,46 @@ class QuoteSql::Test
49
50
  )
50
51
  end
51
52
 
52
- # def test_binds
53
- # expected <<~SQL
54
- # SELECT $1, $2::UUID, $1 AS get_bind_1_again FROM "my_table"
55
- # SQL
56
- # QuoteSql.new("SELECT %bind, %bind__uuid, %bind1 AS get_bind_1_again FROM %table").quote(
57
- # table: "my_table"
58
- # )
59
- # end
53
+ def test_values_hash_active_record
54
+ table = create_active_record_class("tasks") do |t|
55
+ t.text :name
56
+ t.integer :n1, default: 1, null: false
57
+ t.virtual :v1, type: :boolean, stored: true, as: "FALSE"
58
+ t.timestamps
59
+ end
60
+ updated_at = Date.new(2024,1,1)
61
+ expected <<~SQL
62
+ INSERT INTO "tasks" ("id", "name", "n1", "created_at", "updated_at") VALUES (DEFAULT, 'Task1', 1, DEFAULT, DEFAULT), (DEFAULT, 'Task2', DEFAULT, DEFAULT, '2024-01-01')
63
+ SQL
64
+ values = [
65
+ {n1: 1, name: "Task1"},
66
+ {name: "Task2", updated_at: }
67
+ ]
68
+ QuoteSql.new(<<~SQL).quote(table:, values:)
69
+ INSERT INTO %table %values
70
+ SQL
71
+ end
60
72
 
61
- def test_from_values_array
73
+ def test_values_hash_active_record_select_columns
74
+ table = create_active_record_class("tasks") do |t|
75
+ t.text :name
76
+ t.integer :n1, default: 1, null: false
77
+ t.virtual :v1, type: :boolean, stored: true, as: "FALSE"
78
+ t.timestamps
79
+ end
62
80
  expected <<~SQL
63
- SELECT * FROM (VALUES ('a',1,TRUE,NULL)) AS "x" ("column1","column2","column3","column4")
81
+ INSERT INTO "tasks" ("name") VALUES ('Task1'), ('Task2')
82
+ SQL
83
+ values = [
84
+ {n1: 1, name: "Task1"},
85
+ {name: "Task2", id: "12345" }
86
+ ]
87
+ QuoteSql.new(<<~SQL).quote(table:, values:, columns: %i[name])
88
+ INSERT INTO %table %values
64
89
  SQL
65
- "SELECT * FROM %x_values".quote_sql(x_values: [['a', 1, true, nil]])
66
90
  end
67
91
 
92
+
68
93
  def test_from_values_hash_no_columns
69
94
  expected <<~SQL
70
95
  SELECT * FROM (VALUES ('a', 1, true, NULL), ('a', 1, true, NULL), (NULL, 1, NULL, 2)) AS "y" ("a", "b", "c", "d")
@@ -93,7 +118,7 @@ class QuoteSql::Test
93
118
  ) AS "x" ("a", "b", "c", "d")
94
119
  SQL
95
120
  "SELECT * FROM %x_values".quote_sql(
96
- x_columns: {
121
+ x_casts: {
97
122
  a: "text",
98
123
  b: "integer",
99
124
  c: "boolean",
@@ -106,12 +131,6 @@ class QuoteSql::Test
106
131
  ])
107
132
  end
108
133
 
109
- def test_insert_values_array
110
- expected <<~SQL
111
- INSERT INTO x VALUES ('a', 1, true, NULL)
112
- SQL
113
- "INSERT INTO x %values".quote_sql(values: [['a', 1, true, nil]])
114
- end
115
134
 
116
135
  def test_insert_values_hash
117
136
  expected <<~SQL
@@ -124,29 +143,91 @@ class QuoteSql::Test
124
143
  expected <<~SQL
125
144
  SELECT * FROM json_to_recordset('[{"a":1,"b":"foo"},{"a":"2"}]') as "x" ("a" int, "b" text)
126
145
  SQL
127
- "SELECT * FROM %x_json".quote_sql(x_casts: {a: "int", b: "text"}, x_json: [{ a: 1, b: 'foo'}, {a: '2', c: 'bar'}])
146
+ "SELECT * FROM %x_json".quote_sql(x_casts: { a: "int", b: "text" }, x_json: [{ a: 1, b: 'foo' }, { a: '2', c: 'bar' }])
128
147
  end
129
148
 
130
149
  def test_json_insert
131
150
  expected <<~SQL
132
- INSERT INTO users (name, color) SELECT * from json_to_recordset('[{"name":"auge","color":"#611333"}]') AS "x"("name" text,"color" text)
151
+ INSERT INTO users ("name", "color") SELECT * from json_to_recordset('[{"name":"auge","color":"#611333"}]') AS "json"("name" text,"color" text)
133
152
  SQL
134
- x_json = {"first_name"=>nil, "last_name"=>nil, "stripe_id"=>nil, "credits"=>nil, "avatar"=>nil, "name"=>"auge", "color"=>"#611333", "founder"=>nil, "language"=>nil, "country"=>nil, "data"=>{}, "created_at"=>"2020-11-19T09:30:18.670Z", "updated_at"=>"2020-11-19T09:40:00.063Z"}
135
- "INSERT INTO users (name, color) SELECT * from %x_json".quote_sql(x_casts: {name: "text", color: "text"}, x_json:)
153
+ json = { "first_name" => nil, "last_name" => nil, "stripe_id" => nil, "credits" => nil, "avatar" => nil, "name" => "auge", "color" => "#611333", "founder" => nil, "language" => nil, "country" => nil, "data" => {}, "created_at" => "2020-11-19T09:30:18.670Z", "updated_at" => "2020-11-19T09:40:00.063Z" }
154
+ "INSERT INTO users (%columns) SELECT * from %json".quote_sql(columns: %i[name color], json:)
136
155
  end
137
156
 
138
157
  def test_from_json_bind
139
158
  expected <<~SQL
140
- Select * From json_to_recordset($1) AS "x"("a" int,"b" text,"c" boolean)
159
+ Select * From json_to_recordset($1) AS "x"("a" int,"b" text,"c" boolean)
141
160
  SQL
142
- QuoteSQL("Select * From %x_json", x_json: 1, x_casts: {a: "int", b: "text", c: "boolean"})
161
+ QuoteSQL("Select * From %x_json", x_json: 1, x_casts: { a: "int", b: "text", c: "boolean" })
143
162
  end
144
163
 
145
164
  def test_insert_json_bind
146
165
  expected <<~SQL
147
- INSERT INTO table ("a","b","c") Select * From json_to_recordset($1) AS "x"("a" int,"b" text,"c" boolean)
166
+ INSERT INTO table ("a","b","c") Select * From json_to_recordset($1) AS "x"("a" int,"b" text,"c" boolean)
167
+ SQL
168
+ QuoteSQL("INSERT INTO table (%x_columns) Select * From %x_json", x_json: 1, x_casts: { a: "int", b: "text", c: "boolean" })
169
+ end
170
+
171
+ def test_cast_values
172
+ expected <<~SQL
173
+ SELECT
174
+ 'abc'::TEXT,
175
+ '"abc"'::JSON,
176
+ '["cde",null,"fgh"]'::JSONB,
177
+ ARRAY['cde', NULL, 'fgh']::TEXT[],
178
+ ARRAY['"cde"', 'null', '"fgh"']::JSON[],
179
+ '{"foo":"bar","go":1,"strip_null":null}'::JSONB not_compact,
180
+ '{"foo":"bar","go":1}'::JSON compact,
181
+ 'foo=>bar,go=>1,strip_null=>NULL'::HSTORE,
182
+ ARRAY[[1,2,3],[1,2,3]]::INT[][]
183
+ SQL
184
+ array1 = array2 = array3 = ["cde", nil, "fgh"]
185
+ array4 = [[1, 2, 3], [1, 2, 3]]
186
+ hash = { foo: "bar", "go": 1, strip_null: nil }
187
+ QuoteSQL(<<~SQL, field1: 'abc', array1:, array2:, array3:, array4:, hash:, not_compact: hash, compact: hash.merge(nil => false))
188
+ SELECT
189
+ %field1::TEXT,
190
+ %field1::JSON,
191
+ %array1,
192
+ %array2::TEXT[],
193
+ %array3::JSON[],
194
+ %not_compact not_compact,
195
+ %compact::JSON compact,
196
+ %hash::HSTORE,
197
+ %array4::INT[][]
198
+ SQL
199
+ end
200
+
201
+ def test_columns_with_tables
202
+ expected <<~SQL
203
+ SELECT "profiles"."a", "profiles"."b",
204
+ "relationships"."a", "relationships"."b",
205
+ relationship_timestamp("relationships".*)
206
+ SQL
207
+
208
+ profile_table = "profiles"
209
+ relationship_table = "relationships"
210
+ relationship_columns = profile_columns = %i[a b]
211
+
212
+ <<~SQL.quote_sql(profile_columns:, profile_table:, relationship_columns:, relationship_table:)
213
+ SELECT %profile_columns, %relationship_columns,
214
+ relationship_timestamp(%relationship_table.*)
215
+ SQL
216
+ end
217
+
218
+ def test_active_record
219
+ table = create_active_record_class("users") do |t|
220
+ t.text :first_name
221
+ t.integer :n1, default: 1, null: false
222
+ t.virtual :v1, type: :boolean, stored: true, as: "FALSE"
223
+ t.timestamps default: "CURRENT_TIMESTAMP", null: false
224
+ end
225
+ expected <<~SQL
226
+ SELECT "id", "first_name", "n1", "v1", "created_at", "updated_at" FROM "users"
227
+ SQL
228
+ <<~SQL.quote_sql(table:)
229
+ SELECT %columns FROM %table
148
230
  SQL
149
- QuoteSQL("INSERT INTO table (%x_columns) Select * From %x_json", x_json: 1, x_casts: {a: "int", b: "text", c: "boolean"})
150
231
  end
151
232
 
152
233
  # def test_q3
@@ -172,7 +253,6 @@ class QuoteSql::Test
172
253
  # )
173
254
  # end
174
255
 
175
-
176
256
  public
177
257
 
178
258
  def all
@@ -187,16 +267,20 @@ class QuoteSql::Test
187
267
  def run(name, all = false)
188
268
  name = name.to_s.sub(/^test_/, "")
189
269
  rv = ["🧪 #{name}"]
270
+ puts(*rv)
190
271
  @expected = nil
191
272
  @test = send("test_#{name}")
192
273
  if sql.gsub(/\s+/, "")&.downcase&.strip == expected&.gsub(/\s+/, "")&.downcase&.strip
193
- tables = @test.tables.to_h { [[_1, "table"].compact.join("_"), _2] }
194
- columns = @test.instance_variable_get(:@columns).to_h { [[_1, "columns"].compact.join("_"), _2] }
195
- rv += [
196
- "QuoteSql.new(\"#{@test.original}\").quote(#{{**tables, **columns, **@test.quotes }.inspect}).to_sql", "🎯 #{expected}", "✅ #{sql}"]
274
+ # tables = @test.tables.to_h { [[_1, "table"].compact.join("_"), _2] }
275
+ # columns = @test.instance_variable_get(:@columns).to_h { [[_1, "columns"].compact.join("_"), _2] }
276
+ #"QuoteSql.new(\"#{@test.original}\").quote(#{{ **tables, **columns, **@test.quotes }.inspect}).to_sql",
277
+ rv += ["🎯 #{expected}", "✅ #{sql}"]
278
+
197
279
  @success << rv if @success
198
280
  else
199
281
  rv += [@test.inspect, "🎯 #{expected}", "❌ #{sql}"]
282
+ rv << "🎯 " + expected&.gsub(/\s+/, "")&.downcase&.strip
283
+ rv << "❌ " + sql.gsub(/\s+/, "")&.downcase&.strip
200
284
  @fail << rv if @fail
201
285
  end
202
286
  rescue => exc
@@ -214,18 +298,170 @@ class QuoteSql::Test
214
298
  @test.to_sql
215
299
  end
216
300
 
217
- class PseudoActiveRecord
218
- def self.table_name
219
- "pseudo_active_records"
301
+ class PseudoActiveRecordKlass
302
+ class Column
303
+ def initialize(name, type, **options)
304
+ @name = name.to_s
305
+ @type = type
306
+ @null = options[:null]
307
+ type = options[:type] if @type == :virtual
308
+ @sql_type = DATATYPES[/^#{type}$/]
309
+ unless @type == :virtual or options[:default].nil?
310
+ @default = options[:default]
311
+ end
312
+ end
313
+
314
+ attr_reader :name, :type, :sql_type, :null, :default, :default_function
315
+
316
+ def default?
317
+ ! (@default || @default_function).nil?
318
+ end
220
319
  end
320
+ class Columns
321
+ def initialize(&block)
322
+ @rv = []
323
+ block.call(self)
324
+ end
221
325
 
222
- def self.column_names
223
- %w(id column1 column2)
326
+ def to_a
327
+ @rv
328
+ end
329
+
330
+ def timestamps(**options)
331
+ @rv << Column.new( :created_at, :timestamp, null: false, **options)
332
+ @rv << Column.new( :updated_at, :timestamp, null: false, **options)
333
+ end
334
+
335
+ def method_missing(type, name, *args, **options)
336
+ @rv << Column.new(name, type, *args, **options)
337
+ end
224
338
  end
225
339
 
226
- def to_qsl
227
- "SELECT * FROM #{self.class.table_name}"
340
+ def initialize(table_name, id: :uuid, &block)
341
+ @table_name = table_name
342
+ @columns = Columns.new(&block).to_a
343
+ unless id.nil? or id == false
344
+ @columns.unshift(Column.new("id", *[id], null: false, default: "gen_random_uuid()"))
345
+ end
228
346
  end
347
+
348
+ attr_reader :table_name, :columns
349
+
350
+ def column_names
351
+ @columns.map { _1.name }
352
+ end
353
+ end
354
+
355
+ def create_active_record_class(table_name, **options, &block)
356
+ PseudoActiveRecordKlass.new(table_name, **options, &block)
357
+ end
358
+
359
+
360
+ def datatype
361
+ errors = {}
362
+ success = []
363
+ spaces = ->(*) { " " * (rand(4) + 1) }
364
+
365
+ DATATYPES.each_line(chomp: true) do |line|
366
+
367
+ l = line.gsub(/\s+/, &spaces).gsub(/(?<=\()\d+|\d+(?=\))/) { "#{spaces.call}#{rand(10) + 1}#{spaces.call}" }.gsub(/\(/) { "#{spaces.call}(" }
368
+
369
+ m = "jgj hsgjhsgfjh ag %field::#{l} asldfalskjdfl".match(QuoteSql::CASTS)
370
+ if m.present? and l == m[1]
371
+ success << line
372
+ else
373
+ errors[line] = m&.to_a
374
+ end
375
+ line = line + "[]" * (rand(3) + 1)
376
+ m = "jgj hsgjhsgfjh ag %field::#{line} asldfalskjdfl".match(QuoteSql::CASTS)
377
+ if m.present? and line == m[1] + m[2]
378
+ success << line
379
+ else
380
+ errors[line] = m&.to_a
381
+ end
382
+ end
383
+ puts success.sort.inspect
384
+ ap errors
229
385
  end
230
386
 
231
- end
387
+ DATATYPES = <<-DATATYPES
388
+ bigint
389
+ int8
390
+ bigserial
391
+ serial8
392
+ bit
393
+ bit (1)
394
+ bit varying
395
+ varbit
396
+ bit varying (2)
397
+ varbit (2)
398
+ boolean
399
+ bool
400
+ box
401
+ bytea
402
+ character
403
+ char
404
+ character (1)
405
+ char (1)
406
+ character varying
407
+ varchar
408
+ character varying (1)
409
+ varchar (1)
410
+ cidr
411
+ circle
412
+ date
413
+ double precision
414
+ float8
415
+ inet
416
+ integer
417
+ int
418
+ int4
419
+ interval
420
+ interval (1)
421
+ json
422
+ jsonb
423
+ line
424
+ lseg
425
+ macaddr
426
+ macaddr8
427
+ money
428
+ numeric
429
+ numeric(10,3)
430
+ decimal
431
+ decimal(10,3)
432
+ path
433
+ pg_lsn
434
+ pg_snapshot
435
+ point
436
+ polygon
437
+ real
438
+ float4
439
+ smallint
440
+ int2
441
+ smallserial
442
+ serial
443
+ serial2
444
+ serial4
445
+ text
446
+ time
447
+ time(1)
448
+ time without time zone
449
+ time(1) without time zone
450
+ time with time zone
451
+ time(2) with time zone
452
+ timetz
453
+ timestamp
454
+ timestamp(1)
455
+ timestamp without time zone
456
+ timestamp(1) without time zone
457
+ timestamp with time zone
458
+ timestamp(1) with time zone
459
+ timestamptz
460
+ tsquery
461
+ tsvector
462
+ txid_snapshot
463
+ uuid
464
+ xml
465
+ DATATYPES
466
+
467
+ end
@@ -1,3 +1,3 @@
1
1
  class QuoteSql
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.8"
3
3
  end
data/lib/quote_sql.rb CHANGED
@@ -4,24 +4,28 @@ Dir.glob(__FILE__.sub(/\.rb$/, "/*.rb")).each { require(_1) unless _1[/(deprecat
4
4
  class QuoteSql
5
5
 
6
6
  DATA_TYPES_RE = %w(
7
- (?:small|big)(?:int|serial)
8
- bit bool(?:ean)? box bytea cidr circle date
7
+ (?>character\\s+varying|bit\\s+varying|character|varbit|varchar|char|bit|interval)(?>\\s*\\(\\s*\\d+\\s*\\))?
8
+ (?>numeric|decimal)(?>\\s*\\(\\s*\\d+\\s*,\\s*\\d+\\s*\\))?
9
+ timestamptz timetz
10
+ time(?>stamp)?(?>\\s*\\(\\s*\\d+\\s*\\))?(?>\\s+with(?>out)?\\s+time\\s+zone)?
11
+ integer
12
+ (?>small|big)(?>int|serial)
13
+ bool(?>ean)? box bytea cidr circle date
9
14
  (?:date|int[48]|num|ts(?:tz)?)(?:multi)?range
10
15
  macaddr8?
11
- jsonb?
12
- ts(?:query|vector)
13
- float[48] (?:int|serial)[248]?
14
- double_precision inet
15
- integer line lseg money path pg_lsn
16
- pg_snapshot point polygon real text timestamptz timetz
17
- txid_snapshot uuid xml
18
- (bit_varying|varbit|character|char|character varying|varchar)(_\\(\\d+\\))?
19
- (numeric|decimal)(_\\(\d+_\d+\\))?
20
- interval(_(YEAR|MONTH|DAY|HOUR|MINUTE|SECOND|YEAR_TO_MONTH|DAY_TO_HOUR|DAY_TO_MINUTE|DAY_TO_SECOND|HOUR_TO_MINUTE|HOUR_TO_SECOND|MINUTE_TO_SECOND))?(_\\(\d+\\))?
21
- time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
16
+ ts(?>query|vector)
17
+ float[48]
18
+ (?:int|serial)[248]?
19
+ double\\s+precision
20
+ jsonb json
21
+ inet
22
+ line lseg money path
23
+ pg_lsn pg_snapshot txid_snapshot
24
+ point polygon real text
25
+ uuid xml hstore
22
26
  ).join("|")
23
27
 
24
- CASTS = Regexp.new("__(#{DATA_TYPES_RE})$", "i")
28
+ CASTS = Regexp.new("::(#{DATA_TYPES_RE})((?:\\s*\\[\\s*\\d?\\s*\\])*)", "i")
25
29
 
26
30
  def self.conn
27
31
  raise ArgumentError, "You need to define a database connection function"
@@ -46,20 +50,49 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
46
50
  attr_reader :sql, :quotes, :original, :binds, :tables, :columns
47
51
 
48
52
  def table(name = nil)
49
- @tables[name&.to_sym].dup
53
+ table = @tables[name&.to_sym]
54
+ table.is_a?(Class) ? table : table.dup
50
55
  end
51
56
 
57
+ def table=(value)
58
+ name, table = value
59
+ name = name&.to_sym
60
+ @tables[name] = table
61
+ if table.respond_to?(:columns)
62
+ @casts[name] = table.columns.to_h do |c|
63
+ [c.name.to_sym, { sql_type: c.sql_type, default: (c.default || c.default_function rescue nil).present?, virtual: c.type == :virtual }]
64
+ end if @casts[name].blank?
65
+ elsif table.respond_to?(:column_names)
66
+ @casts[name] = table.column_names.to_h { [_1.to_sym, nil] } if @casts[name].blank?
67
+ end
68
+ end
69
+
70
+ alias tables= table=
71
+
72
+ def column=(value)
73
+ name, column = value
74
+ name = name&.to_sym
75
+ @columns[name] = column
76
+ end
77
+
78
+ alias columns= column=
79
+
80
+ def cast=(value)
81
+ name, cast = value
82
+ name = name&.to_sym
83
+ raise ArgumentError unless cast.is_a?(Hash)
84
+ (@casts[name] ||= {}).update(cast.transform_values { _1.is_a?(Hash) ? _1 : { sql_type: _1 } })
85
+ end
86
+
87
+ alias casts= cast=
88
+
52
89
  def columns(name = nil)
53
- @columns[name&.to_sym].dup
90
+ name = name&.to_sym
91
+ @columns[name] || @casts[name]&.keys&.map(&:to_s)
54
92
  end
55
93
 
56
94
  def casts(name = nil)
57
- unless rv = @casts[name&.to_sym]
58
- table = table(name) or return
59
- return unless table.respond_to? :columns
60
- rv = table.columns.to_h { [_1.name.to_sym, _1.sql_type] }
61
- end
62
- rv
95
+ @casts[name&.to_sym]
63
96
  end
64
97
 
65
98
  # Add quotes keys are symbolized
@@ -69,7 +102,8 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
69
102
  _, name, type = quote.to_s.match(re)&.to_a
70
103
  value = quotes.delete quote
71
104
  value = Raw.sql(value) if value.class.to_s == "Arel::Nodes::SqlLiteral"
72
- instance_variable_get(:"@#{type.sub(/s*$/,'s')}")[name&.to_sym] = value
105
+ send(:"#{type}=", [name, value])
106
+ # instance_variable_get(:"@#{type.sub(/s*$/,'s')}")[name&.to_sym] = value
73
107
  end
74
108
  @quotes.update quotes.transform_keys(&:to_sym)
75
109
  self
@@ -87,7 +121,7 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
87
121
  if binds.present? and sql.scan(/(?<=\$)\d+/).map(&:to_i).max != binds.length
88
122
  raise ArgumentError, "Wrong number of binds"
89
123
  end
90
- _exec(sql, binds, prepare: false, async: false)
124
+ _exec(sql, binds, prepare: false)
91
125
  rescue => exc
92
126
  STDERR.puts exc.inspect, self.inspect
93
127
  raise exc
@@ -127,7 +161,7 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
127
161
  if @binds.length != record.length
128
162
  next RuntimeError.new("binds are not equal arguments, #{record.inspect}")
129
163
  end
130
- _exec(sql, record, prepare: false, async: false)
164
+ _exec(sql, record, prepare: false)
131
165
  end
132
166
  end
133
167
 
@@ -152,11 +186,8 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
152
186
  def key_matches
153
187
  @sql.scan(MIXIN_RE).map do |full, *key|
154
188
  key = key.compact[0]
155
- if m = key.match(/^(.+)#{CASTS}/i)
156
- _, key, cast = m.to_a
157
- end
158
189
  has_quote = @quotes.key?(key.to_sym) || key.match?(/(table|columns)$/)
159
- [full, key, cast, has_quote]
190
+ [full, key, has_quote]
160
191
  end
161
192
  end
162
193
 
@@ -166,27 +197,28 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
166
197
  loop do
167
198
  s = StringScanner.new(@sql)
168
199
  sql = ""
169
- key_matches.each do |key_match, key, cast, has_quote|
170
- s.scan_until(/(.*?)#{key_match}([a-z0-9_]*)/im)
171
- matched, pre, post = s.matched, s[1], s[2]
172
- if m = key.match(/^bind(\d+)?/im)
173
- if m[1].present?
174
- bind_num = m[1].to_i
175
- @binds[bind_num - 1] ||= cast
176
- raise "bind #{bind_num} already set to #{@binds[bind_num - 1]}" unless @binds[bind_num - 1] == cast
177
- else
178
- @binds << cast
179
- bind_num = @binds.length
180
- end
181
-
182
- matched = "#{pre}$#{bind_num}#{"::#{cast}" if cast.present?}#{post}"
183
- elsif has_quote
184
- quoted = quoter(key)
200
+ key_matches.each do |key_match, key, has_quote|
201
+ s.scan_until(/(.*?)#{key_match}(#{CASTS}?)/im)
202
+ matched, pre, cast = s.matched, s[1], s[2]
203
+ # if m = key.match(/^bind(\d+)?/im)
204
+ # if m[1].present?
205
+ # bind_num = m[1].to_i
206
+ # @binds[bind_num - 1] ||= cast
207
+ # raise "bind #{bind_num} already set to #{@binds[bind_num - 1]}" unless @binds[bind_num - 1] == cast
208
+ # else
209
+ # @binds << cast
210
+ # bind_num = @binds.length
211
+ # end
212
+ #
213
+ # matched = "#{pre}$#{bind_num}#{"::#{cast}" if cast.present?}#{post}"
214
+ # els
215
+ if has_quote
216
+ quoted = quoter(key, cast)
185
217
  unresolved.delete key
186
218
  if (i = quoted.scan MIXIN_RE).present?
187
219
  unresolved += i.map(&:last)
188
220
  end
189
- matched = "#{pre}#{quoted}#{post}"
221
+ matched = "#{pre}#{quoted}#{cast}"
190
222
  end
191
223
  rescue TypeError
192
224
  ensure
@@ -200,8 +232,8 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
200
232
  self
201
233
  end
202
234
 
203
- def quoter(key)
204
- quoter = @resolved[key.to_sym] = Quoter.new(self, key, @quotes[key.to_sym])
235
+ def quoter(key, cast)
236
+ quoter = @resolved[key.to_sym] = Quoter.new(self, key, cast, @quotes[key.to_sym])
205
237
  quoter.to_sql
206
238
  rescue TypeError => exc
207
239
  @resolved[key.to_sym] = exc
@@ -224,8 +256,11 @@ time(stamp)?(_\\(\d+\\))?(_with(out)?_time_zone)?
224
256
 
225
257
  def self.test(which = :all)
226
258
  require __dir__ + "/quote_sql/test.rb"
227
- if which == :all
259
+ case which
260
+ when :all
228
261
  Test.new.all
262
+ when :datatype
263
+ Test.new.datatype
229
264
  else
230
265
  Test.new.run(which)
231
266
  end
@@ -239,9 +274,3 @@ end
239
274
 
240
275
  QuoteSql.include QuoteSql::Formater
241
276
 
242
- class Array
243
- def depth
244
- select { _1.is_a?(Array) }.map { _1.depth.to_i + 1 }.max || 1
245
- end
246
- end
247
-
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quote-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Kufner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-27 00:00:00.000000000 Z
11
+ date: 2024-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: niceql