torque-postgresql 2.0.6 → 2.1.3
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/lib/torque/postgresql/adapter/database_statements.rb +1 -15
- data/lib/torque/postgresql/adapter/schema_dumper.rb +2 -2
- data/lib/torque/postgresql/adapter/schema_statements.rb +5 -1
- data/lib/torque/postgresql/adapter.rb +16 -0
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +17 -10
- data/lib/torque/postgresql/attributes/builder/enum.rb +5 -5
- data/lib/torque/postgresql/attributes/enum.rb +1 -1
- data/lib/torque/postgresql/attributes/enum_set.rb +1 -1
- data/lib/torque/postgresql/base.rb +7 -1
- data/lib/torque/postgresql/insert_all.rb +26 -0
- data/lib/torque/postgresql/version.rb +1 -1
- data/lib/torque/postgresql.rb +1 -1
- data/spec/factories/item.rb +5 -0
- data/spec/models/item.rb +3 -0
- data/spec/models/question.rb +3 -0
- data/spec/models/question_select.rb +2 -0
- data/spec/schema.rb +19 -1
- data/spec/tests/belongs_to_many_spec.rb +51 -1
- data/spec/tests/insert_all_spec.rb +89 -0
- data/spec/tests/table_inheritance_spec.rb +21 -2
- metadata +39 -31
- data/lib/torque/postgresql/coder.rb +0 -133
- data/spec/tests/coder_spec.rb +0 -367
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '09e539b25b952fb4d48bd092a90bae032ad8c26f1584cc5a704cdf96985b1e7e'
|
4
|
+
data.tar.gz: d79aa047ac44dc47a25979f71a0b46dfcbdc573f28d18bfcccbcb59b0a693c7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2f75f66a5e0bed5f14006ff4ffd8e32f96bbe17f18ff4603c0706301c87114a1880b1a8f7bd530501d95c8f6dc10c563af6da5a2ba5f4414a31115c5510ca0f
|
7
|
+
data.tar.gz: 930b6381e8e920a274c411c2ba8abfe96a5b8462227c51dbf5ee9f2a41ac88b6f3a3946d4e49cab0017bb271c176d58c37690e5e646b8536476b83a1cba5c62b
|
@@ -123,7 +123,7 @@ module Torque
|
|
123
123
|
SQL
|
124
124
|
|
125
125
|
tables.map do |(table, refs)|
|
126
|
-
[table,
|
126
|
+
[table, PG::TextDecoder::Array.new.decode(refs)]
|
127
127
|
end.to_h
|
128
128
|
end
|
129
129
|
|
@@ -147,20 +147,6 @@ module Torque
|
|
147
147
|
SQL
|
148
148
|
end
|
149
149
|
|
150
|
-
# Extracts the value from a PostgreSQL column default definition.
|
151
|
-
def extract_value_from_default(default)
|
152
|
-
case default
|
153
|
-
# Array elements
|
154
|
-
when /\AARRAY\[(.*)\]\z/
|
155
|
-
# TODO: Improve this since it's not the most safe approach
|
156
|
-
eval(default.gsub(/ARRAY|::\w+(\[\])?/, ''))
|
157
|
-
else
|
158
|
-
super
|
159
|
-
end
|
160
|
-
rescue SyntaxError
|
161
|
-
# If somethin goes wrong with the eval, just return nil
|
162
|
-
end
|
163
|
-
|
164
150
|
end
|
165
151
|
end
|
166
152
|
end
|
@@ -43,13 +43,13 @@ module Torque
|
|
43
43
|
inherited_tables = @connection.inherited_tables
|
44
44
|
sorted_tables = @connection.tables.sort - @connection.views
|
45
45
|
|
46
|
-
stream.puts " # These are the common tables
|
46
|
+
stream.puts " # These are the common tables"
|
47
47
|
(sorted_tables - inherited_tables.keys).each do |table_name|
|
48
48
|
table(table_name, stream) unless ignored?(table_name)
|
49
49
|
end
|
50
50
|
|
51
51
|
if inherited_tables.present?
|
52
|
-
stream.puts " # These are tables that
|
52
|
+
stream.puts " # These are tables that have inheritance"
|
53
53
|
inherited_tables.each do |table_name, inherits|
|
54
54
|
next if ignored?(table_name)
|
55
55
|
|
@@ -70,7 +70,11 @@ module Torque
|
|
70
70
|
|
71
71
|
# Returns all values that an enum type can have.
|
72
72
|
def enum_values(name)
|
73
|
-
select_values(
|
73
|
+
select_values(<<-SQL.squish, 'SCHEMA')
|
74
|
+
SELECT enumlabel FROM pg_enum
|
75
|
+
WHERE enumtypid = #{quote(name)}::regtype::oid
|
76
|
+
ORDER BY enumsortorder
|
77
|
+
SQL
|
74
78
|
end
|
75
79
|
|
76
80
|
# Rewrite the method that creates tables to easily accept extra options
|
@@ -15,6 +15,8 @@ module Torque
|
|
15
15
|
include DatabaseStatements
|
16
16
|
include SchemaStatements
|
17
17
|
|
18
|
+
INJECT_WHERE_REGEX = /(DO UPDATE SET.*excluded\.[^ ]+) RETURNING/.freeze
|
19
|
+
|
18
20
|
# Get the current PostgreSQL version as a Gem Version.
|
19
21
|
def version
|
20
22
|
@version ||= Gem::Version.new(
|
@@ -26,6 +28,20 @@ module Torque
|
|
26
28
|
def extract_table_options!(options)
|
27
29
|
super.merge(options.extract!(:inherits))
|
28
30
|
end
|
31
|
+
|
32
|
+
# Allow filtered bulk insert by adding the where clause. This method is only used by
|
33
|
+
# +InsertAll+, so it somewhat safe to override it
|
34
|
+
def build_insert_sql(insert)
|
35
|
+
super.tap do |sql|
|
36
|
+
if insert.update_duplicates? && insert.where_condition?
|
37
|
+
if insert.returning
|
38
|
+
sql.gsub!(INJECT_WHERE_REGEX, "\\1 WHERE #{insert.where} RETURNING")
|
39
|
+
else
|
40
|
+
sql << " WHERE #{insert.where}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
29
45
|
end
|
30
46
|
|
31
47
|
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend Adapter
|
@@ -16,15 +16,16 @@ module Torque
|
|
16
16
|
elsif !target.empty?
|
17
17
|
load_target.pluck(reflection.association_primary_key)
|
18
18
|
else
|
19
|
-
stale_state
|
19
|
+
stale_state || column_default_value
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
def ids_writer(ids)
|
24
|
-
|
24
|
+
ids = ids.presence || column_default_value
|
25
|
+
owner.write_attribute(source_attr, ids)
|
25
26
|
return unless owner.persisted? && owner.attribute_changed?(source_attr)
|
26
27
|
|
27
|
-
owner.update_attribute(source_attr, ids
|
28
|
+
owner.update_attribute(source_attr, ids)
|
28
29
|
end
|
29
30
|
|
30
31
|
def size
|
@@ -59,9 +60,11 @@ module Torque
|
|
59
60
|
target
|
60
61
|
end
|
61
62
|
|
62
|
-
def build_changes
|
63
|
+
def build_changes(from_target = false)
|
64
|
+
return yield if defined?(@_building_changes) && @_building_changes
|
65
|
+
|
63
66
|
@_building_changes = true
|
64
|
-
yield.tap { ids_writer(ids_reader) }
|
67
|
+
yield.tap { ids_writer(from_target ? ids_reader : stale_state) }
|
65
68
|
ensure
|
66
69
|
@_building_changes = nil
|
67
70
|
end
|
@@ -110,7 +113,7 @@ module Torque
|
|
110
113
|
end
|
111
114
|
|
112
115
|
def insert_record(record, *)
|
113
|
-
super.tap do |saved|
|
116
|
+
(record.persisted? || super).tap do |saved|
|
114
117
|
ids_rewriter(record.read_attribute(klass_attr), :<<) if saved
|
115
118
|
end
|
116
119
|
end
|
@@ -179,23 +182,27 @@ module Torque
|
|
179
182
|
def ids_rewriter(ids, operator)
|
180
183
|
list = owner[source_attr] ||= []
|
181
184
|
list = list.public_send(operator, ids)
|
182
|
-
owner[source_attr] = list.uniq.compact.presence
|
185
|
+
owner[source_attr] = list.uniq.compact.presence || column_default_value
|
183
186
|
|
184
187
|
return if @_building_changes || !owner.persisted?
|
185
188
|
owner.update_attribute(source_attr, list)
|
186
189
|
end
|
187
190
|
|
191
|
+
def column_default_value
|
192
|
+
owner.class.columns_hash[source_attr].default
|
193
|
+
end
|
194
|
+
|
188
195
|
## HAS MANY
|
189
196
|
def replace_records(*)
|
190
|
-
build_changes { super }
|
197
|
+
build_changes(true) { super }
|
191
198
|
end
|
192
199
|
|
193
200
|
def concat_records(*)
|
194
|
-
build_changes { super }
|
201
|
+
build_changes(true) { super }
|
195
202
|
end
|
196
203
|
|
197
204
|
def delete_or_destroy(*)
|
198
|
-
build_changes { super }
|
205
|
+
build_changes(true) { super }
|
199
206
|
end
|
200
207
|
|
201
208
|
def difference(a, b)
|
@@ -33,13 +33,13 @@ module Torque
|
|
33
33
|
def values_methods
|
34
34
|
return @values_methods if defined?(@values_methods)
|
35
35
|
|
36
|
-
prefix = options.fetch(:prefix, nil)
|
37
|
-
suffix = options.fetch(:suffix, nil)
|
36
|
+
prefix = options.fetch(:prefix, nil)
|
37
|
+
suffix = options.fetch(:suffix, nil)
|
38
38
|
|
39
|
-
prefix = attribute
|
40
|
-
suffix =
|
39
|
+
prefix = attribute if prefix == true
|
40
|
+
suffix = attribute if suffix == true
|
41
41
|
|
42
|
-
base =
|
42
|
+
base = [prefix, '%s', suffix].compact.join('_')
|
43
43
|
|
44
44
|
@values_methods = begin
|
45
45
|
values.map do |val|
|
@@ -29,7 +29,7 @@ module Torque
|
|
29
29
|
def include_on(klass, method_name = nil)
|
30
30
|
method_name ||= Torque::PostgreSQL.config.enum.base_method
|
31
31
|
Builder.include_on(klass, method_name, Builder::Enum) do |builder|
|
32
|
-
defined_enums[builder.attribute.
|
32
|
+
defined_enums[builder.attribute.to_s] = builder.subtype.klass
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -32,7 +32,7 @@ module Torque
|
|
32
32
|
def include_on(klass, method_name = nil)
|
33
33
|
method_name ||= Torque::PostgreSQL.config.enum.set_method
|
34
34
|
Builder.include_on(klass, method_name, Builder::Enum, set_features: true) do |builder|
|
35
|
-
defined_enums[builder.attribute.
|
35
|
+
defined_enums[builder.attribute.to_s] = builder.subtype
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
@@ -40,7 +40,7 @@ module Torque
|
|
40
40
|
|
41
41
|
pg_class = ::Arel::Table.new('pg_class')
|
42
42
|
source = ::Arel::Table.new(subclass.table_name, as: 'source')
|
43
|
-
quoted_id = ::Arel::Nodes::Quoted.new(
|
43
|
+
quoted_id = ::Arel::Nodes::Quoted.new(id)
|
44
44
|
|
45
45
|
query = ::Arel::SelectManager.new(pg_class)
|
46
46
|
query.join(source).on(pg_class['oid'].eq(source['tableoid']))
|
@@ -206,6 +206,12 @@ module Torque
|
|
206
206
|
::ActiveRecord::Reflection.add_reflection(self, name, reflection)
|
207
207
|
end
|
208
208
|
|
209
|
+
# Allow extra keyword arguments to be sent to +InsertAll+
|
210
|
+
def upsert_all(attributes, **xargs)
|
211
|
+
xargs = xargs.merge(on_duplicate: :update)
|
212
|
+
::ActiveRecord::InsertAll.new(self, attributes, **xargs).execute
|
213
|
+
end
|
214
|
+
|
209
215
|
protected
|
210
216
|
|
211
217
|
# Allow optional select attributes to be loaded manually when they are
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Torque
|
4
|
+
module PostgreSQL
|
5
|
+
module InsertAll
|
6
|
+
attr_reader :where
|
7
|
+
|
8
|
+
def initialize(*args, where: nil, **xargs)
|
9
|
+
super(*args, **xargs)
|
10
|
+
|
11
|
+
@where = where
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InsertAll::Builder
|
16
|
+
delegate :where, to: :insert_all
|
17
|
+
|
18
|
+
def where_condition?
|
19
|
+
!where.nil?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
ActiveRecord::InsertAll.prepend InsertAll
|
24
|
+
ActiveRecord::InsertAll::Builder.include InsertAll::Builder
|
25
|
+
end
|
26
|
+
end
|
data/lib/torque/postgresql.rb
CHANGED
@@ -22,7 +22,7 @@ require 'torque/postgresql/autosave_association'
|
|
22
22
|
require 'torque/postgresql/auxiliary_statement'
|
23
23
|
require 'torque/postgresql/base'
|
24
24
|
require 'torque/postgresql/inheritance'
|
25
|
-
require 'torque/postgresql/
|
25
|
+
require 'torque/postgresql/insert_all'
|
26
26
|
require 'torque/postgresql/migration'
|
27
27
|
require 'torque/postgresql/relation'
|
28
28
|
require 'torque/postgresql/reflection'
|
data/spec/models/item.rb
ADDED
data/spec/schema.rb
CHANGED
@@ -11,13 +11,14 @@
|
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
12
12
|
|
13
13
|
begin
|
14
|
-
version =
|
14
|
+
version = 73
|
15
15
|
|
16
16
|
raise SystemExit if ActiveRecord::Migrator.current_version == version
|
17
17
|
ActiveRecord::Schema.define(version: version) do
|
18
18
|
self.verbose = false
|
19
19
|
|
20
20
|
# These are extensions that must be enabled in order to support this database
|
21
|
+
enable_extension "pgcrypto"
|
21
22
|
enable_extension "plpgsql"
|
22
23
|
|
23
24
|
# These are user-defined types used on this database
|
@@ -102,6 +103,13 @@ begin
|
|
102
103
|
t.index ["author_id"], name: "index_posts_on_author_id", using: :btree
|
103
104
|
end
|
104
105
|
|
106
|
+
create_table "items", force: :cascade do |t|
|
107
|
+
t.string "name"
|
108
|
+
t.bigint "tag_ids", array: true, default: "{1}"
|
109
|
+
t.datetime "created_at", null: false
|
110
|
+
t.datetime "updated_at", null: false
|
111
|
+
end
|
112
|
+
|
105
113
|
create_table "users", force: :cascade do |t|
|
106
114
|
t.string "name", null: false
|
107
115
|
t.enum "role", subtype: :roles, default: :visitor
|
@@ -118,6 +126,12 @@ begin
|
|
118
126
|
t.datetime "updated_at", null: false
|
119
127
|
end
|
120
128
|
|
129
|
+
create_table "questions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
130
|
+
t.string "title"
|
131
|
+
t.datetime "created_at", null: false
|
132
|
+
t.datetime "updated_at", null: false
|
133
|
+
end
|
134
|
+
|
121
135
|
create_table "activity_books", force: :cascade, inherits: :activities do |t|
|
122
136
|
t.text "description"
|
123
137
|
t.string "url"
|
@@ -132,6 +146,10 @@ begin
|
|
132
146
|
|
133
147
|
create_table "activity_post_samples", force: :cascade, inherits: :activity_posts
|
134
148
|
|
149
|
+
create_table "question_selects", force: :cascade, inherits: :questions do |t|
|
150
|
+
t.string "options", array: true
|
151
|
+
end
|
152
|
+
|
135
153
|
# create_table "activity_blanks", force: :cascade, inherits: :activities
|
136
154
|
|
137
155
|
# create_table "activity_images", force: :cascade, inherits: [:activities, :images]
|
@@ -147,6 +147,22 @@ RSpec.describe 'BelongsToMany' do
|
|
147
147
|
expect(record.tags.count).to be_eql(5)
|
148
148
|
end
|
149
149
|
|
150
|
+
it 'does not trigger after commit on the associated record' do
|
151
|
+
called = false
|
152
|
+
|
153
|
+
tag = FactoryBot.create(:tag)
|
154
|
+
Tag.after_commit { called = true }
|
155
|
+
|
156
|
+
expect(called).to be_falsey
|
157
|
+
|
158
|
+
subject.tags << tag
|
159
|
+
|
160
|
+
expect(subject.tag_ids).to be_eql([tag.id])
|
161
|
+
expect(called).to be_falsey
|
162
|
+
|
163
|
+
Tag.reset_callbacks(:commit)
|
164
|
+
end
|
165
|
+
|
150
166
|
it 'can build an associated record' do
|
151
167
|
record = subject.tags.build(name: 'Test')
|
152
168
|
expect(record).to be_a(other)
|
@@ -225,6 +241,17 @@ RSpec.describe 'BelongsToMany' do
|
|
225
241
|
expect(subject.tags.size).to be_eql(0)
|
226
242
|
end
|
227
243
|
|
244
|
+
it 'can clear the array' do
|
245
|
+
record = Video.create(title: 'B', tags: [initial])
|
246
|
+
expect(record.tags.size).to be_eql(1)
|
247
|
+
|
248
|
+
record.update(tag_ids: [])
|
249
|
+
record.reload
|
250
|
+
|
251
|
+
expect(record.tag_ids).to be_nil
|
252
|
+
expect(record.tags.size).to be_eql(0)
|
253
|
+
end
|
254
|
+
|
228
255
|
it 'can have sum operations' do
|
229
256
|
records = FactoryBot.create_list(:tag, 5)
|
230
257
|
subject.tags.concat(records)
|
@@ -318,7 +345,30 @@ RSpec.describe 'BelongsToMany' do
|
|
318
345
|
expect { query.load }.not_to raise_error
|
319
346
|
end
|
320
347
|
|
321
|
-
context
|
348
|
+
context 'When the attribute has a default value' do
|
349
|
+
subject { FactoryBot.create(:item) }
|
350
|
+
|
351
|
+
it 'will always return the column default value' do
|
352
|
+
expect(subject.tag_ids).to be_a(Array)
|
353
|
+
expect(subject.tag_ids).to be_eql([1])
|
354
|
+
end
|
355
|
+
|
356
|
+
it 'will keep the value as an array even when the association is cleared' do
|
357
|
+
records = FactoryBot.create_list(:tag, 5)
|
358
|
+
subject.tags.concat(records)
|
359
|
+
|
360
|
+
subject.reload
|
361
|
+
expect(subject.tag_ids).to be_a(Array)
|
362
|
+
expect(subject.tag_ids).not_to be_eql([1, *records.map(&:id)])
|
363
|
+
|
364
|
+
subject.tags.clear
|
365
|
+
subject.reload
|
366
|
+
expect(subject.tag_ids).to be_a(Array)
|
367
|
+
expect(subject.tag_ids).to be_eql([1])
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
context 'When record is not persisted' do
|
322
372
|
let(:initial) { FactoryBot.create(:tag) }
|
323
373
|
|
324
374
|
subject { Video.new(title: 'A', tags: [initial]) }
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'InsertAll' do
|
4
|
+
context 'on executing' do
|
5
|
+
before do
|
6
|
+
ActiveRecord::InsertAll.send(:public, :to_sql)
|
7
|
+
allow_any_instance_of(ActiveRecord::InsertAll).to receive(:execute, &:to_sql)
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { Tag }
|
11
|
+
|
12
|
+
let(:entries) { [{ name: 'A' }, { name: 'B' }] }
|
13
|
+
|
14
|
+
it 'does not mess with insert_all' do
|
15
|
+
result = subject.insert_all(entries)
|
16
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
17
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
18
|
+
ON CONFLICT DO NOTHING RETURNING "id"
|
19
|
+
SQL
|
20
|
+
|
21
|
+
result = subject.insert_all(entries, returning: %i[name])
|
22
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
23
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
24
|
+
ON CONFLICT DO NOTHING RETURNING "name"
|
25
|
+
SQL
|
26
|
+
|
27
|
+
result = subject.insert_all(entries, returning: %i[id name])
|
28
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
29
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
30
|
+
ON CONFLICT DO NOTHING RETURNING "id","name"
|
31
|
+
SQL
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not mess with insert_all!' do
|
35
|
+
result = subject.insert_all!(entries)
|
36
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
37
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B') RETURNING "id"
|
38
|
+
SQL
|
39
|
+
|
40
|
+
result = subject.insert_all!(entries, returning: %i[name])
|
41
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
42
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B') RETURNING "name"
|
43
|
+
SQL
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'does not mess with upsert without where' do
|
47
|
+
result = subject.upsert_all(entries)
|
48
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
49
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
50
|
+
ON CONFLICT ("id") DO UPDATE SET "name"=excluded."name"
|
51
|
+
RETURNING "id"
|
52
|
+
SQL
|
53
|
+
|
54
|
+
result = subject.upsert_all(entries, returning: %i[name])
|
55
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
56
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
57
|
+
ON CONFLICT ("id") DO UPDATE SET "name"=excluded."name"
|
58
|
+
RETURNING "name"
|
59
|
+
SQL
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'does add the where condition without the returning clause' do
|
63
|
+
result = subject.upsert_all(entries, returning: false, where: '1=1')
|
64
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
65
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
66
|
+
ON CONFLICT ("id") DO UPDATE SET "name"=excluded."name"
|
67
|
+
WHERE 1=1
|
68
|
+
SQL
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'does add the where condition with the returning clause' do
|
72
|
+
result = subject.upsert_all(entries, where: '1=1')
|
73
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
74
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
75
|
+
ON CONFLICT ("id") DO UPDATE SET "name"=excluded."name"
|
76
|
+
WHERE 1=1 RETURNING "id"
|
77
|
+
SQL
|
78
|
+
end
|
79
|
+
|
80
|
+
xit 'dows work with model-based where clause' do
|
81
|
+
result = subject.upsert_all(entries, where: Tag.where(name: 'C'))
|
82
|
+
expect(result.squish).to be_eql(<<~SQL.squish)
|
83
|
+
INSERT INTO "tags" ("name") VALUES ('A'), ('B')
|
84
|
+
ON CONFLICT ("id") DO UPDATE SET "name"=excluded."name"
|
85
|
+
WHERE "tags"."name" = 'C' RETURNING "id"
|
86
|
+
SQL
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -387,10 +387,10 @@ RSpec.describe 'TableInheritance' do
|
|
387
387
|
base.create(title: 'Activity test')
|
388
388
|
child.create(title: 'Activity book')
|
389
389
|
other.create(name: 'An author name')
|
390
|
+
base.instance_variable_set(:@casted_dependents, nil)
|
390
391
|
end
|
391
392
|
|
392
393
|
it 'does not affect normal records' do
|
393
|
-
base.instance_variable_set(:@casted_dependents, {})
|
394
394
|
expect(base.first.cast_record).to be_a(base)
|
395
395
|
expect(child.first.cast_record).to be_a(child)
|
396
396
|
expect(other.first.cast_record).to be_a(other)
|
@@ -408,9 +408,28 @@ RSpec.describe 'TableInheritance' do
|
|
408
408
|
end
|
409
409
|
|
410
410
|
it 'does trigger record casting when accessed through inheritance' do
|
411
|
-
base.instance_variable_set(:@casted_dependents, nil)
|
412
411
|
expect(base.second.cast_record).to eql(child.first)
|
413
412
|
end
|
413
|
+
|
414
|
+
context 'using uuid' do
|
415
|
+
let(:base) { Question }
|
416
|
+
let(:child) { QuestionSelect }
|
417
|
+
|
418
|
+
before :each do
|
419
|
+
base.create(title: 'Simple question')
|
420
|
+
child.create(title: 'Select question')
|
421
|
+
base.instance_variable_set(:@casted_dependents, nil)
|
422
|
+
end
|
423
|
+
|
424
|
+
it 'does not affect normal records' do
|
425
|
+
expect(base.first.cast_record).to be_a(base)
|
426
|
+
expect(child.first.cast_record).to be_a(child)
|
427
|
+
end
|
428
|
+
|
429
|
+
it 'does trigger record casting when accessed through inheritance' do
|
430
|
+
expect(base.second.cast_record).to eql(child.first)
|
431
|
+
end
|
432
|
+
end
|
414
433
|
end
|
415
434
|
end
|
416
435
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: torque-postgresql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carlos Silva
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-09-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -215,12 +215,12 @@ files:
|
|
215
215
|
- lib/torque/postgresql/auxiliary_statement.rb
|
216
216
|
- lib/torque/postgresql/auxiliary_statement/settings.rb
|
217
217
|
- lib/torque/postgresql/base.rb
|
218
|
-
- lib/torque/postgresql/coder.rb
|
219
218
|
- lib/torque/postgresql/collector.rb
|
220
219
|
- lib/torque/postgresql/config.rb
|
221
220
|
- lib/torque/postgresql/geometry_builder.rb
|
222
221
|
- lib/torque/postgresql/i18n.rb
|
223
222
|
- lib/torque/postgresql/inheritance.rb
|
223
|
+
- lib/torque/postgresql/insert_all.rb
|
224
224
|
- lib/torque/postgresql/migration.rb
|
225
225
|
- lib/torque/postgresql/migration/command_recorder.rb
|
226
226
|
- lib/torque/postgresql/railtie.rb
|
@@ -242,6 +242,7 @@ files:
|
|
242
242
|
- spec/en.yml
|
243
243
|
- spec/factories/authors.rb
|
244
244
|
- spec/factories/comments.rb
|
245
|
+
- spec/factories/item.rb
|
245
246
|
- spec/factories/posts.rb
|
246
247
|
- spec/factories/tags.rb
|
247
248
|
- spec/factories/texts.rb
|
@@ -259,7 +260,10 @@ files:
|
|
259
260
|
- spec/models/course.rb
|
260
261
|
- spec/models/geometry.rb
|
261
262
|
- spec/models/guest_comment.rb
|
263
|
+
- spec/models/item.rb
|
262
264
|
- spec/models/post.rb
|
265
|
+
- spec/models/question.rb
|
266
|
+
- spec/models/question_select.rb
|
263
267
|
- spec/models/tag.rb
|
264
268
|
- spec/models/text.rb
|
265
269
|
- spec/models/time_keeper.rb
|
@@ -270,13 +274,13 @@ files:
|
|
270
274
|
- spec/tests/arel_spec.rb
|
271
275
|
- spec/tests/auxiliary_statement_spec.rb
|
272
276
|
- spec/tests/belongs_to_many_spec.rb
|
273
|
-
- spec/tests/coder_spec.rb
|
274
277
|
- spec/tests/collector_spec.rb
|
275
278
|
- spec/tests/distinct_on_spec.rb
|
276
279
|
- spec/tests/enum_set_spec.rb
|
277
280
|
- spec/tests/enum_spec.rb
|
278
281
|
- spec/tests/geometric_builder_spec.rb
|
279
282
|
- spec/tests/has_many_spec.rb
|
283
|
+
- spec/tests/insert_all_spec.rb
|
280
284
|
- spec/tests/interval_spec.rb
|
281
285
|
- spec/tests/lazy_spec.rb
|
282
286
|
- spec/tests/period_spec.rb
|
@@ -303,53 +307,57 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
303
307
|
- !ruby/object:Gem::Version
|
304
308
|
version: 1.8.11
|
305
309
|
requirements: []
|
306
|
-
rubygems_version: 3.1.
|
310
|
+
rubygems_version: 3.1.2
|
307
311
|
signing_key:
|
308
312
|
specification_version: 4
|
309
313
|
summary: ActiveRecord extension to access PostgreSQL advanced resources
|
310
314
|
test_files:
|
311
|
-
- spec/
|
315
|
+
- spec/en.yml
|
316
|
+
- spec/factories/authors.rb
|
317
|
+
- spec/factories/comments.rb
|
318
|
+
- spec/factories/item.rb
|
319
|
+
- spec/factories/posts.rb
|
320
|
+
- spec/factories/tags.rb
|
321
|
+
- spec/factories/texts.rb
|
322
|
+
- spec/factories/users.rb
|
323
|
+
- spec/factories/videos.rb
|
324
|
+
- spec/mocks/cache_query.rb
|
325
|
+
- spec/mocks/create_table.rb
|
312
326
|
- spec/models/activity.rb
|
327
|
+
- spec/models/activity_book.rb
|
328
|
+
- spec/models/activity_post.rb
|
329
|
+
- spec/models/activity_post/sample.rb
|
313
330
|
- spec/models/author.rb
|
314
331
|
- spec/models/author_journalist.rb
|
315
332
|
- spec/models/comment.rb
|
316
333
|
- spec/models/course.rb
|
317
|
-
- spec/models/post.rb
|
318
|
-
- spec/models/text.rb
|
319
|
-
- spec/models/user.rb
|
320
|
-
- spec/models/activity_book.rb
|
321
|
-
- spec/models/activity_post.rb
|
322
334
|
- spec/models/geometry.rb
|
323
335
|
- spec/models/guest_comment.rb
|
336
|
+
- spec/models/item.rb
|
337
|
+
- spec/models/post.rb
|
324
338
|
- spec/models/tag.rb
|
339
|
+
- spec/models/text.rb
|
325
340
|
- spec/models/time_keeper.rb
|
341
|
+
- spec/models/user.rb
|
326
342
|
- spec/models/video.rb
|
327
|
-
- spec/
|
328
|
-
- spec/
|
329
|
-
- spec/
|
330
|
-
- spec/factories/tags.rb
|
331
|
-
- spec/factories/texts.rb
|
332
|
-
- spec/factories/users.rb
|
333
|
-
- spec/factories/videos.rb
|
334
|
-
- spec/tests/geometric_builder_spec.rb
|
335
|
-
- spec/tests/range_spec.rb
|
343
|
+
- spec/models/question.rb
|
344
|
+
- spec/models/question_select.rb
|
345
|
+
- spec/spec_helper.rb
|
336
346
|
- spec/tests/arel_spec.rb
|
337
|
-
- spec/tests/
|
338
|
-
- spec/tests/
|
339
|
-
- spec/tests/coder_spec.rb
|
347
|
+
- spec/tests/auxiliary_statement_spec.rb
|
348
|
+
- spec/tests/belongs_to_many_spec.rb
|
340
349
|
- spec/tests/collector_spec.rb
|
341
350
|
- spec/tests/distinct_on_spec.rb
|
351
|
+
- spec/tests/enum_set_spec.rb
|
352
|
+
- spec/tests/enum_spec.rb
|
353
|
+
- spec/tests/geometric_builder_spec.rb
|
354
|
+
- spec/tests/has_many_spec.rb
|
355
|
+
- spec/tests/insert_all_spec.rb
|
342
356
|
- spec/tests/interval_spec.rb
|
343
357
|
- spec/tests/lazy_spec.rb
|
358
|
+
- spec/tests/period_spec.rb
|
344
359
|
- spec/tests/quoting_spec.rb
|
360
|
+
- spec/tests/range_spec.rb
|
345
361
|
- spec/tests/relation_spec.rb
|
346
|
-
- spec/tests/auxiliary_statement_spec.rb
|
347
|
-
- spec/tests/enum_set_spec.rb
|
348
|
-
- spec/tests/has_many_spec.rb
|
349
|
-
- spec/tests/belongs_to_many_spec.rb
|
350
362
|
- spec/tests/table_inheritance_spec.rb
|
351
|
-
- spec/mocks/cache_query.rb
|
352
|
-
- spec/mocks/create_table.rb
|
353
|
-
- spec/en.yml
|
354
|
-
- spec/spec_helper.rb
|
355
363
|
- spec/schema.rb
|
@@ -1,133 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Torque
|
4
|
-
module PostgreSQL
|
5
|
-
module Coder
|
6
|
-
|
7
|
-
# This class represents an Record to be encoded, instead of a literal Array
|
8
|
-
Record = Class.new(Array)
|
9
|
-
|
10
|
-
class << self
|
11
|
-
|
12
|
-
NEED_QUOTE_FOR = /[\\"(){}, \t\n\r\v\f]/m
|
13
|
-
DELIMITER = ','
|
14
|
-
|
15
|
-
# This method replace the +read_array+ method from PG gem
|
16
|
-
# See https://github.com/ged/ruby-pg/blob/master/ext/pg_text_decoder.c#L177
|
17
|
-
# for more information
|
18
|
-
def decode(value)
|
19
|
-
# TODO: Use StringScanner
|
20
|
-
# See http://ruby-doc.org/stdlib-1.9.3/libdoc/strscan/rdoc/StringScanner.html
|
21
|
-
_decode(::StringIO.new(value))
|
22
|
-
end
|
23
|
-
|
24
|
-
# This method replace the ++ method from PG gem
|
25
|
-
# See https://github.com/ged/ruby-pg/blob/master/ext/pg_text_encoder.c#L398
|
26
|
-
# for more information
|
27
|
-
def encode(value)
|
28
|
-
_encode(value)
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def _decode(stream)
|
34
|
-
quoted = 0
|
35
|
-
escaped = false
|
36
|
-
result = []
|
37
|
-
part = String.new
|
38
|
-
|
39
|
-
# Always start getting the non-collection character, the second char
|
40
|
-
stream.getc if stream.pos == 0
|
41
|
-
|
42
|
-
# Check for an empty list
|
43
|
-
return result if %w[} )].include?(stream.getc)
|
44
|
-
|
45
|
-
# If it's not an empty list, return one position before iterating
|
46
|
-
stream.pos -= 1
|
47
|
-
stream.each_char do |c|
|
48
|
-
|
49
|
-
case
|
50
|
-
when quoted < 1
|
51
|
-
case
|
52
|
-
when c == DELIMITER, c == '}', c == ')'
|
53
|
-
|
54
|
-
unless escaped
|
55
|
-
# Non-quoted empty string or NULL as extense
|
56
|
-
part = nil if quoted == 0 && ( part.length == 0 || part == 'NULL' )
|
57
|
-
result << part
|
58
|
-
end
|
59
|
-
|
60
|
-
return result unless c == DELIMITER
|
61
|
-
|
62
|
-
escaped = false
|
63
|
-
quoted = 0
|
64
|
-
part = String.new
|
65
|
-
|
66
|
-
when c == '"'
|
67
|
-
quoted = 1
|
68
|
-
when c == '{', c == '('
|
69
|
-
result << _decode(stream)
|
70
|
-
escaped = true
|
71
|
-
else
|
72
|
-
part << c
|
73
|
-
end
|
74
|
-
when escaped
|
75
|
-
escaped = false
|
76
|
-
part << c
|
77
|
-
when c == '\\'
|
78
|
-
escaped = true
|
79
|
-
when c == '"'
|
80
|
-
if stream.getc == '"'
|
81
|
-
part << c
|
82
|
-
else
|
83
|
-
stream.pos -= 1
|
84
|
-
quoted = -1
|
85
|
-
end
|
86
|
-
else
|
87
|
-
if ( c == '"' || c == "'" ) && stream.getc != c
|
88
|
-
stream.pos -= 1
|
89
|
-
quoted = -1
|
90
|
-
else
|
91
|
-
part << c
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def _encode(list)
|
99
|
-
is_record = list.is_a?(Record)
|
100
|
-
list.map! do |part|
|
101
|
-
case part
|
102
|
-
when NilClass
|
103
|
-
is_record ? '' : 'NULL'
|
104
|
-
when Array
|
105
|
-
_encode(part)
|
106
|
-
else
|
107
|
-
_quote(part.to_s)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
result = is_record ? '(%s)' : '{%s}'
|
112
|
-
result % list.join(DELIMITER)
|
113
|
-
end
|
114
|
-
|
115
|
-
def _quote(string)
|
116
|
-
len = string.length
|
117
|
-
|
118
|
-
# Fast results
|
119
|
-
return '""' if len == 0
|
120
|
-
return '"NULL"' if len == 4 && string == 'NULL'
|
121
|
-
|
122
|
-
# Check if the string don't need quotes
|
123
|
-
return string unless string =~ NEED_QUOTE_FOR
|
124
|
-
|
125
|
-
# Use the original string escape function
|
126
|
-
PG::Connection.escape_string(string).inspect
|
127
|
-
end
|
128
|
-
|
129
|
-
end
|
130
|
-
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
data/spec/tests/coder_spec.rb
DELETED
@@ -1,367 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe 'Complex coder', type: :helper do
|
4
|
-
let(:coder) { Torque::PostgreSQL::Coder }
|
5
|
-
|
6
|
-
context 'on decode' do
|
7
|
-
|
8
|
-
context 'one dimensional arrays' do
|
9
|
-
it 'returns an empty array' do
|
10
|
-
expect(coder.decode(%[{}])).to eql []
|
11
|
-
end
|
12
|
-
|
13
|
-
it 'returns an array of strings' do
|
14
|
-
expect(coder.decode(%[{1,2,3}])).to eql ['1','2','3']
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'returns an array of strings, with nils replacing NULL characters' do
|
18
|
-
expect(coder.decode(%[{1,,NULL}])).to eql ['1',nil,nil]
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'returns an array with the word NULL' do
|
22
|
-
expect(coder.decode(%[{1,"NULL",3}])).to eql ['1','NULL','3']
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'returns an array of strings when containing commas in a quoted string' do
|
26
|
-
expect(coder.decode(%[{1,"2,3",4}])).to eql ['1','2,3','4']
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'returns an array of strings when containing an escaped quote' do
|
30
|
-
expect(coder.decode(%[{1,"2\\",3",4}])).to eql ['1','2",3','4']
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'returns an array of strings when containing an escaped backslash' do
|
34
|
-
expect(coder.decode(%[{1,"2\\\\",3,4}])).to eql ['1','2\\','3','4']
|
35
|
-
expect(coder.decode(%[{1,"2\\\\\\",3",4}])).to eql ['1','2\\",3','4']
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'returns an array containing empty strings' do
|
39
|
-
expect(coder.decode(%[{1,"",3,""}])).to eql ['1', '', '3', '']
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'returns an array containing unicode strings' do
|
43
|
-
expect(coder.decode(%[{"Paragraph 399(b)(i) – “valid leave” – meaning"}])).to eq(['Paragraph 399(b)(i) – “valid leave” – meaning'])
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
context 'two dimensional arrays' do
|
48
|
-
it 'returns an empty array' do
|
49
|
-
expect(coder.decode(%[{{}}])).to eql [[]]
|
50
|
-
expect(coder.decode(%[{{},{}}])).to eql [[],[]]
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'returns an array of strings with a sub array' do
|
54
|
-
expect(coder.decode(%[{1,{2,3},4}])).to eql ['1',['2','3'],'4']
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'returns an array of strings with a sub array' do
|
58
|
-
expect(coder.decode(%[{1,{"2,3"},4}])).to eql ['1',['2,3'],'4']
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'returns an array of strings with a sub array and a quoted }' do
|
62
|
-
expect(coder.decode(%[{1,{"2,}3",,NULL},4}])).to eql ['1',['2,}3',nil,nil],'4']
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'returns an array of strings with a sub array and a quoted {' do
|
66
|
-
expect(coder.decode(%[{1,{"2,{3"},4}])).to eql ['1',['2,{3'],'4']
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'returns an array of strings with a sub array and a quoted { and escaped quote' do
|
70
|
-
expect(coder.decode(%[{1,{"2\\",{3"},4}])).to eql ['1',['2",{3'],'4']
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'returns an array of strings with a sub array with empty strings' do
|
74
|
-
expect(coder.decode(%[{1,{""},4,{""}}])).to eql ['1',[''],'4',['']]
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
context 'three dimensional arrays' do
|
79
|
-
it 'returns an empty array' do
|
80
|
-
expect(coder.decode(%[{{{}}}])).to eql [[[]]]
|
81
|
-
expect(coder.decode(%[{{{},{}},{{},{}}}])).to eql [[[],[]],[[],[]]]
|
82
|
-
end
|
83
|
-
|
84
|
-
it 'returns an array of strings with sub arrays' do
|
85
|
-
expect(coder.decode(%[{1,{2,{3,4}},{NULL,,6},7}])).to eql ['1',['2',['3','4']],[nil,nil,'6'],'7']
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
context 'record syntax' do
|
90
|
-
it 'returns an empty array' do
|
91
|
-
expect(coder.decode(%[()])).to eql []
|
92
|
-
end
|
93
|
-
|
94
|
-
it 'returns an array of strings' do
|
95
|
-
expect(coder.decode(%[(1,2,3)])).to eql ['1','2','3']
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'returns an array of strings, with nils replacing NULL characters' do
|
99
|
-
expect(coder.decode(%[(1,,NULL)])).to eql ['1',nil,nil]
|
100
|
-
end
|
101
|
-
|
102
|
-
it 'returns an array with the word NULL' do
|
103
|
-
expect(coder.decode(%[(1,"NULL",3)])).to eql ['1','NULL','3']
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'returns an array of strings when containing commas in a quoted string' do
|
107
|
-
expect(coder.decode(%[(1,"2,3",4)])).to eql ['1','2,3','4']
|
108
|
-
end
|
109
|
-
|
110
|
-
it 'returns an array of strings when containing an escaped quote' do
|
111
|
-
expect(coder.decode(%[(1,"2\\",3",4)])).to eql ['1','2",3','4']
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'returns an array of strings when containing an escaped backslash' do
|
115
|
-
expect(coder.decode(%[(1,"2\\\\",3,4)])).to eql ['1','2\\','3','4']
|
116
|
-
expect(coder.decode(%[(1,"2\\\\\\",3",4)])).to eql ['1','2\\",3','4']
|
117
|
-
end
|
118
|
-
|
119
|
-
it 'returns an array containing empty strings' do
|
120
|
-
expect(coder.decode(%[(1,"",3,"")])).to eql ['1', '', '3', '']
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'returns an array containing unicode strings' do
|
124
|
-
expect(coder.decode(%[("Paragraph 399(b)(i) – “valid leave” – meaning")])).to eq(['Paragraph 399(b)(i) – “valid leave” – meaning'])
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
context 'array of records' do
|
129
|
-
it 'returns an empty array' do
|
130
|
-
expect(coder.decode(%[{()}])).to eql [[]]
|
131
|
-
expect(coder.decode(%[{(),()}])).to eql [[],[]]
|
132
|
-
end
|
133
|
-
|
134
|
-
it 'returns an array of strings with a sub array' do
|
135
|
-
expect(coder.decode(%[{1,(2,3),4}])).to eql ['1',['2','3'],'4']
|
136
|
-
end
|
137
|
-
|
138
|
-
it 'returns an array of strings with a sub array' do
|
139
|
-
expect(coder.decode(%[{1,("2,3"),4}])).to eql ['1',['2,3'],'4']
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'returns an array of strings with a sub array and a quoted }' do
|
143
|
-
expect(coder.decode(%[{1,("2,}3",,NULL),4}])).to eql ['1',['2,}3',nil,nil],'4']
|
144
|
-
end
|
145
|
-
|
146
|
-
it 'returns an array of strings with a sub array and a quoted {' do
|
147
|
-
expect(coder.decode(%[{1,("2,{3"),4}])).to eql ['1',['2,{3'],'4']
|
148
|
-
end
|
149
|
-
|
150
|
-
it 'returns an array of strings with a sub array and a quoted { and escaped quote' do
|
151
|
-
expect(coder.decode(%[{1,("2\\",{3"),4}])).to eql ['1',['2",{3'],'4']
|
152
|
-
end
|
153
|
-
|
154
|
-
it 'returns an array of strings with a sub array with empty strings' do
|
155
|
-
expect(coder.decode(%[{1,(""),4,("")}])).to eql ['1',[''],'4',['']]
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
context 'mix of record and array' do
|
160
|
-
it 'returns an empty array' do
|
161
|
-
expect(coder.decode(%[({()})])).to eql [[[]]]
|
162
|
-
expect(coder.decode(%[{({},{}),{(),{}}}])).to eql [[[],[]],[[],[]]]
|
163
|
-
end
|
164
|
-
|
165
|
-
it 'returns an array of strings with sub arrays' do
|
166
|
-
expect(coder.decode(%[{1,(2,{3,4}),(NULL,,6),7}])).to eql ['1',['2',['3','4']],[nil,nil,'6'],'7']
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
context 'record complex sample' do
|
171
|
-
it 'may have double double quotes translate to single double quotes' do
|
172
|
-
expect(coder.decode(%[("Test with double "" quoutes")])).to eql ['Test with double " quoutes']
|
173
|
-
end
|
174
|
-
|
175
|
-
it 'double double quotes may occur any number of times' do
|
176
|
-
expect(coder.decode(%[("Only one ""","Now "" two "".",""",""{""}","""""")])).to eql ['Only one "', 'Now " two ".', '","{"}', '""']
|
177
|
-
end
|
178
|
-
|
179
|
-
it 'may have any kind of value' do
|
180
|
-
expect(coder.decode(%[(String,123456,false,true,"2016-01-01 12:00:00",{1,2,3})])).to eql ['String', '123456', 'false', 'true', '2016-01-01 12:00:00', ['1', '2', '3']]
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
end
|
185
|
-
|
186
|
-
context 'on encode' do
|
187
|
-
let(:record) { Torque::PostgreSQL::Coder::Record }
|
188
|
-
|
189
|
-
context 'one dimensional arrays' do
|
190
|
-
it 'receives an empty array' do
|
191
|
-
expect(coder.encode([])).to eql %[{}]
|
192
|
-
end
|
193
|
-
|
194
|
-
it 'receives an array of strings' do
|
195
|
-
expect(coder.encode(['1','2','3'])).to eql %[{1,2,3}]
|
196
|
-
end
|
197
|
-
|
198
|
-
it 'receives an array of strings, with nils replacing NULL characters' do
|
199
|
-
expect(coder.encode(['1',nil,nil])).to eql %[{1,NULL,NULL}]
|
200
|
-
end
|
201
|
-
|
202
|
-
it 'receives an array with the word NULL' do
|
203
|
-
expect(coder.encode(['1','NULL','3'])).to eql %[{1,"NULL",3}]
|
204
|
-
end
|
205
|
-
|
206
|
-
it 'receives an array of strings when containing commas in a quoted string' do
|
207
|
-
expect(coder.encode(['1','2,3','4'])).to eql %[{1,"2,3",4}]
|
208
|
-
end
|
209
|
-
|
210
|
-
it 'receives an array of strings when containing an escaped quote' do
|
211
|
-
expect(coder.encode(['1','2",3','4'])).to eql %[{1,"2\\",3",4}]
|
212
|
-
end
|
213
|
-
|
214
|
-
it 'receives an array of strings when containing an escaped backslash' do
|
215
|
-
expect(coder.encode(['1','2\\','3','4'])).to eql %[{1,"2\\\\",3,4}]
|
216
|
-
expect(coder.encode(['1','2\\",3','4'])).to eql %[{1,"2\\\\\\",3",4}]
|
217
|
-
end
|
218
|
-
|
219
|
-
it 'receives an array containing empty strings' do
|
220
|
-
expect(coder.encode(['1', '', '3', ''])).to eql %[{1,"",3,""}]
|
221
|
-
end
|
222
|
-
|
223
|
-
it 'receives an array containing unicode strings' do
|
224
|
-
expect(coder.encode(['Paragraph 399(b)(i) – “valid leave” – meaning'])).to eql %[{"Paragraph 399(b)(i) – “valid leave” – meaning"}]
|
225
|
-
end
|
226
|
-
end
|
227
|
-
|
228
|
-
context 'two dimensional arrays' do
|
229
|
-
it 'receives an empty array' do
|
230
|
-
expect(coder.encode([[]])).to eql %[{{}}]
|
231
|
-
expect(coder.encode([[],[]])).to eql %[{{},{}}]
|
232
|
-
end
|
233
|
-
|
234
|
-
it 'receives an array of strings with a sub array' do
|
235
|
-
expect(coder.encode(['1',['2','3'],'4'])).to eql %[{1,{2,3},4}]
|
236
|
-
end
|
237
|
-
|
238
|
-
it 'receives an array of strings with a sub array' do
|
239
|
-
expect(coder.encode(['1',['2,3'],'4'])).to eql %[{1,{"2,3"},4}]
|
240
|
-
end
|
241
|
-
|
242
|
-
it 'receives an array of strings with a sub array and a quoted }' do
|
243
|
-
expect(coder.encode(['1',['2,}3',nil,nil],'4'])).to eql %[{1,{"2,}3",NULL,NULL},4}]
|
244
|
-
end
|
245
|
-
|
246
|
-
it 'receives an array of strings with a sub array and a quoted {' do
|
247
|
-
expect(coder.encode(['1',['2,{3'],'4'])).to eql %[{1,{"2,{3"},4}]
|
248
|
-
end
|
249
|
-
|
250
|
-
it 'receives an array of strings with a sub array and a quoted { and escaped quote' do
|
251
|
-
expect(coder.encode(['1',['2",{3'],'4'])).to eql %[{1,{"2\\",{3"},4}]
|
252
|
-
end
|
253
|
-
|
254
|
-
it 'receives an array of strings with a sub array with empty strings' do
|
255
|
-
expect(coder.encode(['1',[''],'4',['']])).to eql %[{1,{""},4,{""}}]
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
context 'three dimensional arrays' do
|
260
|
-
it 'receives an empty array' do
|
261
|
-
expect(coder.encode([[[]]])).to eql %[{{{}}}]
|
262
|
-
expect(coder.encode([[[],[]],[[],[]]])).to eql %[{{{},{}},{{},{}}}]
|
263
|
-
end
|
264
|
-
|
265
|
-
it 'receives an array of strings with sub arrays' do
|
266
|
-
expect(coder.encode(['1',['2',['3','4']],[nil,nil,'6'],'7'])).to eql %[{1,{2,{3,4}},{NULL,NULL,6},7}]
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
context 'record syntax' do
|
271
|
-
it 'receives an empty array' do
|
272
|
-
expect(coder.encode( record.new )).to eql %[()]
|
273
|
-
end
|
274
|
-
|
275
|
-
it 'receives an array of strings' do
|
276
|
-
expect(coder.encode( record.new(['1','2','3']) )).to eql %[(1,2,3)]
|
277
|
-
end
|
278
|
-
|
279
|
-
it 'receives an array of strings, with nils replacing NULL characters' do
|
280
|
-
expect(coder.encode( record.new(['1',nil,nil]) )).to eql %[(1,,)]
|
281
|
-
end
|
282
|
-
|
283
|
-
it 'receives an array with the word NULL' do
|
284
|
-
expect(coder.encode( record.new(['1','NULL','3']) )).to eql %[(1,"NULL",3)]
|
285
|
-
end
|
286
|
-
|
287
|
-
it 'receives an array of strings when containing commas in a quoted string' do
|
288
|
-
expect(coder.encode( record.new(['1','2,3','4']) )).to eql %[(1,"2,3",4)]
|
289
|
-
end
|
290
|
-
|
291
|
-
it 'receives an array of strings when containing an escaped quote' do
|
292
|
-
expect(coder.encode( record.new(['1','2",3','4']) )).to eql %[(1,"2\\",3",4)]
|
293
|
-
end
|
294
|
-
|
295
|
-
it 'receives an array of strings when containing an escaped backslash' do
|
296
|
-
expect(coder.encode( record.new(['1','2\\','3','4']) )).to eql %[(1,"2\\\\",3,4)]
|
297
|
-
expect(coder.encode( record.new(['1','2\\",3','4']) )).to eql %[(1,"2\\\\\\",3",4)]
|
298
|
-
end
|
299
|
-
|
300
|
-
it 'receives an array containing empty strings' do
|
301
|
-
expect(coder.encode( record.new(['1', '', '3', '']) )).to eql %[(1,"",3,"")]
|
302
|
-
end
|
303
|
-
|
304
|
-
it 'receives an array containing unicode strings' do
|
305
|
-
expect(coder.encode( record.new(['Paragraph 399(b)(i) – “valid leave” – meaning']) )).to eql %[("Paragraph 399(b)(i) – “valid leave” – meaning")]
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
context 'array of records' do
|
310
|
-
it 'receives an empty array' do
|
311
|
-
expect(coder.encode([record.new])).to eql %[{()}]
|
312
|
-
expect(coder.encode([record.new,record.new])).to eql %[{(),()}]
|
313
|
-
end
|
314
|
-
|
315
|
-
it 'receives an array of strings with a sub array' do
|
316
|
-
expect(coder.encode(['1',record.new(['2','3']),'4'])).to eql %[{1,(2,3),4}]
|
317
|
-
end
|
318
|
-
|
319
|
-
it 'receives an array of strings with a sub array' do
|
320
|
-
expect(coder.encode(['1',record.new(['2,3']),'4'])).to eql %[{1,("2,3"),4}]
|
321
|
-
end
|
322
|
-
|
323
|
-
it 'receives an array of strings with a sub array and a quoted }' do
|
324
|
-
expect(coder.encode(['1',record.new(['2,}3',nil,nil]),'4'])).to eql %[{1,("2,}3",,),4}]
|
325
|
-
end
|
326
|
-
|
327
|
-
it 'receives an array of strings with a sub array and a quoted {' do
|
328
|
-
expect(coder.encode(['1',record.new(['2,{3']),'4'])).to eql %[{1,("2,{3"),4}]
|
329
|
-
end
|
330
|
-
|
331
|
-
it 'receives an array of strings with a sub array and a quoted { and escaped quote' do
|
332
|
-
expect(coder.encode(['1',record.new(['2",{3']),'4'])).to eql %[{1,("2\\",{3"),4}]
|
333
|
-
end
|
334
|
-
|
335
|
-
it 'receives an array of strings with a sub array with empty strings' do
|
336
|
-
expect(coder.encode(['1',record.new(['']),'4',record.new([''])])).to eql %[{1,(""),4,("")}]
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
context 'mix of record and array' do
|
341
|
-
it 'receives an empty array' do
|
342
|
-
expect(coder.encode( record.new([[record.new,nil]]) )).to eql %[({(),NULL})]
|
343
|
-
expect(coder.encode( [record.new([[], []]),[record.new,[]]] )).to eql %[{({},{}),{(),{}}}]
|
344
|
-
end
|
345
|
-
|
346
|
-
it 'receives an array of strings with sub arrays' do
|
347
|
-
expect(coder.encode(['1',record.new(['2',['3','4']]),record.new([nil,nil,'6']),'7'])).to eql %[{1,(2,{3,4}),(,,6),7}]
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
context 'record complex sample' do
|
352
|
-
it 'may have double double quotes translate to single double quotes' do
|
353
|
-
expect(coder.encode( record.new(['Test with double " quoutes']) )).to eql %[("Test with double \\" quoutes")]
|
354
|
-
end
|
355
|
-
|
356
|
-
it 'double double quotes may occur any number of times' do
|
357
|
-
expect(coder.encode( record.new(['Only one "', 'Now " two ".', '","{"}', '""']) )).to eql %[("Only one \\"","Now \\" two \\".","\\",\\"{\\"}","\\"\\"")]
|
358
|
-
end
|
359
|
-
|
360
|
-
it 'may have any kind of value' do
|
361
|
-
expect(coder.encode( record.new(['String', '123456', 'false', 'true', '2016-01-01 12:00:00', ['1', '2', '3']]) )).to eql %[(String,123456,false,true,"2016-01-01 12:00:00",{1,2,3})]
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
end
|
366
|
-
|
367
|
-
end
|